From 5cf53a2fa7a6f40fc7ea1c1f144f5cf961c1b719 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Wed, 8 May 2024 12:41:03 -0300 Subject: [PATCH 01/17] fix: correctly parse error responses --- .github/workflows/ci.yaml | 4 ++-- Makefile | 8 ++++++++ src/erlazure.erl | 30 ++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 Makefile diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 845efc0..d22aad1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -33,6 +33,6 @@ jobs: restore-keys: | ${{ runner.os }}-hex- - name: build - run: rebar3 compile + run: make compile - name: eunit - run: rebar3 eunit -m erlazure_xml_tests,erlazure_utils_tests,erlazure_queue_tests,erlazure_blob_tests + run: make eunit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ff61a23 --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +REBAR ?= rebar3 + +compile: + $(REBAR) compile + +.PHONY: eunit +eunit: compile + $(REBAR) eunit -v -m erlazure,erlazure_xml_tests,erlazure_utils_tests,erlazure_queue_tests,erlazure_blob_tests diff --git a/src/erlazure.erl b/src/erlazure.erl index f19160f..314cd28 100644 --- a/src/erlazure.erl +++ b/src/erlazure.erl @@ -970,3 +970,33 @@ return_response(Code, Body, State, ExpectedResponseCode, SuccessAtom) -> _ -> {reply, {error, Body}, State} end. + +%%==================================================================== +%% Tests +%%==================================================================== +-ifdef(TEST). + +-include_lib("eunit/include/eunit.hrl"). + +sample_error_raw() -> + <<"\n\n AuthorizationFailure\n Server failed to authenticate the request. Make sure the value of the Authorization header is formed correctly including the signature.\nRequestId:9d2010f6-fe5f-4cc0-ba45-54162b64e1c9\nTime:2024-05-08T14:53:22.751Z\n">>. + +get_error_code_test_() -> + [ { "sample error response", + ?_assertMatch( + {error, #{ code := "AuthorizationFailure" + , message := "Server failed to authenticate" ++ _ + }}, + get_error_code(sample_error_raw()) + ) + } + , { "unparseable error" + , ?_assertMatch( + {error, #{raw := <<"something else">>}}, + get_error_code(<<"something else">>) + ) + } + ]. + +%% END ifdef(TEST) +-endif. From 5f45cf35632c8cf250c4571163c8009786f6ff7b Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Wed, 8 May 2024 12:42:49 -0300 Subject: [PATCH 02/17] fix: correctly encode zero for `Content-Length` --- src/erlazure.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/erlazure.erl b/src/erlazure.erl index 314cd28..fa5e79c 100644 --- a/src/erlazure.erl +++ b/src/erlazure.erl @@ -778,6 +778,8 @@ get_signature_string(Service, HttpMethod, Headers, Account, Path, Parameters) -> get_headers_string(Service, Headers) -> FoldFun = fun(HeaderName, Acc) -> case lists:keyfind(HeaderName, 1, Headers) of + %% Special case: zero length should be an empty line. + {"Content-Length", "0"} -> lists:concat([Acc, "\n"]); {HeaderName, Value} -> lists:concat([Acc, Value, "\n"]); false -> lists:concat([Acc, "\n"]) end From 7e435fca242e0079f6e5bc745e0ae9f2aa7eaeef Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Wed, 8 May 2024 14:06:51 -0300 Subject: [PATCH 03/17] feat: allow specifying endpoint for testing --- README.md | 14 ++++- src/erlazure.erl | 132 +++++++++++++++++++++++++++++------------------ 2 files changed, 95 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 0ab9a35..b77ac74 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Erlazure requires OTP version R16+. * Delete message * Clear messages * Update message - + * Blob storage service * List containers * Create container @@ -39,7 +39,7 @@ Erlazure requires OTP version R16+. * Put block list * Get block list * Lease container - + * Table storage service * List tables * New table @@ -53,6 +53,16 @@ Start an instance of erlazure by calling ```erlazure:start/2``` where **Account* ``` Account and Key are strings. +### Using an emulated service like [Azurite](https://github.com/Azure/Azurite/blob/2bb552e703772b9a57ca713ef271c3c7c624a535/README.md) + +```erlang +%% default dev credentials from Azurite +Account = "devstoreaccount1". +Key = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==". +%% Mind the trailing slash at the end of the endpoint. +{ok, Pid} = erlazure:start(#{account => Account, key => Key, endpoint => "http://127.0.0.1:10000/"}) +``` + ## Calling Azure services Almost each azure services request has three corresponding functions in ```erlazure``` module, the first has minimal set of parameters, the second has additionaly list of ```Options``` and the third has additionaly ```Timeout``` parameter. diff --git a/src/erlazure.erl b/src/erlazure.erl index fa5e79c..424409a 100644 --- a/src/erlazure.erl +++ b/src/erlazure.erl @@ -40,7 +40,7 @@ -behaviour(gen_server). %% API --export([start/2]). +-export([start/1, start/2]). %% Queue API -export([list_queues/1, list_queues/2, list_queues/3]). @@ -81,15 +81,28 @@ %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --record(state, { account = "", key = "", options = [], param_specs = [] }). +-type init_opts() :: #{ + account := string(), + key := string(), + endpoint => string() +}. +-type state_opts() :: #{ + endpoint := undefined | string() +}. + +-record(state, { account = "", key = "", options = #{}, param_specs = [] }). %%==================================================================== %% API %%==================================================================== +-spec start(init_opts()) -> gen_server:start_ret(). +start(Opts) -> + gen_server:start_link(?MODULE, Opts, []). + -spec start(string(), string()) -> {ok, pid()}. start(Account, Key) -> - gen_server:start_link(?MODULE, {Account, Key}, []). + start(#{account => Account, key => Key}). %%==================================================================== %% Queue @@ -323,16 +336,21 @@ delete_table(Pid, TableName) when is_list(TableName) -> %% gen_server callbacks %%==================================================================== -init({Account, Key}) -> +init(InitOpts) -> + #{ account := Account + , key := Key + } = InitOpts, + StateOpts = parse_init_opts(InitOpts), {ok, #state { account = Account, key = Key, + options = StateOpts, param_specs = get_req_param_specs() }}. % List queues handle_call({list_queues, Options}, _From, State) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{params, [{comp, list}] ++ Options}], - ReqContext = new_req_context(?queue_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?queue_service, ReqOptions, State), {?http_ok, Body} = execute_request(ServiceContext, ReqContext), ParseResult = erlazure_queue:parse_queue_list(Body), @@ -345,7 +363,7 @@ handle_call({set_queue_acl, Queue, SignedId=#signed_id{}, Options}, _From, State {path, string:to_lower(Queue)}, {body, erlazure_queue:get_request_body(set_queue_acl, SignedId)}, {params, [{comp, acl}] ++ Options}], - ReqContext = new_req_context(?queue_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), return_response(Code, Body, State, ?http_no_content, created); @@ -355,7 +373,7 @@ handle_call({get_queue_acl, Queue, Options}, _From, State) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{path, string:to_lower(Queue)}, {params, [{comp, acl}] ++ Options}], - ReqContext = new_req_context(?queue_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?queue_service, ReqOptions, State), {?http_ok, Body} = execute_request(ServiceContext, ReqContext), ParseResult = erlazure_queue:parse_queue_acl_response(Body), @@ -367,7 +385,7 @@ handle_call({create_queue, Queue, Options}, _From, State) -> ReqOptions = [{method, put}, {path, string:to_lower(Queue)}, {params, Options}], - ReqContext = new_req_context(?queue_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, _Body} = execute_request(ServiceContext, ReqContext), case Code of @@ -383,7 +401,7 @@ handle_call({delete_queue, Queue, Options}, _From, State) -> ReqOptions = [{method, delete}, {path, string:to_lower(Queue)}, {params, Options}], - ReqContext = new_req_context(?queue_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), return_response(Code, Body, State, ?http_no_content, deleted); @@ -395,7 +413,7 @@ handle_call({put_message, Queue, Message, Options}, _From, State) -> {path, lists:concat([string:to_lower(Queue), "/messages"])}, {body, erlazure_queue:get_request_body(put_message, Message)}, {params, Options}], - ReqContext = new_req_context(?queue_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), return_response(Code, Body, State, ?http_created, created); @@ -405,7 +423,7 @@ handle_call({get_messages, Queue, Options}, _From, State) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{path, string:to_lower(Queue) ++ "/messages"}, {params, Options}], - ReqContext = new_req_context(?queue_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?queue_service, ReqOptions, State), {?http_ok, Body} = execute_request(ServiceContext, ReqContext), {reply, erlazure_queue:parse_queue_messages_list(Body), State}; @@ -415,7 +433,7 @@ handle_call({peek_messages, Queue, Options}, _From, State) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{path, string:to_lower(Queue) ++ "/messages"}, {params, [{peek_only, true}] ++ Options}], - ReqContext = new_req_context(?queue_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?queue_service, ReqOptions, State), {?http_ok, Body} = execute_request(ServiceContext, ReqContext), {reply, erlazure_queue:parse_queue_messages_list(Body), State}; @@ -426,7 +444,7 @@ handle_call({delete_message, Queue, MessageId, PopReceipt, Options}, _From, Stat ReqOptions = [{method, delete}, {path, lists:concat([string:to_lower(Queue), "/messages/", MessageId])}, {params, [{pop_receipt, PopReceipt}] ++ Options}], - ReqContext = new_req_context(?queue_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), return_response(Code, Body, State, ?http_no_content, deleted); @@ -437,7 +455,7 @@ handle_call({clear_messages, Queue, Options}, _From, State) -> ReqOptions = [{method, delete}, {path, string:to_lower(Queue) ++ "/messages"}, {params, Options}], - ReqContext = new_req_context(?queue_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), return_response(Code, Body, State, ?http_no_content, deleted); @@ -451,7 +469,7 @@ handle_call({update_message, Queue, UpdatedMessage=#queue_message{}, VisibilityT {path, lists:concat([string:to_lower(Queue), "/messages/", UpdatedMessage#queue_message.id])}, {body, erlazure_queue:get_request_body(update_message, UpdatedMessage#queue_message.text)}, {params, Params ++ Options}], - ReqContext = new_req_context(?queue_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), return_response(Code, Body, State, ?http_no_content, updated); @@ -460,7 +478,7 @@ handle_call({update_message, Queue, UpdatedMessage=#queue_message{}, VisibilityT handle_call({list_containers, Options}, _From, State) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{params, [{comp, list}] ++ Options}], - ReqContext = new_req_context(?blob_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?blob_service, ReqOptions, State), {?http_ok, Body} = execute_request(ServiceContext, ReqContext), {ok, Containers} = erlazure_blob:parse_container_list(Body), @@ -472,7 +490,7 @@ handle_call({create_container, Name, Options}, _From, State) -> ReqOptions = [{method, put}, {path, Name}, {params, [{res_type, container}] ++ Options}], - ReqContext = new_req_context(?blob_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), case Code of ?http_created -> {reply, {ok, created}, State}; @@ -485,7 +503,7 @@ handle_call({delete_container, Name, Options}, _From, State) -> ReqOptions = [{method, delete}, {path, Name}, {params, [{res_type, container}] ++ Options}], - RequestContext = new_req_context(?blob_service, State#state.account, State#state.param_specs, ReqOptions), + RequestContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, RequestContext), return_response(Code, Body, State, ?http_accepted, deleted); @@ -499,7 +517,7 @@ handle_call({lease_container, Name, Mode, Options}, _From, State) -> ReqOptions = [{method, put}, {path, Name}, {params, Params ++ Options}], - ReqContext = new_req_context(?blob_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), return_response(Code, Body, State, ?http_accepted, deleted); @@ -511,7 +529,7 @@ handle_call({list_blobs, Name, Options}, _From, State) -> {res_type, container}], ReqOptions = [{path, Name}, {params, Params ++ Options}], - ReqContext = new_req_context(?blob_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?blob_service, ReqOptions, State), {?http_ok, Body} = execute_request(ServiceContext, ReqContext), {ok, Blobs} = erlazure_blob:parse_blob_list(Body), @@ -524,7 +542,7 @@ handle_call({put_blob, Container, Name, Type = block_blob, Data, Options}, _From {path, lists:concat([Container, "/", Name])}, {body, Data}, {params, [{blob_type, Type}] ++ Options}], - ReqContext = new_req_context(?blob_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?blob_service, ReqOptions, State), ReqContext1 = case proplists:get_value(content_type, Options) of undefined -> ReqContext#req_context{ content_type = "application/octet-stream" }; ContentType -> ReqContext#req_context{ content_type = ContentType } @@ -541,7 +559,7 @@ handle_call({put_blob, Container, Name, Type = page_blob, ContentLength, Options ReqOptions = [{method, put}, {path, lists:concat([Container, "/", Name])}, {params, Params ++ Options}], - ReqContext = new_req_context(?blob_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), return_response(Code, Body, State, ?http_created, created); @@ -551,7 +569,7 @@ handle_call({get_blob, Container, Blob, Options}, _From, State) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{path, lists:concat([Container, "/", Blob])}, {params, Options}], - ReqContext = new_req_context(?blob_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), case Code of @@ -559,7 +577,7 @@ handle_call({get_blob, Container, Blob, Options}, _From, State) -> {reply, {ok, Body}, State}; ?http_partial_content-> {reply, {ok, Body}, State}; - _ -> {reply, {error, Body}, State} + _ -> {reply, {error, Body}, State} end; % Snapshot blob @@ -568,7 +586,7 @@ handle_call({snapshot_blob, Container, Blob, Options}, _From, State) -> ReqOptions = [{method, put}, {path, lists:concat([Container, "/", Blob])}, {params, [{comp, snapshot}] ++ Options}], - ReqContext = new_req_context(?blob_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), return_response(Code, Body, State, ?http_created, created); @@ -579,7 +597,7 @@ handle_call({copy_blob, Container, Blob, Source, Options}, _From, State) -> ReqOptions = [{method, put}, {path, lists:concat([Container, "/", Blob])}, {params, [{blob_copy_source, Source}] ++ Options}], - ReqContext = new_req_context(?blob_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), return_response(Code, Body, State, ?http_accepted, created); @@ -590,7 +608,7 @@ handle_call({delete_blob, Container, Blob, Options}, _From, State) -> ReqOptions = [{method, delete}, {path, lists:concat([Container, "/", Blob])}, {params, Options}], - ReqContext = new_req_context(?blob_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), return_response(Code, Body, State, ?http_accepted, deleted); @@ -604,7 +622,7 @@ handle_call({put_block, Container, Blob, BlockId, Content, Options}, _From, Stat {path, lists:concat([Container, "/", Blob])}, {body, Content}, {params, Params ++ Options}], - ReqContext = new_req_context(?blob_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), return_response(Code, Body, State, ?http_created, created); @@ -616,7 +634,7 @@ handle_call({put_block_list, Container, Blob, BlockRefs, Options}, _From, State) {path, lists:concat([Container, "/", Blob])}, {body, erlazure_blob:get_request_body(BlockRefs)}, {params, [{comp, "blocklist"}] ++ Options}], - ReqContext = new_req_context(?blob_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), return_response(Code, Body, State, ?http_created, created); @@ -626,7 +644,7 @@ handle_call({get_block_list, Container, Blob, Options}, _From, State) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{path, lists:concat([Container, "/", Blob])}, {params, [{comp, "blocklist"}] ++ Options}], - ReqContext = new_req_context(?blob_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?blob_service, ReqOptions, State), {?http_ok, Body} = execute_request(ServiceContext, ReqContext), {ok, BlockList} = erlazure_blob:parse_block_list(Body), @@ -644,7 +662,7 @@ handle_call({acquire_blob_lease, Container, Blob, ProposedId, Duration, Options} ReqOptions = [{method, put}, {path, lists:concat([Container, "/", Blob])}, {params, Params ++ Options}], - ReqContext = new_req_context(?blob_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), return_response(Code, Body, State, ?http_created, acquired); @@ -654,7 +672,7 @@ handle_call({list_tables, Options}, _From, State) -> ServiceContext = new_service_context(?table_service, State), ReqOptions = [{path, "Tables"}, {params, Options}], - ReqContext = new_req_context(?table_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?table_service, ReqOptions, State), {?http_ok, Body} = execute_request(ServiceContext, ReqContext), {reply, {ok, erlazure_table:parse_table_list(Body)}, State}; @@ -665,13 +683,13 @@ handle_call({new_table, TableName}, _From, State) -> ReqOptions = [{path, "Tables"}, {method, post}, {body, jsx:encode([{<<"TableName">>, TableName}])}], - ReqContext = new_req_context(?table_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?table_service, ReqOptions, State), ReqContext1 = ReqContext#req_context{ content_type = ?json_content_type }, {Code, Body} = execute_request(ServiceContext, ReqContext1), return_response(Code, Body, State, ?http_created, created); % Get host -handle_call({get_host, Service, Domain}, _From, State) -> +handle_call({get_host, Service, Domain}, _From, State) -> Account = State#state.account, Host = lists:concat([Account, ".", erlang:atom_to_list(Service), Domain]), {reply, Host, State}; @@ -681,7 +699,7 @@ handle_call({delete_table, TableName}, _From, State) -> ServiceContext = new_service_context(?table_service, State), ReqOptions = [{path, io:format("Tables('~s')", [TableName])}, {method, delete}], - ReqContext = new_req_context(?table_service, State#state.account, State#state.param_specs, ReqOptions), + ReqContext = new_req_context(?table_service, ReqOptions, State), {?http_no_content, _} = execute_request(ServiceContext, ReqContext), {reply, {ok, deleted}, State}. @@ -745,21 +763,28 @@ execute_request(ServiceContext = #service_context{}, ReqContext = #req_context{} {Code, Body}; {ok, {{_, _, _}, _, Body}} -> - try get_error_code(Body) of - ErrorCodeAtom -> {error, ErrorCodeAtom} - catch - _ -> {error, Body} - end - end. + get_error_code(Body) + end. get_error_code(Body) -> + try do_get_error_code(Body) of + ErrorCodeContext -> {error, ErrorCodeContext} + catch + _:_ -> {error, #{raw => Body}} + end. + +do_get_error_code(Body) -> {ParseResult, _} = xmerl_scan:string(binary_to_list(Body)), ErrorContent = ParseResult#xmlElement.content, - ErrorContentHead = hd(ErrorContent), - CodeContent = ErrorContentHead#xmlElement.content, - CodeContentHead = hd(CodeContent), - ErrorCodeText = CodeContentHead#xmlText.value, - list_to_atom(ErrorCodeText). + Code = + lists:flatten([Txt + || #xmlElement{name = 'Code', content = Cs} <- ErrorContent, + #xmlText{value = Txt} <- Cs]), + Message = + lists:flatten([Txt + || #xmlElement{name = 'Message', content = Cs} <- ErrorContent, + #xmlText{value = Txt} <- Cs]), + #{code => Code, message => Message}. get_shared_key(Service, Account, Key, HttpMethod, Path, Parameters, Headers) -> SignatureString = get_signature_string(Service, HttpMethod, Headers, Account, Path, Parameters), @@ -805,7 +830,9 @@ get_headers_string(Service, Headers) -> sign_string(Key, StringToSign) -> hmac(base64:decode(Key), StringToSign). -build_uri_base(Service, Account) -> +build_uri_base(_Service, #state{options = #{endpoint := Endpoint}}) when Endpoint =/= undefined -> + Endpoint; +build_uri_base(Service, #state{account = Account}) -> lists:concat(["https://", get_host(Service, Account), "/"]). get_host(Service, Account) -> @@ -903,7 +930,7 @@ new_service_context(?table_service, State=#state{}) -> account = State#state.account, key = State#state.key }. -new_req_context(Service, Account, ParamSpecs, Options) -> +new_req_context(Service, Options, State) -> Method = proplists:get_value(method, Options, get), Path = proplists:get_value(path, Options, ""), Body = proplists:get_value(body, Options, ""), @@ -917,10 +944,11 @@ new_req_context(Service, Account, ParamSpecs, Options) -> true -> [] end, + ParamSpecs = State#state.param_specs, ReqParams = get_req_uri_params(Params, ParamSpecs), ReqHeaders = lists:append([Headers, AddHeaders, get_req_headers(Params, ParamSpecs)]), - #req_context{ address = build_uri_base(Service, Account), + #req_context{ address = build_uri_base(Service, State), path = Path, method = Method, body = Body, @@ -973,6 +1001,12 @@ return_response(Code, Body, State, ExpectedResponseCode, SuccessAtom) -> {reply, {error, Body}, State} end. +-spec parse_init_opts(init_opts()) -> state_opts(). +parse_init_opts(InitOpts) -> + Endpoint = maps:get(endpoint, InitOpts, undefined), + #{ endpoint => Endpoint + }. + %%==================================================================== %% Tests %%==================================================================== From 7c0616386e0c223038579bf36f3c87517289a12f Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Wed, 8 May 2024 14:09:07 -0300 Subject: [PATCH 04/17] test: add basic smoke tests for blob storage ops --- .github/workflows/ci.yaml | 6 ++ Makefile | 6 +- test/erlazure_SUITE.erl | 120 ++++++++++++++++++++++++++++++++++++++ test/start_azurite.sh | 5 ++ test/test_utils.erl | 36 +++++++++++- 5 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 test/erlazure_SUITE.erl create mode 100755 test/start_azurite.sh diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d22aad1..3b992e3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -36,3 +36,9 @@ jobs: run: make compile - name: eunit run: make eunit + - name: start azurite + run: test/start_azurite.sh + - name: ct + run: make ct + env: + AZURITE_ENDPOINT: "http://127.0.0.1:10000/" diff --git a/Makefile b/Makefile index ff61a23..3bdf428 100644 --- a/Makefile +++ b/Makefile @@ -4,5 +4,9 @@ compile: $(REBAR) compile .PHONY: eunit -eunit: compile +eunit: $(REBAR) eunit -v -m erlazure,erlazure_xml_tests,erlazure_utils_tests,erlazure_queue_tests,erlazure_blob_tests + +.PHONY: ct +ct: + $(REBAR) ct -v --readable=true diff --git a/test/erlazure_SUITE.erl b/test/erlazure_SUITE.erl new file mode 100644 index 0000000..b86320a --- /dev/null +++ b/test/erlazure_SUITE.erl @@ -0,0 +1,120 @@ +%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Redistribution and use in source and binary forms, with or without +%% modification, are permitted provided that the following conditions are met: +%% +%% * Redistributions of source code must retain the above copyright notice, +%% this list of conditions and the following disclaimer. +%% * Redistributions in binary form must reproduce the above copyright +%% notice, this list of conditions and the following disclaimer in the +%% documentation and/or other materials provided with the distribution. +%% * Neither the name of erlazure nor the names of its contributors may be used to +%% endorse or promote products derived from this software without specific +%% prior written permission. +%% +%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +%% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +%% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +%% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +%% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +%% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +%% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +%% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +%% POSSIBILITY OF SUCH DAMAGE. +-module(erlazure_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). +-include("erlazure.hrl"). + +%%------------------------------------------------------------------------------ +%% Type definitions +%%------------------------------------------------------------------------------ + +%% Default Azurite credentials +%% See: https://github.com/Azure/Azurite/blob/main/README.md#default-storage-account +-define(ACCOUNT, "devstoreaccount1"). +-define(KEY, "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="). + +%%------------------------------------------------------------------------------ +%% CT boilerplate +%%------------------------------------------------------------------------------ + +all() -> + test_utils:all(?MODULE). + +init_per_suite(Config) -> + Endpoint = os:getenv("AZURITE_ENDPOINT", "http://127.0.0.1:10000/"), + #{host := Host, port := Port} = uri_string:parse(Endpoint), + case test_utils:is_tcp_server_available(Host, Port) of + false -> + throw(endpoint_unavailable); + true -> + ok + end, + [{endpoint, Endpoint} | Config]. + +end_per_suite(_Config) -> + ok. + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, Config) -> + delete_all_containers(Config), + ok. + +%%------------------------------------------------------------------------------ +%% Helper fns +%%------------------------------------------------------------------------------ + +start(Config) -> + Endpoint = ?config(endpoint, Config), + {ok, Pid} = erlazure:start(#{account => ?ACCOUNT, key => ?KEY, endpoint => Endpoint}), + Pid. + +delete_all_containers(Config) -> + Pid = start(Config), + {Containers, _} = erlazure:list_containers(Pid), + lists:foreach( + fun(#blob_container{name = Name}) -> + {ok, deleted} = erlazure:delete_container(Pid, Name) + end, + Containers). + +container_name(Name) -> + IOList = re:replace(atom_to_list(Name), <<"[^a-z0-9-]">>, <<"-">>, [global]), + binary_to_list(iolist_to_binary(IOList)). + +%%------------------------------------------------------------------------------ +%% Test cases : blob storage +%%------------------------------------------------------------------------------ + +%% Basic smoke test for basic blob storage operations. +t_blob_storage_smoke_test(Config) -> + Endpoint = ?config(endpoint, Config), + {ok, Pid} = erlazure:start(#{account => ?ACCOUNT, key => ?KEY, endpoint => Endpoint}), + %% Create a container + Container = container_name(?FUNCTION_NAME), + ?assertMatch({[], _}, erlazure:list_containers(Pid)), + ?assertMatch({ok, created}, erlazure:create_container(Pid, Container)), + %% Upload some blobs + ?assertMatch({ok, created}, erlazure:put_block_blob(Pid, Container, "blob1", <<"1">>)), + ?assertMatch({ok, created}, erlazure:put_block_blob(Pid, Container, "blob2", <<"2">>)), + ?assertMatch({[#cloud_blob{name = "blob1"}, #cloud_blob{name = "blob2"}], _}, + erlazure:list_blobs(Pid, Container)), + %% Read back data + ?assertMatch({ok, <<"1">>}, erlazure:get_blob(Pid, Container, "blob1")), + ?assertMatch({ok, <<"2">>}, erlazure:get_blob(Pid, Container, "blob2")), + %% Delete blob + ?assertMatch({ok, deleted}, erlazure:delete_blob(Pid, Container, "blob1")), + ?assertMatch({[#cloud_blob{name = "blob2"}], _}, + erlazure:list_blobs(Pid, Container)), + %% Delete container + ?assertMatch({ok, deleted}, erlazure:delete_container(Pid, Container)), + ok. diff --git a/test/start_azurite.sh b/test/start_azurite.sh new file mode 100755 index 0000000..0ec575e --- /dev/null +++ b/test/start_azurite.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -exuo pipefail + +docker run -d --rm -p 10000:10000 --name azurite mcr.microsoft.com/azure-storage/azurite azurite-blob --blobHost 0.0.0.0 -d debug.log diff --git a/test/test_utils.erl b/test/test_utils.erl index 3c7e02d..05f37e1 100644 --- a/test/test_utils.erl +++ b/test/test_utils.erl @@ -28,8 +28,38 @@ -module(test_utils). -author("Dmitry Kataskin"). -%% API --export([append_ticks/1, get_ticks/0, read_file/1]). +-compile(export_all). +-compile(nowarn_export_all). + +-define(DEFAULT_TCP_SERVER_CHECK_AVAIL_TIMEOUT, 1000). + +all(Module) -> + lists:usort([ + F + || {F, 1} <- Module:module_info(exports), + string:substr(atom_to_list(F), 1, 2) == "t_" + ]). + +-spec is_tcp_server_available( + Host :: inet:socket_address() | inet:hostname(), + Port :: inet:port_number() +) -> boolean. +is_tcp_server_available(Host, Port) -> + is_tcp_server_available(Host, Port, ?DEFAULT_TCP_SERVER_CHECK_AVAIL_TIMEOUT). + +-spec is_tcp_server_available( + Host :: inet:socket_address() | inet:hostname(), + Port :: inet:port_number(), + Timeout :: integer() +) -> boolean. +is_tcp_server_available(Host, Port, Timeout) -> + case gen_tcp:connect(Host, Port, [], Timeout) of + {ok, Socket} -> + gen_tcp:close(Socket), + true; + {error, _} -> + false + end. append_ticks(Name) -> Name ++ integer_to_list(get_ticks()). @@ -43,4 +73,4 @@ read_file(FileName) -> erlang:binary_to_list(Binary). file_path(File) -> - filename:join([code:priv_dir(erlazure), responses, File]). \ No newline at end of file + filename:join([code:priv_dir(erlazure), responses, File]). From 3ec5e516995db2a8892e85eb8175d7b66543e6bf Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Wed, 8 May 2024 14:25:29 -0300 Subject: [PATCH 05/17] feat: wrap keys to avoid printing when crashing --- src/erlazure.erl | 29 +++++++++++++++++++++++++---- test/erlazure_SUITE.erl | 7 +++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/erlazure.erl b/src/erlazure.erl index 424409a..aa67bce 100644 --- a/src/erlazure.erl +++ b/src/erlazure.erl @@ -83,7 +83,7 @@ -type init_opts() :: #{ account := string(), - key := string(), + key := string() | function(), endpoint => string() }. -type state_opts() :: #{ @@ -97,8 +97,9 @@ %%==================================================================== -spec start(init_opts()) -> gen_server:start_ret(). -start(Opts) -> - gen_server:start_link(?MODULE, Opts, []). +start(InitOpts0) -> + InitOpts = ensure_wrapped_key(InitOpts0), + gen_server:start_link(?MODULE, InitOpts, []). -spec start(string(), string()) -> {ok, pid()}. start(Account, Key) -> @@ -828,7 +829,7 @@ get_headers_string(Service, Headers) -> -spec sign_string(base64:ascii_string(), string()) -> binary(). sign_string(Key, StringToSign) -> - hmac(base64:decode(Key), StringToSign). + hmac(base64:decode(unwrap(Key)), StringToSign). build_uri_base(_Service, #state{options = #{endpoint := Endpoint}}) when Endpoint =/= undefined -> Endpoint; @@ -1007,6 +1008,26 @@ parse_init_opts(InitOpts) -> #{ endpoint => Endpoint }. +unwrap(Fun) when is_function(Fun) -> + %% handle potentially nested functions + unwrap(Fun()); +unwrap(V) -> + V. + +wrap(V) -> + fun() -> + V + end. + +-spec ensure_wrapped_key(init_opts()) -> init_opts(). +ensure_wrapped_key(#{key := Key} = InitOpts) -> + case is_function(Key) of + true -> + InitOpts; + false -> + InitOpts#{key := wrap(Key)} + end. + %%==================================================================== %% Tests %%==================================================================== diff --git a/test/erlazure_SUITE.erl b/test/erlazure_SUITE.erl index b86320a..d3a1dcc 100644 --- a/test/erlazure_SUITE.erl +++ b/test/erlazure_SUITE.erl @@ -118,3 +118,10 @@ t_blob_storage_smoke_test(Config) -> %% Delete container ?assertMatch({ok, deleted}, erlazure:delete_container(Pid, Container)), ok. + +%% Basic smoke test to check that we can pass already wrapped keys to `erlazure:start`. +t_blob_storage_wrapped_key(Config) -> + Endpoint = ?config(endpoint, Config), + {ok, Pid} = erlazure:start(#{account => ?ACCOUNT, key => ?KEY, endpoint => Endpoint}), + ?assertMatch({[], _}, erlazure:list_containers(Pid)), + ok. From 6f06767d8f520353663c50b6e0d6cf94adf928a6 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Wed, 8 May 2024 14:48:20 -0300 Subject: [PATCH 06/17] ci: pin `jsx` version --- rebar.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.config b/rebar.config index c31a076..286e3be 100644 --- a/rebar.config +++ b/rebar.config @@ -1,5 +1,5 @@ {deps, [ - {jsx, ".*", {git, "https://github.com/talentdeficit/jsx.git", "main"}} + {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "v3.1.0"}}} ]}. {eunit_opts, [verbose]}. {cover_enabled, true}. From 618c911bf034455f3df7a25f43fd0bf4fff38be9 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Wed, 8 May 2024 15:32:22 -0300 Subject: [PATCH 07/17] feat: implement `put_append_blob` and `append_block` https://learn.microsoft.com/en-us/rest/api/storageservices/put-blob?tabs=microsoft-entra-id#request https://learn.microsoft.com/en-us/rest/api/storageservices/append-block?tabs=microsoft-entra-id --- src/erlazure.erl | 41 +++++++++++++++++++++++++++++++++++++++++ src/erlazure_blob.erl | 2 ++ test/erlazure_SUITE.erl | 24 ++++++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/src/erlazure.erl b/src/erlazure.erl index aa67bce..ae76ccf 100644 --- a/src/erlazure.erl +++ b/src/erlazure.erl @@ -62,6 +62,8 @@ -export([lease_container/3, lease_container/4, lease_container/5]). -export([list_blobs/2, list_blobs/3, list_blobs/4]). -export([put_block_blob/4, put_block_blob/5, put_block_blob/6]). +-export([put_append_blob/3, put_append_blob/4, put_append_blob/5]). +-export([append_block/4, append_block/5, append_block/6]). -export([put_page_blob/4, put_page_blob/5, put_page_blob/6]). -export([get_blob/3, get_blob/4, get_blob/5]). -export([snapshot_blob/3, snapshot_blob/4, snapshot_blob/5]). @@ -242,6 +244,20 @@ put_page_blob(Pid, Container, Name, ContentLength, Options) -> put_page_blob(Pid, Container, Name, ContentLength, Options, Timeout) when is_list(Options); is_integer(Timeout) -> gen_server:call(Pid, {put_blob, Container, Name, page_blob, ContentLength, Options}, Timeout). +put_append_blob(Pid, Container, Name) -> + put_append_blob(Pid, Container, Name, []). +put_append_blob(Pid, Container, Name, Options) -> + put_append_blob(Pid, Container, Name, Options, ?gen_server_call_default_timeout). +put_append_blob(Pid, Container, Name, Options, Timeout) when is_list(Options); is_integer(Timeout) -> + gen_server:call(Pid, {put_blob, Container, Name, append_blob, Options}, Timeout). + +append_block(Pid, Container, Name, Data) -> + append_block(Pid, Container, Name, Data, []). +append_block(Pid, Container, Name, Data, Options) -> + append_block(Pid, Container, Name, Data, Options, ?gen_server_call_default_timeout). +append_block(Pid, Container, Name, Data, Options, Timeout) when is_list(Options); is_integer(Timeout) -> + gen_server:call(Pid, {append_block, Container, Name, Data, Options}, Timeout). + list_blobs(Pid, Container) -> list_blobs(Pid, Container, []). list_blobs(Pid, Container, Options) -> @@ -565,6 +581,31 @@ handle_call({put_blob, Container, Name, Type = page_blob, ContentLength, Options {Code, Body} = execute_request(ServiceContext, ReqContext), return_response(Code, Body, State, ?http_created, created); +% Put append blob +handle_call({put_blob, Container, Name, Type = append_blob, Options}, _From, State) -> + ServiceContext = new_service_context(?blob_service, State), + Params = [{blob_type, Type}], + ReqOptions = [{method, put}, + {path, lists:concat([Container, "/", Name])}, + {params, Params ++ Options}], + ReqContext = new_req_context(?blob_service, ReqOptions, State), + + {Code, Body} = execute_request(ServiceContext, ReqContext), + return_response(Code, Body, State, ?http_created, created); + +% Append block +handle_call({append_block, Container, Name, Data, Options}, _From, State) -> + ServiceContext = new_service_context(?blob_service, State), + Params = [{comp, "appendblock"}], + ReqOptions = [{method, put}, + {path, lists:concat([Container, "/", Name])}, + {body, Data}, + {params, Params ++ Options}], + ReqContext = new_req_context(?blob_service, ReqOptions, State), + + {Code, Body} = execute_request(ServiceContext, ReqContext), + return_response(Code, Body, State, ?http_created, appended); + % Get blob handle_call({get_blob, Container, Blob, Options}, _From, State) -> ServiceContext = new_service_context(?blob_service, State), diff --git a/src/erlazure_blob.erl b/src/erlazure_blob.erl index 53791ff..0dd5f8f 100644 --- a/src/erlazure_blob.erl +++ b/src/erlazure_blob.erl @@ -166,9 +166,11 @@ parse_block(#xmlElement{ content = Content }, Type) -> lists:foldl(FoldFun, #blob_block{ type = Type }, Nodes). str_to_blob_type("BlockBlob") -> block_blob; +str_to_blob_type("AppendBlob") -> append_blob; str_to_blob_type("PageBlob") -> page_blob. blob_type_to_str(block_blob) -> "BlockBlob"; +blob_type_to_str(append_blob) -> "AppendBlob"; blob_type_to_str(page_blob) -> "PageBlob". block_type_to_node(uncommitted) -> 'Uncommitted'; diff --git a/test/erlazure_SUITE.erl b/test/erlazure_SUITE.erl index d3a1dcc..829dc1c 100644 --- a/test/erlazure_SUITE.erl +++ b/test/erlazure_SUITE.erl @@ -125,3 +125,27 @@ t_blob_storage_wrapped_key(Config) -> {ok, Pid} = erlazure:start(#{account => ?ACCOUNT, key => ?KEY, endpoint => Endpoint}), ?assertMatch({[], _}, erlazure:list_containers(Pid)), ok. + +%% Basic smoke test for append blob storage operations. +t_append_blob_smoke_test(Config) -> + Endpoint = ?config(endpoint, Config), + {ok, Pid} = erlazure:start(#{account => ?ACCOUNT, key => ?KEY, endpoint => Endpoint}), + %% Create a container + Container = container_name(?FUNCTION_NAME), + ?assertMatch({[], _}, erlazure:list_containers(Pid)), + ?assertMatch({ok, created}, erlazure:create_container(Pid, Container)), + %% Upload some blobs + ?assertMatch({ok, created}, erlazure:put_append_blob(Pid, Container, "blob1")), + ?assertMatch({ok, appended}, erlazure:append_block(Pid, Container, "blob1", <<"1">>)), + ?assertMatch({ok, appended}, erlazure:append_block(Pid, Container, "blob1", <<"\n">>)), + ?assertMatch({ok, appended}, erlazure:append_block(Pid, Container, "blob1", <<"2">>)), + ?assertMatch({[#cloud_blob{name = "blob1"}], _}, + erlazure:list_blobs(Pid, Container)), + %% Read back data + ?assertMatch({ok, <<"1\n2">>}, erlazure:get_blob(Pid, Container, "blob1")), + %% Delete blob + ?assertMatch({ok, deleted}, erlazure:delete_blob(Pid, Container, "blob1")), + ?assertMatch({[], _}, erlazure:list_blobs(Pid, Container)), + %% Delete container + ?assertMatch({ok, deleted}, erlazure:delete_container(Pid, Container)), + ok. From 15edc34523a0acb0c3d94babd2127fb7f0c5909b Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Thu, 9 May 2024 15:43:36 -0300 Subject: [PATCH 08/17] chore: update MS API version for blob storage service --- include/erlazure.hrl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/erlazure.hrl b/include/erlazure.hrl index c7ed382..73efa39 100644 --- a/include/erlazure.hrl +++ b/include/erlazure.hrl @@ -31,7 +31,7 @@ -define(http_created, 201). -define(http_accepted, 202). -define(http_no_content, 204). --define(http_partial_content, 206). +-define(http_partial_content, 206). -define(blob_service, blob). -define(table_service, table). @@ -39,7 +39,7 @@ -define(file_service, file). -define(queue_service_ver, "2014-02-14"). --define(blob_service_ver, "2014-02-14"). +-define(blob_service_ver, "2024-05-04"). -define(table_service_ver, "2014-02-14"). -define(file_service_ver, "2014-02-14"). From 4cf05d754e6288d65cc899059a918e28d8cc63c9 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Thu, 9 May 2024 15:56:31 -0300 Subject: [PATCH 09/17] fix: handle httpc errors --- src/erlazure.erl | 15 +++++++++++---- test/erlazure_SUITE.erl | 13 +++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/erlazure.erl b/src/erlazure.erl index ae76ccf..1ad6d47 100644 --- a/src/erlazure.erl +++ b/src/erlazure.erl @@ -497,9 +497,13 @@ handle_call({list_containers, Options}, _From, State) -> ReqOptions = [{params, [{comp, list}] ++ Options}], ReqContext = new_req_context(?blob_service, ReqOptions, State), - {?http_ok, Body} = execute_request(ServiceContext, ReqContext), - {ok, Containers} = erlazure_blob:parse_container_list(Body), - {reply, Containers, State}; + case execute_request(ServiceContext, ReqContext) of + {?http_ok, Body} -> + {ok, Containers} = erlazure_blob:parse_container_list(Body), + {reply, Containers, State}; + {error, _} = Error -> + {reply, Error, State} + end; % Create a container handle_call({create_container, Name, Options}, _From, State) -> @@ -805,7 +809,10 @@ execute_request(ServiceContext = #service_context{}, ReqContext = #req_context{} {Code, Body}; {ok, {{_, _, _}, _, Body}} -> - get_error_code(Body) + get_error_code(Body); + + {error, Error} -> + {error, Error} end. get_error_code(Body) -> diff --git a/test/erlazure_SUITE.erl b/test/erlazure_SUITE.erl index 829dc1c..5cb4b2c 100644 --- a/test/erlazure_SUITE.erl +++ b/test/erlazure_SUITE.erl @@ -149,3 +149,16 @@ t_append_blob_smoke_test(Config) -> %% Delete container ?assertMatch({ok, deleted}, erlazure:delete_container(Pid, Container)), ok. + +%% Test error handling when endpoint is unavailable +t_blob_failure_to_connect(_Config) -> + BadEndpoint = "http://127.0.0.2:65535/", + {ok, Pid} = erlazure:start(#{account => ?ACCOUNT, key => ?KEY, endpoint => BadEndpoint}), + ?assertMatch({error, {failed_connect, _}}, erlazure:list_containers(Pid)), + ?assertMatch({error, {failed_connect, _}}, erlazure:create_container(Pid, "c")), + ?assertMatch({error, {failed_connect, _}}, erlazure:delete_container(Pid, "c")), + ?assertMatch({error, {failed_connect, _}}, erlazure:put_append_blob(Pid, "c", "b1")), + ?assertMatch({error, {failed_connect, _}}, erlazure:put_block_blob(Pid, "c", "b1", <<"a">>)), + ?assertMatch({error, {failed_connect, _}}, erlazure:append_block(Pid, "c", "b1", <<"a">>)), + ?assertMatch({error, {failed_connect, _}}, erlazure:get_blob(Pid, "c", "b1")), + ok. From 4b072a0e69e3d70daccebcf26b72b192296fa430 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Fri, 17 May 2024 11:33:33 -0300 Subject: [PATCH 10/17] feat: allow specifying content type when creating append blob --- src/erlazure.erl | 6 +++++- test/erlazure_SUITE.erl | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/erlazure.erl b/src/erlazure.erl index 1ad6d47..fe33c62 100644 --- a/src/erlazure.erl +++ b/src/erlazure.erl @@ -592,7 +592,11 @@ handle_call({put_blob, Container, Name, Type = append_blob, Options}, _From, Sta ReqOptions = [{method, put}, {path, lists:concat([Container, "/", Name])}, {params, Params ++ Options}], - ReqContext = new_req_context(?blob_service, ReqOptions, State), + ReqContext1 = new_req_context(?blob_service, ReqOptions, State), + ReqContext = case proplists:get_value(content_type, Options) of + undefined -> ReqContext1#req_context{ content_type = "application/octet-stream" }; + ContentType -> ReqContext1#req_context{ content_type = ContentType } + end, {Code, Body} = execute_request(ServiceContext, ReqContext), return_response(Code, Body, State, ?http_created, created); diff --git a/test/erlazure_SUITE.erl b/test/erlazure_SUITE.erl index 5cb4b2c..4daba70 100644 --- a/test/erlazure_SUITE.erl +++ b/test/erlazure_SUITE.erl @@ -135,12 +135,16 @@ t_append_blob_smoke_test(Config) -> ?assertMatch({[], _}, erlazure:list_containers(Pid)), ?assertMatch({ok, created}, erlazure:create_container(Pid, Container)), %% Upload some blobs - ?assertMatch({ok, created}, erlazure:put_append_blob(Pid, Container, "blob1")), + Opts = [{content_type, "text/csv"}], + ?assertMatch({ok, created}, erlazure:put_append_blob(Pid, Container, "blob1", Opts)), ?assertMatch({ok, appended}, erlazure:append_block(Pid, Container, "blob1", <<"1">>)), ?assertMatch({ok, appended}, erlazure:append_block(Pid, Container, "blob1", <<"\n">>)), ?assertMatch({ok, appended}, erlazure:append_block(Pid, Container, "blob1", <<"2">>)), + ListedBlobs = erlazure:list_blobs(Pid, Container), ?assertMatch({[#cloud_blob{name = "blob1"}], _}, - erlazure:list_blobs(Pid, Container)), + ListedBlobs), + {[#cloud_blob{name = "blob1", properties = BlobProps}], _} = ListedBlobs, + ?assertMatch(#{content_type := "text/csv"}, maps:from_list(BlobProps)), %% Read back data ?assertMatch({ok, <<"1\n2">>}, erlazure:get_blob(Pid, Container, "blob1")), %% Delete blob From f74212a8b0d7153b61c2d5b97d73e6e3e4ec5715 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Mon, 20 May 2024 16:46:21 -0300 Subject: [PATCH 11/17] fix: correctly encode and sign block ids --- src/erlazure.erl | 7 ++++++- src/erlazure_blob.erl | 6 +++++- test/erlazure_SUITE.erl | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/erlazure.erl b/src/erlazure.erl index fe33c62..d1a6855 100644 --- a/src/erlazure.erl +++ b/src/erlazure.erl @@ -667,7 +667,7 @@ handle_call({delete_blob, Container, Blob, Options}, _From, State) -> handle_call({put_block, Container, Blob, BlockId, Content, Options}, _From, State) -> ServiceContext = new_service_context(?blob_service, State), Params = [{comp, block}, - {blob_block_id, base64:encode_to_string(BlockId)}], + {blob_block_id, uri_string:quote(base64:encode_to_string(BlockId))}], ReqOptions = [{method, put}, {path, lists:concat([Container, "/", Blob])}, {body, Content}, @@ -938,6 +938,11 @@ combine_canonical_param({Param, Value}, _PreviousParam, Acc, ParamList) -> [H | T] = ParamList, combine_canonical_param(H, Param, add_param_value(Param, Value, Acc), T). +add_param_value(Param = "blockid", Value, Acc) -> + %% special case: `blockid' must be URL-encoded when sending the request, but not + %% when signing it. At this point, we've already encoded it. + Acc ++ "\n" ++ string:to_lower(Param) ++ ":" ++ uri_string:unquote(Value); + add_param_value(Param, Value, Acc) -> Acc ++ "\n" ++ string:to_lower(Param) ++ ":" ++ Value. diff --git a/src/erlazure_blob.erl b/src/erlazure_blob.erl index 0dd5f8f..61008d7 100644 --- a/src/erlazure_blob.erl +++ b/src/erlazure_blob.erl @@ -188,7 +188,11 @@ get_request_body(BlockRefs) -> FoldFun = fun(BlockRef=#blob_block{}, Acc) -> [{block_type_to_node(BlockRef#blob_block.type), [], - [base64:encode_to_string(BlockRef#blob_block.id)]} | Acc] + [base64:encode_to_string(BlockRef#blob_block.id)]} | Acc]; + ({BlockId, BlockType}, Acc) -> + [{block_type_to_node(BlockType), + [], + [base64:encode_to_string(BlockId)]} | Acc] end, Data = {'BlockList', [], lists:reverse(lists:foldl(FoldFun, [], BlockRefs))}, lists:flatten(xmerl:export_simple([Data], xmerl_xml)). diff --git a/test/erlazure_SUITE.erl b/test/erlazure_SUITE.erl index 4daba70..9c4707d 100644 --- a/test/erlazure_SUITE.erl +++ b/test/erlazure_SUITE.erl @@ -166,3 +166,42 @@ t_blob_failure_to_connect(_Config) -> ?assertMatch({error, {failed_connect, _}}, erlazure:append_block(Pid, "c", "b1", <<"a">>)), ?assertMatch({error, {failed_connect, _}}, erlazure:get_blob(Pid, "c", "b1")), ok. + +%% Basic smoke test for block blob storage operations. +t_put_block(Config) -> + Endpoint = ?config(endpoint, Config), + {ok, Pid} = erlazure:start(#{account => ?ACCOUNT, key => ?KEY, endpoint => Endpoint}), + %% Create a container + Container = container_name(?FUNCTION_NAME), + ?assertMatch({[], _}, erlazure:list_containers(Pid)), + ?assertMatch({ok, created}, erlazure:create_container(Pid, Container)), + %% Upload some blocks + Opts = [{content_type, "text/csv"}], + BlobName = "blob1", + ?assertMatch({ok, created}, erlazure:put_block_blob(Pid, Container, BlobName, <<>>, Opts)), + %% Note: this short name is important for this test. It'll produce a base64 string + %% that's padded. That padding must be URL-encoded when sending the request, but not + %% when generating the string to sign. + BlockId1 = <<"blo1">>, + ?assertMatch({ok, created}, erlazure:put_block(Pid, Container, BlobName, BlockId1, <<"a">>)), + %% Testing iolists + BlockId2 = <<"blo2">>, + ?assertMatch({ok, created}, erlazure:put_block(Pid, Container, BlobName, BlockId2, [<<"\n">>, ["b", [$\n]]])), + %% Not yet committed. + ?assertMatch({ok, <<"">>}, erlazure:get_blob(Pid, Container, BlobName)), + %% Committing + BlockList1 = [{BlockId1, latest}], + ?assertMatch({ok, created}, erlazure:put_block_list(Pid, Container, BlobName, BlockList1)), + %% Committed only first block. + ?assertMatch({ok, <<"a">>}, erlazure:get_blob(Pid, Container, BlobName)), + %% Block 2 was dropped after committing. + ?assertMatch({[#blob_block{id = "blo1"}], _}, erlazure:get_block_list(Pid, Container, BlobName)), + BlockId3 = <<"blo3">>, + ?assertMatch({ok, created}, erlazure:put_block(Pid, Container, BlobName, BlockId3, [<<"\n">>, ["b", [$\n]]])), + %% Commit both blocks + BlockList2 = [{BlockId1, committed}, {BlockId3, uncommitted}], + ?assertMatch({ok, created}, erlazure:put_block_list(Pid, Container, BlobName, BlockList2)), + ?assertMatch({ok, <<"a\nb\n">>}, erlazure:get_blob(Pid, Container, BlobName)), + %% Delete container + ?assertMatch({ok, deleted}, erlazure:delete_container(Pid, Container)), + ok. From 438edf9c48b5491c89044be2b480c2ca703818ab Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Tue, 21 May 2024 11:25:08 -0300 Subject: [PATCH 12/17] fix: allow specifying content type in `put_block_list` --- src/erlazure.erl | 13 +++++++++++-- test/erlazure_SUITE.erl | 22 +++++++++++++++------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/erlazure.erl b/src/erlazure.erl index d1a6855..55857f8 100644 --- a/src/erlazure.erl +++ b/src/erlazure.erl @@ -678,12 +678,13 @@ handle_call({put_block, Container, Blob, BlockId, Content, Options}, _From, Stat return_response(Code, Body, State, ?http_created, created); % Put block list -handle_call({put_block_list, Container, Blob, BlockRefs, Options}, _From, State) -> +handle_call({put_block_list, Container, Blob, BlockRefs, Options0}, _From, State) -> ServiceContext = new_service_context(?blob_service, State), + {ExtraReqOpts, Options} = proplist_take(req_opts, Options0, []), ReqOptions = [{method, put}, {path, lists:concat([Container, "/", Blob])}, {body, erlazure_blob:get_request_body(BlockRefs)}, - {params, [{comp, "blocklist"}] ++ Options}], + {params, [{comp, "blocklist"}] ++ Options} | ExtraReqOpts], ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), @@ -1085,6 +1086,14 @@ ensure_wrapped_key(#{key := Key} = InitOpts) -> InitOpts#{key := wrap(Key)} end. +proplist_take(Key, Proplist, Default) -> + case lists:keytake(Key, 1, Proplist) of + false -> + {Default, Proplist}; + {value, {Key, Value}, NewProplist} -> + {Value, NewProplist} + end. + %%==================================================================== %% Tests %%==================================================================== diff --git a/test/erlazure_SUITE.erl b/test/erlazure_SUITE.erl index 9c4707d..604fba3 100644 --- a/test/erlazure_SUITE.erl +++ b/test/erlazure_SUITE.erl @@ -175,10 +175,10 @@ t_put_block(Config) -> Container = container_name(?FUNCTION_NAME), ?assertMatch({[], _}, erlazure:list_containers(Pid)), ?assertMatch({ok, created}, erlazure:create_container(Pid, Container)), - %% Upload some blocks - Opts = [{content_type, "text/csv"}], + %% Upload some blocks. Note: this content-type will be overwritten later by `put_block_list'. + Opts1 = [{content_type, "application/json"}], BlobName = "blob1", - ?assertMatch({ok, created}, erlazure:put_block_blob(Pid, Container, BlobName, <<>>, Opts)), + ?assertMatch({ok, created}, erlazure:put_block_blob(Pid, Container, BlobName, <<"0">>, Opts1)), %% Note: this short name is important for this test. It'll produce a base64 string %% that's padded. That padding must be URL-encoded when sending the request, but not %% when generating the string to sign. @@ -187,21 +187,29 @@ t_put_block(Config) -> %% Testing iolists BlockId2 = <<"blo2">>, ?assertMatch({ok, created}, erlazure:put_block(Pid, Container, BlobName, BlockId2, [<<"\n">>, ["b", [$\n]]])), - %% Not yet committed. - ?assertMatch({ok, <<"">>}, erlazure:get_blob(Pid, Container, BlobName)), + %% Not yet committed. Contains only the data from the blob creation. + ?assertMatch({ok, <<"0">>}, erlazure:get_blob(Pid, Container, BlobName)), %% Committing BlockList1 = [{BlockId1, latest}], ?assertMatch({ok, created}, erlazure:put_block_list(Pid, Container, BlobName, BlockList1)), - %% Committed only first block. + %% Committed only first block. Initial data was lost, as it was not in the block list. ?assertMatch({ok, <<"a">>}, erlazure:get_blob(Pid, Container, BlobName)), %% Block 2 was dropped after committing. ?assertMatch({[#blob_block{id = "blo1"}], _}, erlazure:get_block_list(Pid, Container, BlobName)), BlockId3 = <<"blo3">>, ?assertMatch({ok, created}, erlazure:put_block(Pid, Container, BlobName, BlockId3, [<<"\n">>, ["b", [$\n]]])), %% Commit both blocks + Opts2 = [{req_opts, [{headers, [{"x-ms-blob-content-type", "text/csv"}]}]}], BlockList2 = [{BlockId1, committed}, {BlockId3, uncommitted}], - ?assertMatch({ok, created}, erlazure:put_block_list(Pid, Container, BlobName, BlockList2)), + ?assertMatch({ok, created}, erlazure:put_block_list(Pid, Container, BlobName, BlockList2, Opts2)), ?assertMatch({ok, <<"a\nb\n">>}, erlazure:get_blob(Pid, Container, BlobName)), + %% Check content type. + ListedBlobs = erlazure:list_blobs(Pid, Container), + ?assertMatch({[#cloud_blob{name = "blob1"}], _}, + ListedBlobs), + {[#cloud_blob{name = "blob1", properties = Props}], _} = ListedBlobs, + %% Content-type from `put_block_list' prevails. + ?assertMatch(#{content_type := "text/csv"}, maps:from_list(Props)), %% Delete container ?assertMatch({ok, deleted}, erlazure:delete_container(Pid, Container)), ok. From 0c27179478066b509efac099b3fd385cb0087b9d Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Thu, 6 Jun 2024 16:03:30 -0300 Subject: [PATCH 13/17] feat: remove `gen_server` usage Fixes https://github.com/dkataskin/erlazure/issues/40 --- README.md | 18 +- src/erlazure.erl | 684 +++++++++++----------------- test/erlazure_SUITE.erl | 108 ++--- test/erlazure_queue_cloud_tests.erl | 102 ++--- 4 files changed, 380 insertions(+), 532 deletions(-) diff --git a/README.md b/README.md index b77ac74..4c28ff9 100644 --- a/README.md +++ b/README.md @@ -47,9 +47,9 @@ Erlazure requires OTP version R16+. ## Starting an instance of erlazure -Start an instance of erlazure by calling ```erlazure:start/2``` where **Account** is Storage account name and **Key** is Storage account key. +Start an instance of erlazure by calling `erlazure:new/2` where **Account** is Storage account name and **Key** is Storage account key. ```erlang -{ok, Pid} = erlazure:start(Account, Key) +{ok, State} = erlazure:new(Account, Key) ``` Account and Key are strings. @@ -60,7 +60,7 @@ Account and Key are strings. Account = "devstoreaccount1". Key = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==". %% Mind the trailing slash at the end of the endpoint. -{ok, Pid} = erlazure:start(#{account => Account, key => Key, endpoint => "http://127.0.0.1:10000/"}) +{ok, State} = erlazure:new(#{account => Account, key => Key, endpoint => "http://127.0.0.1:10000/"}) ``` ## Calling Azure services @@ -76,23 +76,23 @@ For a list of supported options for each azure service request please consult ms ### Upload block blob ``` -{ok, Pid} = erlazure:start("storage", "2o4b4tHpoWifLU+BlyzsIG1VtlO9LgBRFyl1qLw/+w9/ZszSxKGIK8JYac/UEJp5r8HKgiOiG8YTqGS9otAYWA=="), +{ok, State} = erlazure:new("storage", "2o4b4tHpoWifLU+BlyzsIG1VtlO9LgBRFyl1qLw/+w9/ZszSxKGIK8JYac/UEJp5r8HKgiOiG8YTqGS9otAYWA=="), {ok, Binary} = file:read("/path/to/some/small/file"), -{ok, created} = erlazure:put_block_blob(Pid, "uploads", "test_upload.file", Binary). +{ok, created} = erlazure:put_block_blob(State, "uploads", "test_upload.file", Binary). ``` ### Upload block blob with timeout set Uploads block blob and waits no longer than 15 seconds for erlazure to finish the upload ``` -{ok, Pid} = erlazure:start("storage", "2o4b4tHpoWifLU+BlyzsIG1VtlO9LgBRFyl1qLw/+w9/ZszSxKGIK8JYac/UEJp5r8HKgiOiG8YTqGS9otAYWA=="), +{ok, State} = erlazure:new("storage", "2o4b4tHpoWifLU+BlyzsIG1VtlO9LgBRFyl1qLw/+w9/ZszSxKGIK8JYac/UEJp5r8HKgiOiG8YTqGS9otAYWA=="), {ok, Binary} = file:read("/path/to/some/other/file"), -{ok, created} = erlazure:put_block_blob(Pid, "uploads", "test_upload2.file", Binary, [], 15000). +{ok, created} = erlazure:put_block_blob(State, "uploads", "test_upload2.file", Binary, [], 15000). ``` ### Get 20 messages from a queue Retrieves max 20 messages from a queue ``` -{ok, Pid} = erlazure:start("storage", "2o4b4tHpoWifLU+BlyzsIG1VtlO9LgBRFyl1qLw/+w9/ZszSxKGIK8JYac/UEJp5r8HKgiOiG8YTqGS9otAYWA=="), -{ok, Messages} = erlazure:get_messages(Pid, "test_queue", [{num_of_messages, 20}]). +{ok, State} = erlazure:new("storage", "2o4b4tHpoWifLU+BlyzsIG1VtlO9LgBRFyl1qLw/+w9/ZszSxKGIK8JYac/UEJp5r8HKgiOiG8YTqGS9otAYWA=="), +{ok, Messages} = erlazure:get_messages(State, "test_queue", [{num_of_messages, 20}]). ``` ## License diff --git a/src/erlazure.erl b/src/erlazure.erl index 55857f8..c8445c8 100644 --- a/src/erlazure.erl +++ b/src/erlazure.erl @@ -37,10 +37,8 @@ -define(json_content_type, "application/json"). -define(gen_server_call_default_timeout, 30000). --behaviour(gen_server). - %% API --export([start/1, start/2]). +-export([new/1, new/2]). %% Queue API -export([list_queues/1, list_queues/2, list_queues/3]). @@ -80,9 +78,6 @@ %% Host API -export([get_host/3, get_host/4]). -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). - -type init_opts() :: #{ account := string(), key := string() | function(), @@ -93,267 +88,17 @@ }. -record(state, { account = "", key = "", options = #{}, param_specs = [] }). +-type state() :: #state{}. + +-export_types([state/0]). %%==================================================================== %% API %%==================================================================== --spec start(init_opts()) -> gen_server:start_ret(). -start(InitOpts0) -> +-spec new(init_opts()) -> {ok, state()}. +new(InitOpts0) -> InitOpts = ensure_wrapped_key(InitOpts0), - gen_server:start_link(?MODULE, InitOpts, []). - --spec start(string(), string()) -> {ok, pid()}. -start(Account, Key) -> - start(#{account => Account, key => Key}). - -%%==================================================================== -%% Queue -%%==================================================================== - --spec list_queues(pid()) -> enum_parse_result(queue()). -list_queues(Pid) -> - list_queues(Pid, []). - --spec list_queues(pid(), common_opts()) -> enum_parse_result(queue()). -list_queues(Pid, Options) -> - list_queues(Pid, Options, ?gen_server_call_default_timeout). - --spec list_queues(pid(), common_opts(), pos_integer()) -> enum_parse_result(queue()). -list_queues(Pid, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {list_queues, Options}, Timeout). - --type queue_acl_opts() :: req_param_timeout() | req_param_clientrequestid(). --spec set_queue_acl(pid(), string(), signed_id()) -> {ok, created}. -set_queue_acl(Pid, Queue, SignedId=#signed_id{}) -> - set_queue_acl(Pid, Queue, SignedId, []). - --spec set_queue_acl(pid(), string(), signed_id(), list(queue_acl_opts())) -> {ok, created}. -set_queue_acl(Pid, Queue, SignedId=#signed_id{}, Options) -> - set_queue_acl(Pid, Queue, SignedId, Options, ?gen_server_call_default_timeout). - --spec set_queue_acl(pid(), string(), signed_id(), list(queue_acl_opts()), pos_integer()) -> {ok, created}. -set_queue_acl(Pid, Queue, SignedId=#signed_id{}, Options, Timeout) when is_list(Options); is_integer(Timeout)-> - gen_server:call(Pid, {set_queue_acl, Queue, SignedId, Options}, Timeout). - --spec get_queue_acl(pid(), string()) -> {ok, no_acl} | {ok, signed_id()}. -get_queue_acl(Pid, Queue) -> - get_queue_acl(Pid, Queue, []). - --spec get_queue_acl(pid(), string(), list(queue_acl_opts())) -> {ok, no_acl} | {ok, signed_id()}. -get_queue_acl(Pid, Queue, Options) -> - get_queue_acl(Pid, Queue, Options, ?gen_server_call_default_timeout). - --spec get_queue_acl(pid(), string(), list(queue_acl_opts()), pos_integer()) -> {ok, no_acl} | {ok, signed_id()}. -get_queue_acl(Pid, Queue, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {get_queue_acl, Queue, Options}, Timeout). - --spec create_queue(pid(), string()) -> created_response() | already_created_response(). -create_queue(Pid, Queue) -> - create_queue(Pid, Queue, []). -create_queue(Pid, Queue, Options) -> - create_queue(Pid, Queue, Options, ?gen_server_call_default_timeout). -create_queue(Pid, Queue, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {create_queue, Queue, Options}, Timeout). - -delete_queue(Pid, Queue) -> - delete_queue(Pid, Queue, []). -delete_queue(Pid, Queue, Options) -> - delete_queue(Pid, Queue, Options, ?gen_server_call_default_timeout). -delete_queue(Pid, Queue, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {delete_queue, Queue, Options}, Timeout). - -put_message(Pid, Queue, Message) -> - put_message(Pid, Queue, Message, []). -put_message(Pid, Queue, Message, Options) -> - put_message(Pid, Queue, Message, Options, ?gen_server_call_default_timeout). -put_message(Pid, Queue, Message, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {put_message, Queue, Message, Options}, Timeout). - -get_messages(Pid, Queue) -> - get_messages(Pid, Queue, []). -get_messages(Pid, Queue, Options) -> - get_messages(Pid, Queue, Options, ?gen_server_call_default_timeout). -get_messages(Pid, Queue, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {get_messages, Queue, Options}, Timeout). - -peek_messages(Pid, Queue) -> - peek_messages(Pid, Queue, []). -peek_messages(Pid, Queue, Options) -> - peek_messages(Pid, Queue, Options, ?gen_server_call_default_timeout). -peek_messages(Pid, Queue, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {peek_messages, Queue, Options}, Timeout). - -delete_message(Pid, Queue, MessageId, PopReceipt) -> - delete_message(Pid, Queue, MessageId, PopReceipt, []). -delete_message(Pid, Queue, MessageId, PopReceipt, Options) -> - delete_message(Pid, Queue, MessageId, PopReceipt, Options, ?gen_server_call_default_timeout). -delete_message(Pid, Queue, MessageId, PopReceipt, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {delete_message, Queue, MessageId, PopReceipt, Options}, Timeout). - -clear_messages(Pid, Queue) -> - clear_messages(Pid, Queue, []). -clear_messages(Pid, Queue, Options) -> - clear_messages(Pid, Queue, Options, ?gen_server_call_default_timeout). -clear_messages(Pid, Queue, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {clear_messages, Queue, Options}, Timeout). - -update_message(Pid, Queue, UpdatedMessage=#queue_message{}, VisibilityTimeout) -> - update_message(Pid, Queue, UpdatedMessage, VisibilityTimeout, []). -update_message(Pid, Queue, UpdatedMessage=#queue_message{}, VisibilityTimeout, Options) -> - update_message(Pid, Queue, UpdatedMessage, VisibilityTimeout, Options, ?gen_server_call_default_timeout). -update_message(Pid, Queue, UpdatedMessage=#queue_message{}, VisibilityTimeout, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {update_message, Queue, UpdatedMessage, VisibilityTimeout, Options}, Timeout). - -%%==================================================================== -%% Blob -%%==================================================================== - -list_containers(Pid) -> - list_containers(Pid, []). -list_containers(Pid, Options) -> - list_containers(Pid, Options, ?gen_server_call_default_timeout). -list_containers(Pid, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {list_containers, Options}, Timeout). - -create_container(Pid, Name) -> - create_container(Pid, Name, []). -create_container(Pid, Name, Options) -> - create_container(Pid, Name, Options, ?gen_server_call_default_timeout). -create_container(Pid, Name, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {create_container, Name, Options}, Timeout). - -delete_container(Pid, Name) -> - delete_container(Pid, Name, []). -delete_container(Pid, Name, Options) -> - delete_container(Pid, Name, Options, ?gen_server_call_default_timeout). -delete_container(Pid, Name, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {delete_container, Name, Options}, Timeout). - -put_block_blob(Pid, Container, Name, Data) -> - put_block_blob(Pid, Container, Name, Data, []). -put_block_blob(Pid, Container, Name, Data, Options) -> - put_block_blob(Pid, Container, Name, Data, Options, ?gen_server_call_default_timeout). -put_block_blob(Pid, Container, Name, Data, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {put_blob, Container, Name, block_blob, Data, Options}, Timeout). - -put_page_blob(Pid, Container, Name, ContentLength) -> - put_page_blob(Pid, Container, Name, ContentLength, []). -put_page_blob(Pid, Container, Name, ContentLength, Options) -> - put_page_blob(Pid, Container, Name, ContentLength, Options, ?gen_server_call_default_timeout). -put_page_blob(Pid, Container, Name, ContentLength, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {put_blob, Container, Name, page_blob, ContentLength, Options}, Timeout). - -put_append_blob(Pid, Container, Name) -> - put_append_blob(Pid, Container, Name, []). -put_append_blob(Pid, Container, Name, Options) -> - put_append_blob(Pid, Container, Name, Options, ?gen_server_call_default_timeout). -put_append_blob(Pid, Container, Name, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {put_blob, Container, Name, append_blob, Options}, Timeout). - -append_block(Pid, Container, Name, Data) -> - append_block(Pid, Container, Name, Data, []). -append_block(Pid, Container, Name, Data, Options) -> - append_block(Pid, Container, Name, Data, Options, ?gen_server_call_default_timeout). -append_block(Pid, Container, Name, Data, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {append_block, Container, Name, Data, Options}, Timeout). - -list_blobs(Pid, Container) -> - list_blobs(Pid, Container, []). -list_blobs(Pid, Container, Options) -> - list_blobs(Pid, Container, Options, ?gen_server_call_default_timeout). -list_blobs(Pid, Container, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {list_blobs, Container, Options}, Timeout). - -get_blob(Pid, Container, Blob) -> - get_blob(Pid, Container, Blob, []). -get_blob(Pid, Container, Blob, Options) -> - get_blob(Pid, Container, Blob, Options, ?gen_server_call_default_timeout). -get_blob(Pid, Container, Blob, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {get_blob, Container, Blob, Options}, Timeout). - -snapshot_blob(Pid, Container, Blob) -> - snapshot_blob(Pid, Container, Blob, []). -snapshot_blob(Pid, Container, Blob, Options) -> - snapshot_blob(Pid, Container, Blob, Options, ?gen_server_call_default_timeout). -snapshot_blob(Pid, Container, Blob, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {snapshot_blob, Container, Blob, Options}, Timeout). - -copy_blob(Pid, Container, Blob, Source) -> - copy_blob(Pid, Container, Blob, Source, []). -copy_blob(Pid, Container, Blob, Source, Options) -> - copy_blob(Pid, Container, Blob, Source, Options, ?gen_server_call_default_timeout). -copy_blob(Pid, Container, Blob, Source, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {copy_blob, Container, Blob, Source, Options}, Timeout). - -delete_blob(Pid, Container, Blob) -> - delete_blob(Pid, Container, Blob, []). -delete_blob(Pid, Container, Blob, Options) -> - delete_blob(Pid, Container, Blob, Options, ?gen_server_call_default_timeout). -delete_blob(Pid, Container, Blob, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {delete_blob, Container, Blob, Options}, Timeout). - -put_block(Pid, Container, Blob, BlockId, BlockContent) -> - put_block(Pid, Container, Blob, BlockId, BlockContent, []). -put_block(Pid, Container, Blob, BlockId, BlockContent, Options) -> - put_block(Pid, Container, Blob, BlockId, BlockContent, Options, ?gen_server_call_default_timeout). -put_block(Pid, Container, Blob, BlockId, BlockContent, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {put_block, Container, Blob, BlockId, BlockContent, Options}, Timeout). - -put_block_list(Pid, Container, Blob, BlockRefs) -> - put_block_list(Pid, Container, Blob, BlockRefs, []). -put_block_list(Pid, Container, Blob, BlockRefs, Options) -> - put_block_list(Pid, Container, Blob, BlockRefs, Options, ?gen_server_call_default_timeout). -put_block_list(Pid, Container, Blob, BlockRefs, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {put_block_list, Container, Blob, BlockRefs, Options}, Timeout). - -get_block_list(Pid, Container, Blob) -> - get_block_list(Pid, Container, Blob, []). -get_block_list(Pid, Container, Blob, Options) -> - get_block_list(Pid, Container, Blob, Options, ?gen_server_call_default_timeout). -get_block_list(Pid, Container, Blob, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {get_block_list, Container, Blob, Options}, Timeout). - -acquire_blob_lease(Pid, Container, Blob, Duration) -> - acquire_blob_lease(Pid, Container, Blob, Duration, []). -acquire_blob_lease(Pid, Container, Blob, Duration, Options) -> - acquire_blob_lease(Pid, Container, Blob, "", Duration, Options, ?gen_server_call_default_timeout). -acquire_blob_lease(Pid, Container, Blob, ProposedId, Duration, Options, Timeout) when is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {acquire_blob_lease, Container, Blob, ProposedId, Duration, Options}, Timeout). - -lease_container(Pid, Name, Mode) -> - lease_container(Pid, Name, Mode, []). -lease_container(Pid, Name, Mode, Options) -> - lease_container(Pid, Name, Mode, Options, ?gen_server_call_default_timeout). -lease_container(Pid, Name, Mode, Options, Timeout) when is_atom(Mode); is_list(Options); is_integer(Timeout) -> - gen_server:call(Pid, {lease_container, Name, Mode, Options}, Timeout). - -%%==================================================================== -%% Table -%%==================================================================== - -list_tables(Pid) -> - list_tables(Pid, [], ?gen_server_call_default_timeout). -list_tables(Pid, Options, Timeout) when is_list(Options); is_integer(Timeout)-> - gen_server:call(Pid, {list_tables, Options}, Timeout). - -new_table(Pid, TableName) when is_list(TableName) -> - new_table(Pid, list_to_binary(TableName)); - -new_table(Pid, TableName) when is_binary(TableName) -> - gen_server:call(Pid, {new_table, TableName}). - -delete_table(Pid, TableName) when is_binary(TableName) -> - delete_table(Pid, binary_to_list(TableName)); - -delete_table(Pid, TableName) when is_list(TableName) -> - gen_server:call(Pid, {delete_table, TableName}). - -%%==================================================================== -%% gen_server callbacks -%%==================================================================== - -init(InitOpts) -> #{ account := Account , key := Key } = InitOpts, @@ -363,18 +108,42 @@ init(InitOpts) -> options = StateOpts, param_specs = get_req_param_specs() }}. -% List queues -handle_call({list_queues, Options}, _From, State) -> +-spec new(string(), string()) -> {ok, state()}. +new(Account, Key) -> + new(#{account => Account, key => Key}). + +%%==================================================================== +%% Queue +%%==================================================================== + +-spec list_queues(state()) -> enum_parse_result(queue()). +list_queues(State) -> + list_queues(State, []). + +-spec list_queues(state(), common_opts()) -> enum_parse_result(queue()). +list_queues(State, Options) -> + list_queues(State, Options, ?gen_server_call_default_timeout). + +-spec list_queues(state(), common_opts(), pos_integer()) -> enum_parse_result(queue()). +list_queues(State, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{params, [{comp, list}] ++ Options}], ReqContext = new_req_context(?queue_service, ReqOptions, State), {?http_ok, Body} = execute_request(ServiceContext, ReqContext), - ParseResult = erlazure_queue:parse_queue_list(Body), - {reply, ParseResult, State}; + erlazure_queue:parse_queue_list(Body). + +-type queue_acl_opts() :: req_param_timeout() | req_param_clientrequestid(). +-spec set_queue_acl(state(), string(), signed_id()) -> {ok, created}. +set_queue_acl(State, Queue, SignedId=#signed_id{}) -> + set_queue_acl(State, Queue, SignedId, []). + +-spec set_queue_acl(state(), string(), signed_id(), list(queue_acl_opts())) -> {ok, created}. +set_queue_acl(State, Queue, SignedId=#signed_id{}, Options) -> + set_queue_acl(State, Queue, SignedId, Options, ?gen_server_call_default_timeout). -% Set queue acl -handle_call({set_queue_acl, Queue, SignedId=#signed_id{}, Options}, _From, State) -> +-spec set_queue_acl(state(), string(), signed_id(), list(queue_acl_opts()), pos_integer()) -> {ok, created}. +set_queue_acl(State, Queue, SignedId=#signed_id{}, Options, Timeout) when is_list(Options); is_integer(Timeout)-> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{method, put}, {path, string:to_lower(Queue)}, @@ -383,21 +152,32 @@ handle_call({set_queue_acl, Queue, SignedId=#signed_id{}, Options}, _From, State ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_no_content, created); + return_response(Code, Body, State, ?http_no_content, created). + +-spec get_queue_acl(state(), string()) -> {ok, no_acl} | {ok, signed_id()}. +get_queue_acl(State, Queue) -> + get_queue_acl(State, Queue, []). + +-spec get_queue_acl(state(), string(), list(queue_acl_opts())) -> {ok, no_acl} | {ok, signed_id()}. +get_queue_acl(State, Queue, Options) -> + get_queue_acl(State, Queue, Options, ?gen_server_call_default_timeout). -% Get queue acl -handle_call({get_queue_acl, Queue, Options}, _From, State) -> +-spec get_queue_acl(state(), string(), list(queue_acl_opts()), pos_integer()) -> {ok, no_acl} | {ok, signed_id()}. +get_queue_acl(State, Queue, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{path, string:to_lower(Queue)}, {params, [{comp, acl}] ++ Options}], ReqContext = new_req_context(?queue_service, ReqOptions, State), {?http_ok, Body} = execute_request(ServiceContext, ReqContext), - ParseResult = erlazure_queue:parse_queue_acl_response(Body), - {reply, ParseResult, State}; - -% Create queue -handle_call({create_queue, Queue, Options}, _From, State) -> + erlazure_queue:parse_queue_acl_response(Body). + +-spec create_queue(state(), string()) -> created_response() | already_created_response(). +create_queue(State, Queue) -> + create_queue(State, Queue, []). +create_queue(State, Queue, Options) -> + create_queue(State, Queue, Options, ?gen_server_call_default_timeout). +create_queue(State, Queue, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{method, put}, {path, string:to_lower(Queue)}, @@ -407,13 +187,16 @@ handle_call({create_queue, Queue, Options}, _From, State) -> {Code, _Body} = execute_request(ServiceContext, ReqContext), case Code of ?http_created -> - {reply, {ok, created}, State}; + {ok, created}; ?http_no_content -> - {reply, {error, already_created}, State} - end; + {error, already_created} + end. -% Delete queue -handle_call({delete_queue, Queue, Options}, _From, State) -> +delete_queue(State, Queue) -> + delete_queue(State, Queue, []). +delete_queue(State, Queue, Options) -> + delete_queue(State, Queue, Options, ?gen_server_call_default_timeout). +delete_queue(State, Queue, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{method, delete}, {path, string:to_lower(Queue)}, @@ -421,10 +204,13 @@ handle_call({delete_queue, Queue, Options}, _From, State) -> ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_no_content, deleted); + return_response(Code, Body, State, ?http_no_content, deleted). -% Add message to a queue -handle_call({put_message, Queue, Message, Options}, _From, State) -> +put_message(State, Queue, Message) -> + put_message(State, Queue, Message, []). +put_message(State, Queue, Message, Options) -> + put_message(State, Queue, Message, Options, ?gen_server_call_default_timeout). +put_message(State, Queue, Message, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{method, post}, {path, lists:concat([string:to_lower(Queue), "/messages"])}, @@ -433,30 +219,39 @@ handle_call({put_message, Queue, Message, Options}, _From, State) -> ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_created, created); + return_response(Code, Body, State, ?http_created, created). -% Get messages from the queue -handle_call({get_messages, Queue, Options}, _From, State) -> +get_messages(State, Queue) -> + get_messages(State, Queue, []). +get_messages(State, Queue, Options) -> + get_messages(State, Queue, Options, ?gen_server_call_default_timeout). +get_messages(State, Queue, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{path, string:to_lower(Queue) ++ "/messages"}, {params, Options}], ReqContext = new_req_context(?queue_service, ReqOptions, State), {?http_ok, Body} = execute_request(ServiceContext, ReqContext), - {reply, erlazure_queue:parse_queue_messages_list(Body), State}; + erlazure_queue:parse_queue_messages_list(Body). -% Peek messages from the queue -handle_call({peek_messages, Queue, Options}, _From, State) -> +peek_messages(State, Queue) -> + peek_messages(State, Queue, []). +peek_messages(State, Queue, Options) -> + peek_messages(State, Queue, Options, ?gen_server_call_default_timeout). +peek_messages(State, Queue, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{path, string:to_lower(Queue) ++ "/messages"}, {params, [{peek_only, true}] ++ Options}], ReqContext = new_req_context(?queue_service, ReqOptions, State), {?http_ok, Body} = execute_request(ServiceContext, ReqContext), - {reply, erlazure_queue:parse_queue_messages_list(Body), State}; + erlazure_queue:parse_queue_messages_list(Body). -% Delete message from the queue -handle_call({delete_message, Queue, MessageId, PopReceipt, Options}, _From, State) -> +delete_message(State, Queue, MessageId, PopReceipt) -> + delete_message(State, Queue, MessageId, PopReceipt, []). +delete_message(State, Queue, MessageId, PopReceipt, Options) -> + delete_message(State, Queue, MessageId, PopReceipt, Options, ?gen_server_call_default_timeout). +delete_message(State, Queue, MessageId, PopReceipt, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{method, delete}, {path, lists:concat([string:to_lower(Queue), "/messages/", MessageId])}, @@ -464,10 +259,13 @@ handle_call({delete_message, Queue, MessageId, PopReceipt, Options}, _From, Stat ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_no_content, deleted); + return_response(Code, Body, State, ?http_no_content, deleted). -% Delete all messages from the queue -handle_call({clear_messages, Queue, Options}, _From, State) -> +clear_messages(State, Queue) -> + clear_messages(State, Queue, []). +clear_messages(State, Queue, Options) -> + clear_messages(State, Queue, Options, ?gen_server_call_default_timeout). +clear_messages(State, Queue, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{method, delete}, {path, string:to_lower(Queue) ++ "/messages"}, @@ -475,10 +273,13 @@ handle_call({clear_messages, Queue, Options}, _From, State) -> ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_no_content, deleted); + return_response(Code, Body, State, ?http_no_content, deleted). -% Update a message in the queue -handle_call({update_message, Queue, UpdatedMessage=#queue_message{}, VisibilityTimeout, Options}, _From, State) -> +update_message(State, Queue, UpdatedMessage=#queue_message{}, VisibilityTimeout) -> + update_message(State, Queue, UpdatedMessage, VisibilityTimeout, []). +update_message(State, Queue, UpdatedMessage=#queue_message{}, VisibilityTimeout, Options) -> + update_message(State, Queue, UpdatedMessage, VisibilityTimeout, Options, ?gen_server_call_default_timeout). +update_message(State, Queue, UpdatedMessage=#queue_message{}, VisibilityTimeout, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?queue_service, State), Params = [{pop_receipt, UpdatedMessage#queue_message.pop_receipt}, {message_visibility_timeout, integer_to_list(VisibilityTimeout)}], @@ -489,10 +290,17 @@ handle_call({update_message, Queue, UpdatedMessage=#queue_message{}, VisibilityT ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_no_content, updated); + return_response(Code, Body, State, ?http_no_content, updated). -% List containers -handle_call({list_containers, Options}, _From, State) -> +%%==================================================================== +%% Blob +%%==================================================================== + +list_containers(State) -> + list_containers(State, []). +list_containers(State, Options) -> + list_containers(State, Options, ?gen_server_call_default_timeout). +list_containers(State, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{params, [{comp, list}] ++ Options}], ReqContext = new_req_context(?blob_service, ReqOptions, State), @@ -500,13 +308,16 @@ handle_call({list_containers, Options}, _From, State) -> case execute_request(ServiceContext, ReqContext) of {?http_ok, Body} -> {ok, Containers} = erlazure_blob:parse_container_list(Body), - {reply, Containers, State}; + Containers; {error, _} = Error -> - {reply, Error, State} - end; + Error + end. -% Create a container -handle_call({create_container, Name, Options}, _From, State) -> +create_container(State, Name) -> + create_container(State, Name, []). +create_container(State, Name, Options) -> + create_container(State, Name, Options, ?gen_server_call_default_timeout). +create_container(State, Name, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{method, put}, {path, Name}, @@ -514,12 +325,15 @@ handle_call({create_container, Name, Options}, _From, State) -> ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), case Code of - ?http_created -> {reply, {ok, created}, State}; - _ -> {reply, {error, Body}, State} - end; + ?http_created -> {ok, created}; + _ -> {error, Body} + end. -% Delete container -handle_call({delete_container, Name, Options}, _From, State) -> +delete_container(State, Name) -> + delete_container(State, Name, []). +delete_container(State, Name, Options) -> + delete_container(State, Name, Options, ?gen_server_call_default_timeout). +delete_container(State, Name, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{method, delete}, {path, Name}, @@ -527,42 +341,18 @@ handle_call({delete_container, Name, Options}, _From, State) -> RequestContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, RequestContext), - return_response(Code, Body, State, ?http_accepted, deleted); + return_response(Code, Body, State, ?http_accepted, deleted). -% Lease a container -handle_call({lease_container, Name, Mode, Options}, _From, State) -> - ServiceContext = new_service_context(?blob_service, State), - Params = [{comp, lease}, - {res_type, container}, - {lease_action, Mode}], - ReqOptions = [{method, put}, - {path, Name}, - {params, Params ++ Options}], - ReqContext = new_req_context(?blob_service, ReqOptions, State), - - {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_accepted, deleted); - -% List blobs -handle_call({list_blobs, Name, Options}, _From, State) -> - ServiceContext = new_service_context(?blob_service, State), - Params = [{comp, list}, - {res_type, container}], - ReqOptions = [{path, Name}, - {params, Params ++ Options}], - ReqContext = new_req_context(?blob_service, ReqOptions, State), - - {?http_ok, Body} = execute_request(ServiceContext, ReqContext), - {ok, Blobs} = erlazure_blob:parse_blob_list(Body), - {reply, Blobs, State}; - -% Put block blob -handle_call({put_blob, Container, Name, Type = block_blob, Data, Options}, _From, State) -> +put_block_blob(State, Container, Name, Data) -> + put_block_blob(State, Container, Name, Data, []). +put_block_blob(State, Container, Name, Data, Options) -> + put_block_blob(State, Container, Name, Data, Options, ?gen_server_call_default_timeout). +put_block_blob(State, Container, Name, Data, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{method, put}, {path, lists:concat([Container, "/", Name])}, {body, Data}, - {params, [{blob_type, Type}] ++ Options}], + {params, [{blob_type, block_blob}] ++ Options}], ReqContext = new_req_context(?blob_service, ReqOptions, State), ReqContext1 = case proplists:get_value(content_type, Options) of undefined -> ReqContext#req_context{ content_type = "application/octet-stream" }; @@ -570,12 +360,15 @@ handle_call({put_blob, Container, Name, Type = block_blob, Data, Options}, _From end, {Code, Body} = execute_request(ServiceContext, ReqContext1), - return_response(Code, Body, State, ?http_created, created); + return_response(Code, Body, State, ?http_created, created). -% Put page blob -handle_call({put_blob, Container, Name, Type = page_blob, ContentLength, Options}, _From, State) -> +put_page_blob(State, Container, Name, ContentLength) -> + put_page_blob(State, Container, Name, ContentLength, []). +put_page_blob(State, Container, Name, ContentLength, Options) -> + put_page_blob(State, Container, Name, ContentLength, Options, ?gen_server_call_default_timeout). +put_page_blob(State, Container, Name, ContentLength, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?blob_service, State), - Params = [{blob_type, Type}, + Params = [{blob_type, page_blob}, {blob_content_length, ContentLength}], ReqOptions = [{method, put}, {path, lists:concat([Container, "/", Name])}, @@ -583,12 +376,15 @@ handle_call({put_blob, Container, Name, Type = page_blob, ContentLength, Options ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_created, created); + return_response(Code, Body, State, ?http_created, created). -% Put append blob -handle_call({put_blob, Container, Name, Type = append_blob, Options}, _From, State) -> +put_append_blob(State, Container, Name) -> + put_append_blob(State, Container, Name, []). +put_append_blob(State, Container, Name, Options) -> + put_append_blob(State, Container, Name, Options, ?gen_server_call_default_timeout). +put_append_blob(State, Container, Name, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?blob_service, State), - Params = [{blob_type, Type}], + Params = [{blob_type, append_blob}], ReqOptions = [{method, put}, {path, lists:concat([Container, "/", Name])}, {params, Params ++ Options}], @@ -599,10 +395,13 @@ handle_call({put_blob, Container, Name, Type = append_blob, Options}, _From, Sta end, {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_created, created); + return_response(Code, Body, State, ?http_created, created). -% Append block -handle_call({append_block, Container, Name, Data, Options}, _From, State) -> +append_block(State, Container, Name, Data) -> + append_block(State, Container, Name, Data, []). +append_block(State, Container, Name, Data, Options) -> + append_block(State, Container, Name, Data, Options, ?gen_server_call_default_timeout). +append_block(State, Container, Name, Data, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?blob_service, State), Params = [{comp, "appendblock"}], ReqOptions = [{method, put}, @@ -612,10 +411,29 @@ handle_call({append_block, Container, Name, Data, Options}, _From, State) -> ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_created, appended); + return_response(Code, Body, State, ?http_created, appended). -% Get blob -handle_call({get_blob, Container, Blob, Options}, _From, State) -> +list_blobs(State, Container) -> + list_blobs(State, Container, []). +list_blobs(State, Container, Options) -> + list_blobs(State, Container, Options, ?gen_server_call_default_timeout). +list_blobs(State, Container, Options, Timeout) when is_list(Options); is_integer(Timeout) -> + ServiceContext = new_service_context(?blob_service, State), + Params = [{comp, list}, + {res_type, container}], + ReqOptions = [{path, Container}, + {params, Params ++ Options}], + ReqContext = new_req_context(?blob_service, ReqOptions, State), + + {?http_ok, Body} = execute_request(ServiceContext, ReqContext), + {ok, Blobs} = erlazure_blob:parse_blob_list(Body), + Blobs. + +get_blob(State, Container, Blob) -> + get_blob(State, Container, Blob, []). +get_blob(State, Container, Blob, Options) -> + get_blob(State, Container, Blob, Options, ?gen_server_call_default_timeout). +get_blob(State, Container, Blob, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{path, lists:concat([Container, "/", Blob])}, {params, Options}], @@ -624,14 +442,17 @@ handle_call({get_blob, Container, Blob, Options}, _From, State) -> {Code, Body} = execute_request(ServiceContext, ReqContext), case Code of ?http_ok -> - {reply, {ok, Body}, State}; + {ok, Body}; ?http_partial_content-> - {reply, {ok, Body}, State}; - _ -> {reply, {error, Body}, State} - end; + {ok, Body}; + _ -> {error, Body} + end. -% Snapshot blob -handle_call({snapshot_blob, Container, Blob, Options}, _From, State) -> +snapshot_blob(State, Container, Blob) -> + snapshot_blob(State, Container, Blob, []). +snapshot_blob(State, Container, Blob, Options) -> + snapshot_blob(State, Container, Blob, Options, ?gen_server_call_default_timeout). +snapshot_blob(State, Container, Blob, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{method, put}, {path, lists:concat([Container, "/", Blob])}, @@ -639,10 +460,13 @@ handle_call({snapshot_blob, Container, Blob, Options}, _From, State) -> ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_created, created); + return_response(Code, Body, State, ?http_created, created). -% Copy blob -handle_call({copy_blob, Container, Blob, Source, Options}, _From, State) -> +copy_blob(State, Container, Blob, Source) -> + copy_blob(State, Container, Blob, Source, []). +copy_blob(State, Container, Blob, Source, Options) -> + copy_blob(State, Container, Blob, Source, Options, ?gen_server_call_default_timeout). +copy_blob(State, Container, Blob, Source, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{method, put}, {path, lists:concat([Container, "/", Blob])}, @@ -650,10 +474,13 @@ handle_call({copy_blob, Container, Blob, Source, Options}, _From, State) -> ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_accepted, created); + return_response(Code, Body, State, ?http_accepted, created). -% Delete blob -handle_call({delete_blob, Container, Blob, Options}, _From, State) -> +delete_blob(State, Container, Blob) -> + delete_blob(State, Container, Blob, []). +delete_blob(State, Container, Blob, Options) -> + delete_blob(State, Container, Blob, Options, ?gen_server_call_default_timeout). +delete_blob(State, Container, Blob, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{method, delete}, {path, lists:concat([Container, "/", Blob])}, @@ -661,24 +488,30 @@ handle_call({delete_blob, Container, Blob, Options}, _From, State) -> ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_accepted, deleted); + return_response(Code, Body, State, ?http_accepted, deleted). -% Put block -handle_call({put_block, Container, Blob, BlockId, Content, Options}, _From, State) -> +put_block(State, Container, Blob, BlockId, BlockContent) -> + put_block(State, Container, Blob, BlockId, BlockContent, []). +put_block(State, Container, Blob, BlockId, BlockContent, Options) -> + put_block(State, Container, Blob, BlockId, BlockContent, Options, ?gen_server_call_default_timeout). +put_block(State, Container, Blob, BlockId, BlockContent, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?blob_service, State), Params = [{comp, block}, {blob_block_id, uri_string:quote(base64:encode_to_string(BlockId))}], ReqOptions = [{method, put}, {path, lists:concat([Container, "/", Blob])}, - {body, Content}, + {body, BlockContent}, {params, Params ++ Options}], ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_created, created); + return_response(Code, Body, State, ?http_created, created). -% Put block list -handle_call({put_block_list, Container, Blob, BlockRefs, Options0}, _From, State) -> +put_block_list(State, Container, Blob, BlockRefs) -> + put_block_list(State, Container, Blob, BlockRefs, []). +put_block_list(State, Container, Blob, BlockRefs, Options) -> + put_block_list(State, Container, Blob, BlockRefs, Options, ?gen_server_call_default_timeout). +put_block_list(State, Container, Blob, BlockRefs, Options0, Timeout) when is_list(Options0); is_integer(Timeout) -> ServiceContext = new_service_context(?blob_service, State), {ExtraReqOpts, Options} = proplist_take(req_opts, Options0, []), ReqOptions = [{method, put}, @@ -688,10 +521,13 @@ handle_call({put_block_list, Container, Blob, BlockRefs, Options0}, _From, State ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_created, created); + return_response(Code, Body, State, ?http_created, created). -% Get block list -handle_call({get_block_list, Container, Blob, Options}, _From, State) -> +get_block_list(State, Container, Blob) -> + get_block_list(State, Container, Blob, []). +get_block_list(State, Container, Blob, Options) -> + get_block_list(State, Container, Blob, Options, ?gen_server_call_default_timeout). +get_block_list(State, Container, Blob, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{path, lists:concat([Container, "/", Blob])}, {params, [{comp, "blocklist"}] ++ Options}], @@ -699,10 +535,13 @@ handle_call({get_block_list, Container, Blob, Options}, _From, State) -> {?http_ok, Body} = execute_request(ServiceContext, ReqContext), {ok, BlockList} = erlazure_blob:parse_block_list(Body), - {reply, BlockList, State}; + BlockList. -% Acquire blob lease -handle_call({acquire_blob_lease, Container, Blob, ProposedId, Duration, Options}, _From, State) -> +acquire_blob_lease(State, Container, Blob, Duration) -> + acquire_blob_lease(State, Container, Blob, Duration, []). +acquire_blob_lease(State, Container, Blob, Duration, Options) -> + acquire_blob_lease(State, Container, Blob, "", Duration, Options, ?gen_server_call_default_timeout). +acquire_blob_lease(State, Container, Blob, ProposedId, Duration, Options, Timeout) when is_list(Options); is_integer(Timeout) -> ServiceContext = new_service_context(?blob_service, State), Params = [{lease_action, acquire}, @@ -716,20 +555,44 @@ handle_call({acquire_blob_lease, Container, Blob, ProposedId, Duration, Options} ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_created, acquired); + return_response(Code, Body, State, ?http_created, acquired). -% List tables -handle_call({list_tables, Options}, _From, State) -> +lease_container(State, Name, Mode) -> + lease_container(State, Name, Mode, []). +lease_container(State, Name, Mode, Options) -> + lease_container(State, Name, Mode, Options, ?gen_server_call_default_timeout). +lease_container(State, Name, Mode, Options, Timeout) when is_atom(Mode); is_list(Options); is_integer(Timeout) -> + ServiceContext = new_service_context(?blob_service, State), + Params = [{comp, lease}, + {res_type, container}, + {lease_action, Mode}], + ReqOptions = [{method, put}, + {path, Name}, + {params, Params ++ Options}], + ReqContext = new_req_context(?blob_service, ReqOptions, State), + + {Code, Body} = execute_request(ServiceContext, ReqContext), + return_response(Code, Body, State, ?http_accepted, deleted). + +%%==================================================================== +%% Table +%%==================================================================== + +list_tables(State) -> + list_tables(State, [], ?gen_server_call_default_timeout). +list_tables(State, Options, Timeout) when is_list(Options); is_integer(Timeout)-> ServiceContext = new_service_context(?table_service, State), ReqOptions = [{path, "Tables"}, {params, Options}], ReqContext = new_req_context(?table_service, ReqOptions, State), {?http_ok, Body} = execute_request(ServiceContext, ReqContext), - {reply, {ok, erlazure_table:parse_table_list(Body)}, State}; + {ok, erlazure_table:parse_table_list(Body)}. + +new_table(State, TableName) when is_list(TableName) -> + new_table(State, list_to_binary(TableName)); -% New tables -handle_call({new_table, TableName}, _From, State) -> +new_table(State, TableName) when is_binary(TableName) -> ServiceContext = new_service_context(?table_service, State), ReqOptions = [{path, "Tables"}, {method, post}, @@ -737,34 +600,18 @@ handle_call({new_table, TableName}, _From, State) -> ReqContext = new_req_context(?table_service, ReqOptions, State), ReqContext1 = ReqContext#req_context{ content_type = ?json_content_type }, {Code, Body} = execute_request(ServiceContext, ReqContext1), - return_response(Code, Body, State, ?http_created, created); + return_response(Code, Body, State, ?http_created, created). -% Get host -handle_call({get_host, Service, Domain}, _From, State) -> - Account = State#state.account, - Host = lists:concat([Account, ".", erlang:atom_to_list(Service), Domain]), - {reply, Host, State}; +delete_table(State, TableName) when is_binary(TableName) -> + delete_table(State, binary_to_list(TableName)); -% Delete table -handle_call({delete_table, TableName}, _From, State) -> +delete_table(State, TableName) when is_list(TableName) -> ServiceContext = new_service_context(?table_service, State), ReqOptions = [{path, io:format("Tables('~s')", [TableName])}, {method, delete}], ReqContext = new_req_context(?table_service, ReqOptions, State), {?http_no_content, _} = execute_request(ServiceContext, ReqContext), - {reply, {ok, deleted}, State}. - -handle_cast(_Msg, State) -> - {noreply, State}. - -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, _State) -> - ok. - -code_change(_OldVer, State, _Extra) -> - {ok, State}. + {ok, deleted}. %%-------------------------------------------------------------------- %% Private functions @@ -891,18 +738,19 @@ build_uri_base(Service, #state{account = Account}) -> get_host(Service, Account) -> lists:concat([Account, ".", erlang:atom_to_list(Service), ".core.windows.net"]). -get_host(Pid, Service, []) -> +get_host(State, Service, []) -> Domain = ".core.windows.net", Timeout = ?gen_server_call_default_timeout, - get_host(Pid, Service, Domain, Timeout); -get_host(Pid, Service, Timeout) when is_integer(Timeout) -> + get_host(State, Service, Domain, Timeout); +get_host(State, Service, Timeout) when is_integer(Timeout) -> Domain = ".core.windows.net", - get_host(Pid, Service, Domain, Timeout); -get_host(Pid, Service, Domain) when is_list(Domain) -> + get_host(State, Service, Domain, Timeout); +get_host(State, Service, Domain) when is_list(Domain) -> Timeout = ?gen_server_call_default_timeout, - get_host(Pid, Service, Domain, Timeout). -get_host(Pid, Service, Domain, Timeout) when is_list(Domain), is_integer(Timeout) -> - gen_server:call(Pid, {get_host, Service, Domain}, Timeout). + get_host(State, Service, Domain, Timeout). +get_host(State, Service, Domain, Timeout) when is_list(Domain), is_integer(Timeout) -> + Account = State#state.account, + lists:concat([Account, ".", erlang:atom_to_list(Service), Domain]). -spec canonicalize_headers([string()]) -> string(). canonicalize_headers(Headers) -> @@ -1055,9 +903,9 @@ get_req_common_param_specs() -> return_response(Code, Body, State, ExpectedResponseCode, SuccessAtom) -> case Code of ExpectedResponseCode -> - {reply, {ok, SuccessAtom}, State}; + {ok, SuccessAtom}; _ -> - {reply, {error, Body}, State} + {error, Body} end. -spec parse_init_opts(init_opts()) -> state_opts(). diff --git a/test/erlazure_SUITE.erl b/test/erlazure_SUITE.erl index 604fba3..6f9018f 100644 --- a/test/erlazure_SUITE.erl +++ b/test/erlazure_SUITE.erl @@ -73,17 +73,17 @@ end_per_testcase(_TestCase, Config) -> %% Helper fns %%------------------------------------------------------------------------------ -start(Config) -> +new(Config) -> Endpoint = ?config(endpoint, Config), - {ok, Pid} = erlazure:start(#{account => ?ACCOUNT, key => ?KEY, endpoint => Endpoint}), - Pid. + {ok, State} = erlazure:new(#{account => ?ACCOUNT, key => ?KEY, endpoint => Endpoint}), + State. delete_all_containers(Config) -> - Pid = start(Config), - {Containers, _} = erlazure:list_containers(Pid), + State = new(Config), + {Containers, _} = erlazure:list_containers(State), lists:foreach( fun(#blob_container{name = Name}) -> - {ok, deleted} = erlazure:delete_container(Pid, Name) + {ok, deleted} = erlazure:delete_container(State, Name) end, Containers). @@ -98,118 +98,118 @@ container_name(Name) -> %% Basic smoke test for basic blob storage operations. t_blob_storage_smoke_test(Config) -> Endpoint = ?config(endpoint, Config), - {ok, Pid} = erlazure:start(#{account => ?ACCOUNT, key => ?KEY, endpoint => Endpoint}), + {ok, State} = erlazure:new(#{account => ?ACCOUNT, key => ?KEY, endpoint => Endpoint}), %% Create a container Container = container_name(?FUNCTION_NAME), - ?assertMatch({[], _}, erlazure:list_containers(Pid)), - ?assertMatch({ok, created}, erlazure:create_container(Pid, Container)), + ?assertMatch({[], _}, erlazure:list_containers(State)), + ?assertMatch({ok, created}, erlazure:create_container(State, Container)), %% Upload some blobs - ?assertMatch({ok, created}, erlazure:put_block_blob(Pid, Container, "blob1", <<"1">>)), - ?assertMatch({ok, created}, erlazure:put_block_blob(Pid, Container, "blob2", <<"2">>)), + ?assertMatch({ok, created}, erlazure:put_block_blob(State, Container, "blob1", <<"1">>)), + ?assertMatch({ok, created}, erlazure:put_block_blob(State, Container, "blob2", <<"2">>)), ?assertMatch({[#cloud_blob{name = "blob1"}, #cloud_blob{name = "blob2"}], _}, - erlazure:list_blobs(Pid, Container)), + erlazure:list_blobs(State, Container)), %% Read back data - ?assertMatch({ok, <<"1">>}, erlazure:get_blob(Pid, Container, "blob1")), - ?assertMatch({ok, <<"2">>}, erlazure:get_blob(Pid, Container, "blob2")), + ?assertMatch({ok, <<"1">>}, erlazure:get_blob(State, Container, "blob1")), + ?assertMatch({ok, <<"2">>}, erlazure:get_blob(State, Container, "blob2")), %% Delete blob - ?assertMatch({ok, deleted}, erlazure:delete_blob(Pid, Container, "blob1")), + ?assertMatch({ok, deleted}, erlazure:delete_blob(State, Container, "blob1")), ?assertMatch({[#cloud_blob{name = "blob2"}], _}, - erlazure:list_blobs(Pid, Container)), + erlazure:list_blobs(State, Container)), %% Delete container - ?assertMatch({ok, deleted}, erlazure:delete_container(Pid, Container)), + ?assertMatch({ok, deleted}, erlazure:delete_container(State, Container)), ok. %% Basic smoke test to check that we can pass already wrapped keys to `erlazure:start`. t_blob_storage_wrapped_key(Config) -> Endpoint = ?config(endpoint, Config), - {ok, Pid} = erlazure:start(#{account => ?ACCOUNT, key => ?KEY, endpoint => Endpoint}), - ?assertMatch({[], _}, erlazure:list_containers(Pid)), + {ok, State} = erlazure:new(#{account => ?ACCOUNT, key => ?KEY, endpoint => Endpoint}), + ?assertMatch({[], _}, erlazure:list_containers(State)), ok. %% Basic smoke test for append blob storage operations. t_append_blob_smoke_test(Config) -> Endpoint = ?config(endpoint, Config), - {ok, Pid} = erlazure:start(#{account => ?ACCOUNT, key => ?KEY, endpoint => Endpoint}), + {ok, State} = erlazure:new(#{account => ?ACCOUNT, key => ?KEY, endpoint => Endpoint}), %% Create a container Container = container_name(?FUNCTION_NAME), - ?assertMatch({[], _}, erlazure:list_containers(Pid)), - ?assertMatch({ok, created}, erlazure:create_container(Pid, Container)), + ?assertMatch({[], _}, erlazure:list_containers(State)), + ?assertMatch({ok, created}, erlazure:create_container(State, Container)), %% Upload some blobs Opts = [{content_type, "text/csv"}], - ?assertMatch({ok, created}, erlazure:put_append_blob(Pid, Container, "blob1", Opts)), - ?assertMatch({ok, appended}, erlazure:append_block(Pid, Container, "blob1", <<"1">>)), - ?assertMatch({ok, appended}, erlazure:append_block(Pid, Container, "blob1", <<"\n">>)), - ?assertMatch({ok, appended}, erlazure:append_block(Pid, Container, "blob1", <<"2">>)), - ListedBlobs = erlazure:list_blobs(Pid, Container), + ?assertMatch({ok, created}, erlazure:put_append_blob(State, Container, "blob1", Opts)), + ?assertMatch({ok, appended}, erlazure:append_block(State, Container, "blob1", <<"1">>)), + ?assertMatch({ok, appended}, erlazure:append_block(State, Container, "blob1", <<"\n">>)), + ?assertMatch({ok, appended}, erlazure:append_block(State, Container, "blob1", <<"2">>)), + ListedBlobs = erlazure:list_blobs(State, Container), ?assertMatch({[#cloud_blob{name = "blob1"}], _}, ListedBlobs), {[#cloud_blob{name = "blob1", properties = BlobProps}], _} = ListedBlobs, ?assertMatch(#{content_type := "text/csv"}, maps:from_list(BlobProps)), %% Read back data - ?assertMatch({ok, <<"1\n2">>}, erlazure:get_blob(Pid, Container, "blob1")), + ?assertMatch({ok, <<"1\n2">>}, erlazure:get_blob(State, Container, "blob1")), %% Delete blob - ?assertMatch({ok, deleted}, erlazure:delete_blob(Pid, Container, "blob1")), - ?assertMatch({[], _}, erlazure:list_blobs(Pid, Container)), + ?assertMatch({ok, deleted}, erlazure:delete_blob(State, Container, "blob1")), + ?assertMatch({[], _}, erlazure:list_blobs(State, Container)), %% Delete container - ?assertMatch({ok, deleted}, erlazure:delete_container(Pid, Container)), + ?assertMatch({ok, deleted}, erlazure:delete_container(State, Container)), ok. %% Test error handling when endpoint is unavailable t_blob_failure_to_connect(_Config) -> BadEndpoint = "http://127.0.0.2:65535/", - {ok, Pid} = erlazure:start(#{account => ?ACCOUNT, key => ?KEY, endpoint => BadEndpoint}), - ?assertMatch({error, {failed_connect, _}}, erlazure:list_containers(Pid)), - ?assertMatch({error, {failed_connect, _}}, erlazure:create_container(Pid, "c")), - ?assertMatch({error, {failed_connect, _}}, erlazure:delete_container(Pid, "c")), - ?assertMatch({error, {failed_connect, _}}, erlazure:put_append_blob(Pid, "c", "b1")), - ?assertMatch({error, {failed_connect, _}}, erlazure:put_block_blob(Pid, "c", "b1", <<"a">>)), - ?assertMatch({error, {failed_connect, _}}, erlazure:append_block(Pid, "c", "b1", <<"a">>)), - ?assertMatch({error, {failed_connect, _}}, erlazure:get_blob(Pid, "c", "b1")), + {ok, State} = erlazure:new(#{account => ?ACCOUNT, key => ?KEY, endpoint => BadEndpoint}), + ?assertMatch({error, {failed_connect, _}}, erlazure:list_containers(State)), + ?assertMatch({error, {failed_connect, _}}, erlazure:create_container(State, "c")), + ?assertMatch({error, {failed_connect, _}}, erlazure:delete_container(State, "c")), + ?assertMatch({error, {failed_connect, _}}, erlazure:put_append_blob(State, "c", "b1")), + ?assertMatch({error, {failed_connect, _}}, erlazure:put_block_blob(State, "c", "b1", <<"a">>)), + ?assertMatch({error, {failed_connect, _}}, erlazure:append_block(State, "c", "b1", <<"a">>)), + ?assertMatch({error, {failed_connect, _}}, erlazure:get_blob(State, "c", "b1")), ok. %% Basic smoke test for block blob storage operations. t_put_block(Config) -> Endpoint = ?config(endpoint, Config), - {ok, Pid} = erlazure:start(#{account => ?ACCOUNT, key => ?KEY, endpoint => Endpoint}), + {ok, State} = erlazure:new(#{account => ?ACCOUNT, key => ?KEY, endpoint => Endpoint}), %% Create a container Container = container_name(?FUNCTION_NAME), - ?assertMatch({[], _}, erlazure:list_containers(Pid)), - ?assertMatch({ok, created}, erlazure:create_container(Pid, Container)), + ?assertMatch({[], _}, erlazure:list_containers(State)), + ?assertMatch({ok, created}, erlazure:create_container(State, Container)), %% Upload some blocks. Note: this content-type will be overwritten later by `put_block_list'. Opts1 = [{content_type, "application/json"}], BlobName = "blob1", - ?assertMatch({ok, created}, erlazure:put_block_blob(Pid, Container, BlobName, <<"0">>, Opts1)), + ?assertMatch({ok, created}, erlazure:put_block_blob(State, Container, BlobName, <<"0">>, Opts1)), %% Note: this short name is important for this test. It'll produce a base64 string %% that's padded. That padding must be URL-encoded when sending the request, but not %% when generating the string to sign. BlockId1 = <<"blo1">>, - ?assertMatch({ok, created}, erlazure:put_block(Pid, Container, BlobName, BlockId1, <<"a">>)), + ?assertMatch({ok, created}, erlazure:put_block(State, Container, BlobName, BlockId1, <<"a">>)), %% Testing iolists BlockId2 = <<"blo2">>, - ?assertMatch({ok, created}, erlazure:put_block(Pid, Container, BlobName, BlockId2, [<<"\n">>, ["b", [$\n]]])), + ?assertMatch({ok, created}, erlazure:put_block(State, Container, BlobName, BlockId2, [<<"\n">>, ["b", [$\n]]])), %% Not yet committed. Contains only the data from the blob creation. - ?assertMatch({ok, <<"0">>}, erlazure:get_blob(Pid, Container, BlobName)), + ?assertMatch({ok, <<"0">>}, erlazure:get_blob(State, Container, BlobName)), %% Committing BlockList1 = [{BlockId1, latest}], - ?assertMatch({ok, created}, erlazure:put_block_list(Pid, Container, BlobName, BlockList1)), + ?assertMatch({ok, created}, erlazure:put_block_list(State, Container, BlobName, BlockList1)), %% Committed only first block. Initial data was lost, as it was not in the block list. - ?assertMatch({ok, <<"a">>}, erlazure:get_blob(Pid, Container, BlobName)), + ?assertMatch({ok, <<"a">>}, erlazure:get_blob(State, Container, BlobName)), %% Block 2 was dropped after committing. - ?assertMatch({[#blob_block{id = "blo1"}], _}, erlazure:get_block_list(Pid, Container, BlobName)), + ?assertMatch({[#blob_block{id = "blo1"}], _}, erlazure:get_block_list(State, Container, BlobName)), BlockId3 = <<"blo3">>, - ?assertMatch({ok, created}, erlazure:put_block(Pid, Container, BlobName, BlockId3, [<<"\n">>, ["b", [$\n]]])), + ?assertMatch({ok, created}, erlazure:put_block(State, Container, BlobName, BlockId3, [<<"\n">>, ["b", [$\n]]])), %% Commit both blocks Opts2 = [{req_opts, [{headers, [{"x-ms-blob-content-type", "text/csv"}]}]}], BlockList2 = [{BlockId1, committed}, {BlockId3, uncommitted}], - ?assertMatch({ok, created}, erlazure:put_block_list(Pid, Container, BlobName, BlockList2, Opts2)), - ?assertMatch({ok, <<"a\nb\n">>}, erlazure:get_blob(Pid, Container, BlobName)), + ?assertMatch({ok, created}, erlazure:put_block_list(State, Container, BlobName, BlockList2, Opts2)), + ?assertMatch({ok, <<"a\nb\n">>}, erlazure:get_blob(State, Container, BlobName)), %% Check content type. - ListedBlobs = erlazure:list_blobs(Pid, Container), + ListedBlobs = erlazure:list_blobs(State, Container), ?assertMatch({[#cloud_blob{name = "blob1"}], _}, ListedBlobs), {[#cloud_blob{name = "blob1", properties = Props}], _} = ListedBlobs, %% Content-type from `put_block_list' prevails. ?assertMatch(#{content_type := "text/csv"}, maps:from_list(Props)), %% Delete container - ?assertMatch({ok, deleted}, erlazure:delete_container(Pid, Container)), + ?assertMatch({ok, deleted}, erlazure:delete_container(State, Container)), ok. diff --git a/test/erlazure_queue_cloud_tests.erl b/test/erlazure_queue_cloud_tests.erl index 1b023cf..082879f 100644 --- a/test/erlazure_queue_cloud_tests.erl +++ b/test/erlazure_queue_cloud_tests.erl @@ -123,55 +123,55 @@ peek_messages_test_() -> fun peek_messages/1}. start() -> - {ok, Pid} = erlazure:start(?account_name, ?account_key), + {ok, State} = erlazure:new(?account_name, ?account_key), UniqueQueueName = get_queue_unique_name(), - {Pid, UniqueQueueName}. + {State, UniqueQueueName}. start_create() -> - {ok, Pid} = erlazure:start(?account_name, ?account_key), + {ok, State} = erlazure:new(?account_name, ?account_key), UniqueQueueName = get_queue_unique_name(), - {ok, created} = erlazure:create_queue(Pid, UniqueQueueName), - {Pid, UniqueQueueName}. + {ok, created} = erlazure:create_queue(State, UniqueQueueName), + {State, UniqueQueueName}. -stop({Pid, QueueName}) -> - erlazure:delete_queue(Pid, QueueName). +stop({State, QueueName}) -> + erlazure:delete_queue(State, QueueName). -create_queue({Pid, QueueName}) -> - Response = erlazure:create_queue(Pid, QueueName), +create_queue({State, QueueName}) -> + Response = erlazure:create_queue(State, QueueName), ?_assertMatch({ok, created}, Response). -create_queue_duplicate_name({Pid, QueueName}) -> - Response = erlazure:create_queue(Pid, QueueName), +create_queue_duplicate_name({State, QueueName}) -> + Response = erlazure:create_queue(State, QueueName), ?_assertMatch({error, already_created}, Response). -list_queues({Pid, QueueName}) -> +list_queues({State, QueueName}) -> LowerQueueName = string:to_lower(QueueName), - {ok, {Queues, _Metadata}} = erlazure:list_queues(Pid), + {ok, {Queues, _Metadata}} = erlazure:list_queues(State), Queue = lists:keyfind(LowerQueueName, 2, Queues), ?_assertMatch(#queue { name = LowerQueueName }, Queue). -delete_queue({Pid, QueueName}) -> - Response = erlazure:delete_queue(Pid, QueueName), +delete_queue({State, QueueName}) -> + Response = erlazure:delete_queue(State, QueueName), ?_assertMatch({ok, deleted}, Response). -delete_queue_twice({Pid, QueueName}) -> - {ok, deleted} = erlazure:delete_queue(Pid, QueueName), - Response = erlazure:delete_queue(Pid, QueueName), +delete_queue_twice({State, QueueName}) -> + {ok, deleted} = erlazure:delete_queue(State, QueueName), + Response = erlazure:delete_queue(State, QueueName), ?_assertMatch({ok, deleted}, Response). -set_queue_acl({Pid, QueueName}) -> +set_queue_acl({State, QueueName}) -> SignedId = get_queue_test_acl(), - Response = erlazure:set_queue_acl(Pid, QueueName, SignedId), + Response = erlazure:set_queue_acl(State, QueueName, SignedId), ?_assertMatch({ok, created}, Response). -get_queue_empty_acl({Pid, QueueName}) -> - Response = erlazure:get_queue_acl(Pid, QueueName), +get_queue_empty_acl({State, QueueName}) -> + Response = erlazure:get_queue_acl(State, QueueName), ?_assertMatch({ok, no_acl}, Response). -get_queue_acl({Pid, QueueName}) -> +get_queue_acl({State, QueueName}) -> SignedId = get_queue_test_acl(), - {ok, created} = erlazure:set_queue_acl(Pid, QueueName, SignedId), - {ok, Response} = erlazure:get_queue_acl(Pid, QueueName), + {ok, created} = erlazure:set_queue_acl(State, QueueName, SignedId), + {ok, Response} = erlazure:get_queue_acl(State, QueueName), Id = SignedId#signed_id.id, Start = string:left(SignedId#signed_id.access_policy#access_policy.start, 19), @@ -183,41 +183,41 @@ get_queue_acl({Pid, QueueName}) -> ?_assertMatch([], string:tokens(Response#signed_id.access_policy#access_policy.permission, Permission))]. -put_message({Pid, QueueName}) -> - Response = erlazure:put_message(Pid, QueueName, "test message"), +put_message({State, QueueName}) -> + Response = erlazure:put_message(State, QueueName, "test message"), ?_assertMatch({ok, created}, Response). -get_message({Pid, QueueName}) -> - {ok, created} = erlazure:put_message(Pid, QueueName, "test message"), - Response = erlazure:get_messages(Pid, QueueName), +get_message({State, QueueName}) -> + {ok, created} = erlazure:put_message(State, QueueName, "test message"), + Response = erlazure:get_messages(State, QueueName), ?_assertMatch({ok, [#queue_message { text = "test message"}]}, Response). -get_messages({Pid, QueueName}) -> - {ok, created} = erlazure:put_message(Pid, QueueName, "test message1"), - {ok, created} = erlazure:put_message(Pid, QueueName, "test message2"), - {ok, created} = erlazure:put_message(Pid, QueueName, "test message3"), - {ok, Messages} = erlazure:get_messages(Pid, QueueName, [{num_of_messages, 32}]), +get_messages({State, QueueName}) -> + {ok, created} = erlazure:put_message(State, QueueName, "test message1"), + {ok, created} = erlazure:put_message(State, QueueName, "test message2"), + {ok, created} = erlazure:put_message(State, QueueName, "test message3"), + {ok, Messages} = erlazure:get_messages(State, QueueName, [{num_of_messages, 32}]), ?_assertEqual(3, lists:flatlength(Messages)). -get_messages_removes_from_queue({Pid, QueueName}) -> - {ok, created} = erlazure:put_message(Pid, QueueName, "test message1"), - {ok, created} = erlazure:put_message(Pid, QueueName, "test message2"), - {ok, created} = erlazure:put_message(Pid, QueueName, "test message3"), - {ok, _Messages} = erlazure:get_messages(Pid, QueueName, [{num_of_messages, 32}]), - {ok, Messages} = erlazure:get_messages(Pid, QueueName, [{num_of_messages, 32}]), +get_messages_removes_from_queue({State, QueueName}) -> + {ok, created} = erlazure:put_message(State, QueueName, "test message1"), + {ok, created} = erlazure:put_message(State, QueueName, "test message2"), + {ok, created} = erlazure:put_message(State, QueueName, "test message3"), + {ok, _Messages} = erlazure:get_messages(State, QueueName, [{num_of_messages, 32}]), + {ok, Messages} = erlazure:get_messages(State, QueueName, [{num_of_messages, 32}]), ?_assertMatch([], Messages). -peek_message({Pid, QueueName}) -> - {ok, created} = erlazure:put_message(Pid, QueueName, "test message 1"), - {ok, created} = erlazure:put_message(Pid, QueueName, "test message 2"), - Response = erlazure:peek_messages(Pid, QueueName), +peek_message({State, QueueName}) -> + {ok, created} = erlazure:put_message(State, QueueName, "test message 1"), + {ok, created} = erlazure:put_message(State, QueueName, "test message 2"), + Response = erlazure:peek_messages(State, QueueName), ?_assertMatch({ok, [#queue_message { text = "test message 1" }]}, Response). -peek_messages({Pid, QueueName}) -> - {ok, created} = erlazure:put_message(Pid, QueueName, "test message 1"), - {ok, created} = erlazure:put_message(Pid, QueueName, "test message 2"), - {ok, created} = erlazure:put_message(Pid, QueueName, "test message 3"), - {ok, Messages} = erlazure:peek_messages(Pid, QueueName, [{num_of_messages, 32}]), +peek_messages({State, QueueName}) -> + {ok, created} = erlazure:put_message(State, QueueName, "test message 1"), + {ok, created} = erlazure:put_message(State, QueueName, "test message 2"), + {ok, created} = erlazure:put_message(State, QueueName, "test message 3"), + {ok, Messages} = erlazure:peek_messages(State, QueueName, [{num_of_messages, 32}]), ?_assertEqual(3, lists:flatlength(Messages)). get_queue_unique_name() -> @@ -236,4 +236,4 @@ get_queue_test_acl() -> expiry = erlazure_utils:iso_8601_fmt(Expiry), permission = "raup" }, #signed_id { id = "12345678901234567890123456789012", - access_policy = AccessPolicy }. \ No newline at end of file + access_policy = AccessPolicy }. From 65b80619d6cdb691c11f81d12cc0de3032cd9769 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Thu, 6 Jun 2024 16:18:00 -0300 Subject: [PATCH 14/17] refactor: remove references to `gen_server` timeouts --- README.md | 11 +-- src/erlazure.erl | 197 ++++++++++++++++------------------------------- 2 files changed, 69 insertions(+), 139 deletions(-) diff --git a/README.md b/README.md index 4c28ff9..c9554dc 100644 --- a/README.md +++ b/README.md @@ -64,12 +64,10 @@ Key = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr ``` ## Calling Azure services -Almost each azure services request has three corresponding functions in ```erlazure``` module, the first has minimal set of parameters, the second has additionaly list of ```Options``` and the third has additionaly ```Timeout``` parameter. +Almost each azure services request has three corresponding functions in ```erlazure``` module, the first has minimal set of parameters, the second has additionaly list of ```Options```. ```Options``` as the name states is list of options supported by this azure services request, each options is tuple ```{OptionName, OptionValue}``` where ```OptionName``` is atom and ```OptionValue``` can be of any type, option is passed either as a header or as a query string parameter. -```Timeout``` is number of milliseconds to wait for a reply from erlazure, infinity value is not supported. Note, that this is gen_server call timeout and isn't http request timeout (it's set to infinity by default) or azure service request timeout (you can specify it by sending option ```{timeout, _Timeout}```). By default timeout is set to 30 seconds. - For a list of supported options for each azure service request please consult msdn documentation. ## Examples @@ -80,13 +78,6 @@ For a list of supported options for each azure service request please consult ms {ok, Binary} = file:read("/path/to/some/small/file"), {ok, created} = erlazure:put_block_blob(State, "uploads", "test_upload.file", Binary). ``` -### Upload block blob with timeout set -Uploads block blob and waits no longer than 15 seconds for erlazure to finish the upload -``` -{ok, State} = erlazure:new("storage", "2o4b4tHpoWifLU+BlyzsIG1VtlO9LgBRFyl1qLw/+w9/ZszSxKGIK8JYac/UEJp5r8HKgiOiG8YTqGS9otAYWA=="), -{ok, Binary} = file:read("/path/to/some/other/file"), -{ok, created} = erlazure:put_block_blob(State, "uploads", "test_upload2.file", Binary, [], 15000). -``` ### Get 20 messages from a queue Retrieves max 20 messages from a queue diff --git a/src/erlazure.erl b/src/erlazure.erl index c8445c8..afa1372 100644 --- a/src/erlazure.erl +++ b/src/erlazure.erl @@ -35,48 +35,47 @@ -include("../include/erlazure.hrl"). -define(json_content_type, "application/json"). --define(gen_server_call_default_timeout, 30000). %% API -export([new/1, new/2]). %% Queue API --export([list_queues/1, list_queues/2, list_queues/3]). --export([set_queue_acl/3, set_queue_acl/4, set_queue_acl/5]). --export([get_queue_acl/2, get_queue_acl/3, get_queue_acl/4]). --export([create_queue/2, create_queue/3, create_queue/4]). --export([delete_queue/2, delete_queue/3, delete_queue/4]). --export([put_message/3, put_message/4, put_message/5]). --export([get_messages/2, get_messages/3, get_messages/4]). --export([peek_messages/2, peek_messages/3, peek_messages/4]). --export([delete_message/4, delete_message/5, delete_message/6]). --export([clear_messages/2, clear_messages/3, clear_messages/4]). --export([update_message/4, update_message/5, update_message/6]). +-export([list_queues/1, list_queues/2]). +-export([set_queue_acl/3, set_queue_acl/4]). +-export([get_queue_acl/2, get_queue_acl/3]). +-export([create_queue/2, create_queue/3]). +-export([delete_queue/2, delete_queue/3]). +-export([put_message/3, put_message/4]). +-export([get_messages/2, get_messages/3]). +-export([peek_messages/2, peek_messages/3]). +-export([delete_message/4, delete_message/5]). +-export([clear_messages/2, clear_messages/3]). +-export([update_message/4, update_message/5]). %% Blob API --export([list_containers/1, list_containers/2, list_containers/3]). --export([create_container/2, create_container/3, create_container/4]). --export([delete_container/2, delete_container/3, delete_container/4]). --export([lease_container/3, lease_container/4, lease_container/5]). --export([list_blobs/2, list_blobs/3, list_blobs/4]). --export([put_block_blob/4, put_block_blob/5, put_block_blob/6]). --export([put_append_blob/3, put_append_blob/4, put_append_blob/5]). --export([append_block/4, append_block/5, append_block/6]). --export([put_page_blob/4, put_page_blob/5, put_page_blob/6]). --export([get_blob/3, get_blob/4, get_blob/5]). --export([snapshot_blob/3, snapshot_blob/4, snapshot_blob/5]). --export([copy_blob/4, copy_blob/5, copy_blob/6]). --export([delete_blob/3, delete_blob/4, delete_blob/5]). --export([put_block/5, put_block/6, put_block/7]). --export([put_block_list/4, put_block_list/5, put_block_list/6]). --export([get_block_list/3, get_block_list/4, get_block_list/5]). --export([acquire_blob_lease/4, acquire_blob_lease/5, acquire_blob_lease/7]). +-export([list_containers/1, list_containers/2]). +-export([create_container/2, create_container/3]). +-export([delete_container/2, delete_container/3]). +-export([lease_container/3, lease_container/4]). +-export([list_blobs/2, list_blobs/3]). +-export([put_block_blob/4, put_block_blob/5]). +-export([put_append_blob/3, put_append_blob/4]). +-export([append_block/4, append_block/5]). +-export([put_page_blob/4, put_page_blob/5]). +-export([get_blob/3, get_blob/4]). +-export([snapshot_blob/3, snapshot_blob/4]). +-export([copy_blob/4, copy_blob/5]). +-export([delete_blob/3, delete_blob/4]). +-export([put_block/5, put_block/6]). +-export([put_block_list/4, put_block_list/5]). +-export([get_block_list/3, get_block_list/4]). +-export([acquire_blob_lease/4, acquire_blob_lease/5, acquire_blob_lease/6]). %% Table API --export([list_tables/1, list_tables/3, new_table/2, delete_table/2]). +-export([list_tables/1, list_tables/2, new_table/2, delete_table/2]). %% Host API --export([get_host/3, get_host/4]). +-export([get_host/3]). -type init_opts() :: #{ account := string(), @@ -121,11 +120,7 @@ list_queues(State) -> list_queues(State, []). -spec list_queues(state(), common_opts()) -> enum_parse_result(queue()). -list_queues(State, Options) -> - list_queues(State, Options, ?gen_server_call_default_timeout). - --spec list_queues(state(), common_opts(), pos_integer()) -> enum_parse_result(queue()). -list_queues(State, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +list_queues(State, Options) when is_list(Options) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{params, [{comp, list}] ++ Options}], ReqContext = new_req_context(?queue_service, ReqOptions, State), @@ -139,11 +134,7 @@ set_queue_acl(State, Queue, SignedId=#signed_id{}) -> set_queue_acl(State, Queue, SignedId, []). -spec set_queue_acl(state(), string(), signed_id(), list(queue_acl_opts())) -> {ok, created}. -set_queue_acl(State, Queue, SignedId=#signed_id{}, Options) -> - set_queue_acl(State, Queue, SignedId, Options, ?gen_server_call_default_timeout). - --spec set_queue_acl(state(), string(), signed_id(), list(queue_acl_opts()), pos_integer()) -> {ok, created}. -set_queue_acl(State, Queue, SignedId=#signed_id{}, Options, Timeout) when is_list(Options); is_integer(Timeout)-> +set_queue_acl(State, Queue, SignedId=#signed_id{}, Options) when is_list(Options)-> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{method, put}, {path, string:to_lower(Queue)}, @@ -159,11 +150,7 @@ get_queue_acl(State, Queue) -> get_queue_acl(State, Queue, []). -spec get_queue_acl(state(), string(), list(queue_acl_opts())) -> {ok, no_acl} | {ok, signed_id()}. -get_queue_acl(State, Queue, Options) -> - get_queue_acl(State, Queue, Options, ?gen_server_call_default_timeout). - --spec get_queue_acl(state(), string(), list(queue_acl_opts()), pos_integer()) -> {ok, no_acl} | {ok, signed_id()}. -get_queue_acl(State, Queue, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +get_queue_acl(State, Queue, Options) when is_list(Options) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{path, string:to_lower(Queue)}, {params, [{comp, acl}] ++ Options}], @@ -175,9 +162,7 @@ get_queue_acl(State, Queue, Options, Timeout) when is_list(Options); is_integer( -spec create_queue(state(), string()) -> created_response() | already_created_response(). create_queue(State, Queue) -> create_queue(State, Queue, []). -create_queue(State, Queue, Options) -> - create_queue(State, Queue, Options, ?gen_server_call_default_timeout). -create_queue(State, Queue, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +create_queue(State, Queue, Options) when is_list(Options) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{method, put}, {path, string:to_lower(Queue)}, @@ -194,9 +179,7 @@ create_queue(State, Queue, Options, Timeout) when is_list(Options); is_integer(T delete_queue(State, Queue) -> delete_queue(State, Queue, []). -delete_queue(State, Queue, Options) -> - delete_queue(State, Queue, Options, ?gen_server_call_default_timeout). -delete_queue(State, Queue, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +delete_queue(State, Queue, Options) when is_list(Options) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{method, delete}, {path, string:to_lower(Queue)}, @@ -208,9 +191,7 @@ delete_queue(State, Queue, Options, Timeout) when is_list(Options); is_integer(T put_message(State, Queue, Message) -> put_message(State, Queue, Message, []). -put_message(State, Queue, Message, Options) -> - put_message(State, Queue, Message, Options, ?gen_server_call_default_timeout). -put_message(State, Queue, Message, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +put_message(State, Queue, Message, Options) when is_list(Options) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{method, post}, {path, lists:concat([string:to_lower(Queue), "/messages"])}, @@ -223,9 +204,7 @@ put_message(State, Queue, Message, Options, Timeout) when is_list(Options); is_i get_messages(State, Queue) -> get_messages(State, Queue, []). -get_messages(State, Queue, Options) -> - get_messages(State, Queue, Options, ?gen_server_call_default_timeout). -get_messages(State, Queue, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +get_messages(State, Queue, Options) when is_list(Options) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{path, string:to_lower(Queue) ++ "/messages"}, {params, Options}], @@ -236,9 +215,7 @@ get_messages(State, Queue, Options, Timeout) when is_list(Options); is_integer(T peek_messages(State, Queue) -> peek_messages(State, Queue, []). -peek_messages(State, Queue, Options) -> - peek_messages(State, Queue, Options, ?gen_server_call_default_timeout). -peek_messages(State, Queue, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +peek_messages(State, Queue, Options) when is_list(Options) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{path, string:to_lower(Queue) ++ "/messages"}, {params, [{peek_only, true}] ++ Options}], @@ -249,9 +226,7 @@ peek_messages(State, Queue, Options, Timeout) when is_list(Options); is_integer( delete_message(State, Queue, MessageId, PopReceipt) -> delete_message(State, Queue, MessageId, PopReceipt, []). -delete_message(State, Queue, MessageId, PopReceipt, Options) -> - delete_message(State, Queue, MessageId, PopReceipt, Options, ?gen_server_call_default_timeout). -delete_message(State, Queue, MessageId, PopReceipt, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +delete_message(State, Queue, MessageId, PopReceipt, Options) when is_list(Options) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{method, delete}, {path, lists:concat([string:to_lower(Queue), "/messages/", MessageId])}, @@ -263,9 +238,7 @@ delete_message(State, Queue, MessageId, PopReceipt, Options, Timeout) when is_li clear_messages(State, Queue) -> clear_messages(State, Queue, []). -clear_messages(State, Queue, Options) -> - clear_messages(State, Queue, Options, ?gen_server_call_default_timeout). -clear_messages(State, Queue, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +clear_messages(State, Queue, Options) when is_list(Options) -> ServiceContext = new_service_context(?queue_service, State), ReqOptions = [{method, delete}, {path, string:to_lower(Queue) ++ "/messages"}, @@ -277,9 +250,7 @@ clear_messages(State, Queue, Options, Timeout) when is_list(Options); is_integer update_message(State, Queue, UpdatedMessage=#queue_message{}, VisibilityTimeout) -> update_message(State, Queue, UpdatedMessage, VisibilityTimeout, []). -update_message(State, Queue, UpdatedMessage=#queue_message{}, VisibilityTimeout, Options) -> - update_message(State, Queue, UpdatedMessage, VisibilityTimeout, Options, ?gen_server_call_default_timeout). -update_message(State, Queue, UpdatedMessage=#queue_message{}, VisibilityTimeout, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +update_message(State, Queue, UpdatedMessage=#queue_message{}, VisibilityTimeout, Options) when is_list(Options) -> ServiceContext = new_service_context(?queue_service, State), Params = [{pop_receipt, UpdatedMessage#queue_message.pop_receipt}, {message_visibility_timeout, integer_to_list(VisibilityTimeout)}], @@ -298,9 +269,7 @@ update_message(State, Queue, UpdatedMessage=#queue_message{}, VisibilityTimeout, list_containers(State) -> list_containers(State, []). -list_containers(State, Options) -> - list_containers(State, Options, ?gen_server_call_default_timeout). -list_containers(State, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +list_containers(State, Options) when is_list(Options) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{params, [{comp, list}] ++ Options}], ReqContext = new_req_context(?blob_service, ReqOptions, State), @@ -315,9 +284,7 @@ list_containers(State, Options, Timeout) when is_list(Options); is_integer(Timeo create_container(State, Name) -> create_container(State, Name, []). -create_container(State, Name, Options) -> - create_container(State, Name, Options, ?gen_server_call_default_timeout). -create_container(State, Name, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +create_container(State, Name, Options) when is_list(Options) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{method, put}, {path, Name}, @@ -331,9 +298,7 @@ create_container(State, Name, Options, Timeout) when is_list(Options); is_intege delete_container(State, Name) -> delete_container(State, Name, []). -delete_container(State, Name, Options) -> - delete_container(State, Name, Options, ?gen_server_call_default_timeout). -delete_container(State, Name, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +delete_container(State, Name, Options) when is_list(Options) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{method, delete}, {path, Name}, @@ -345,9 +310,7 @@ delete_container(State, Name, Options, Timeout) when is_list(Options); is_intege put_block_blob(State, Container, Name, Data) -> put_block_blob(State, Container, Name, Data, []). -put_block_blob(State, Container, Name, Data, Options) -> - put_block_blob(State, Container, Name, Data, Options, ?gen_server_call_default_timeout). -put_block_blob(State, Container, Name, Data, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +put_block_blob(State, Container, Name, Data, Options) when is_list(Options) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{method, put}, {path, lists:concat([Container, "/", Name])}, @@ -364,9 +327,7 @@ put_block_blob(State, Container, Name, Data, Options, Timeout) when is_list(Opti put_page_blob(State, Container, Name, ContentLength) -> put_page_blob(State, Container, Name, ContentLength, []). -put_page_blob(State, Container, Name, ContentLength, Options) -> - put_page_blob(State, Container, Name, ContentLength, Options, ?gen_server_call_default_timeout). -put_page_blob(State, Container, Name, ContentLength, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +put_page_blob(State, Container, Name, ContentLength, Options) when is_list(Options) -> ServiceContext = new_service_context(?blob_service, State), Params = [{blob_type, page_blob}, {blob_content_length, ContentLength}], @@ -380,9 +341,7 @@ put_page_blob(State, Container, Name, ContentLength, Options, Timeout) when is_l put_append_blob(State, Container, Name) -> put_append_blob(State, Container, Name, []). -put_append_blob(State, Container, Name, Options) -> - put_append_blob(State, Container, Name, Options, ?gen_server_call_default_timeout). -put_append_blob(State, Container, Name, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +put_append_blob(State, Container, Name, Options) when is_list(Options) -> ServiceContext = new_service_context(?blob_service, State), Params = [{blob_type, append_blob}], ReqOptions = [{method, put}, @@ -399,9 +358,7 @@ put_append_blob(State, Container, Name, Options, Timeout) when is_list(Options); append_block(State, Container, Name, Data) -> append_block(State, Container, Name, Data, []). -append_block(State, Container, Name, Data, Options) -> - append_block(State, Container, Name, Data, Options, ?gen_server_call_default_timeout). -append_block(State, Container, Name, Data, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +append_block(State, Container, Name, Data, Options) when is_list(Options) -> ServiceContext = new_service_context(?blob_service, State), Params = [{comp, "appendblock"}], ReqOptions = [{method, put}, @@ -415,9 +372,7 @@ append_block(State, Container, Name, Data, Options, Timeout) when is_list(Option list_blobs(State, Container) -> list_blobs(State, Container, []). -list_blobs(State, Container, Options) -> - list_blobs(State, Container, Options, ?gen_server_call_default_timeout). -list_blobs(State, Container, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +list_blobs(State, Container, Options) when is_list(Options) -> ServiceContext = new_service_context(?blob_service, State), Params = [{comp, list}, {res_type, container}], @@ -431,9 +386,7 @@ list_blobs(State, Container, Options, Timeout) when is_list(Options); is_integer get_blob(State, Container, Blob) -> get_blob(State, Container, Blob, []). -get_blob(State, Container, Blob, Options) -> - get_blob(State, Container, Blob, Options, ?gen_server_call_default_timeout). -get_blob(State, Container, Blob, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +get_blob(State, Container, Blob, Options) when is_list(Options) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{path, lists:concat([Container, "/", Blob])}, {params, Options}], @@ -450,9 +403,7 @@ get_blob(State, Container, Blob, Options, Timeout) when is_list(Options); is_int snapshot_blob(State, Container, Blob) -> snapshot_blob(State, Container, Blob, []). -snapshot_blob(State, Container, Blob, Options) -> - snapshot_blob(State, Container, Blob, Options, ?gen_server_call_default_timeout). -snapshot_blob(State, Container, Blob, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +snapshot_blob(State, Container, Blob, Options) when is_list(Options) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{method, put}, {path, lists:concat([Container, "/", Blob])}, @@ -464,9 +415,7 @@ snapshot_blob(State, Container, Blob, Options, Timeout) when is_list(Options); i copy_blob(State, Container, Blob, Source) -> copy_blob(State, Container, Blob, Source, []). -copy_blob(State, Container, Blob, Source, Options) -> - copy_blob(State, Container, Blob, Source, Options, ?gen_server_call_default_timeout). -copy_blob(State, Container, Blob, Source, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +copy_blob(State, Container, Blob, Source, Options) when is_list(Options) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{method, put}, {path, lists:concat([Container, "/", Blob])}, @@ -478,9 +427,7 @@ copy_blob(State, Container, Blob, Source, Options, Timeout) when is_list(Options delete_blob(State, Container, Blob) -> delete_blob(State, Container, Blob, []). -delete_blob(State, Container, Blob, Options) -> - delete_blob(State, Container, Blob, Options, ?gen_server_call_default_timeout). -delete_blob(State, Container, Blob, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +delete_blob(State, Container, Blob, Options) when is_list(Options) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{method, delete}, {path, lists:concat([Container, "/", Blob])}, @@ -492,9 +439,7 @@ delete_blob(State, Container, Blob, Options, Timeout) when is_list(Options); is_ put_block(State, Container, Blob, BlockId, BlockContent) -> put_block(State, Container, Blob, BlockId, BlockContent, []). -put_block(State, Container, Blob, BlockId, BlockContent, Options) -> - put_block(State, Container, Blob, BlockId, BlockContent, Options, ?gen_server_call_default_timeout). -put_block(State, Container, Blob, BlockId, BlockContent, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +put_block(State, Container, Blob, BlockId, BlockContent, Options) when is_list(Options) -> ServiceContext = new_service_context(?blob_service, State), Params = [{comp, block}, {blob_block_id, uri_string:quote(base64:encode_to_string(BlockId))}], @@ -509,9 +454,7 @@ put_block(State, Container, Blob, BlockId, BlockContent, Options, Timeout) when put_block_list(State, Container, Blob, BlockRefs) -> put_block_list(State, Container, Blob, BlockRefs, []). -put_block_list(State, Container, Blob, BlockRefs, Options) -> - put_block_list(State, Container, Blob, BlockRefs, Options, ?gen_server_call_default_timeout). -put_block_list(State, Container, Blob, BlockRefs, Options0, Timeout) when is_list(Options0); is_integer(Timeout) -> +put_block_list(State, Container, Blob, BlockRefs, Options0) when is_list(Options0) -> ServiceContext = new_service_context(?blob_service, State), {ExtraReqOpts, Options} = proplist_take(req_opts, Options0, []), ReqOptions = [{method, put}, @@ -525,9 +468,7 @@ put_block_list(State, Container, Blob, BlockRefs, Options0, Timeout) when is_lis get_block_list(State, Container, Blob) -> get_block_list(State, Container, Blob, []). -get_block_list(State, Container, Blob, Options) -> - get_block_list(State, Container, Blob, Options, ?gen_server_call_default_timeout). -get_block_list(State, Container, Blob, Options, Timeout) when is_list(Options); is_integer(Timeout) -> +get_block_list(State, Container, Blob, Options) when is_list(Options) -> ServiceContext = new_service_context(?blob_service, State), ReqOptions = [{path, lists:concat([Container, "/", Blob])}, {params, [{comp, "blocklist"}] ++ Options}], @@ -540,8 +481,8 @@ get_block_list(State, Container, Blob, Options, Timeout) when is_list(Options); acquire_blob_lease(State, Container, Blob, Duration) -> acquire_blob_lease(State, Container, Blob, Duration, []). acquire_blob_lease(State, Container, Blob, Duration, Options) -> - acquire_blob_lease(State, Container, Blob, "", Duration, Options, ?gen_server_call_default_timeout). -acquire_blob_lease(State, Container, Blob, ProposedId, Duration, Options, Timeout) when is_list(Options); is_integer(Timeout) -> + acquire_blob_lease(State, Container, Blob, "", Duration, Options). +acquire_blob_lease(State, Container, Blob, ProposedId, Duration, Options) when is_list(Options) -> ServiceContext = new_service_context(?blob_service, State), Params = [{lease_action, acquire}, @@ -559,9 +500,7 @@ acquire_blob_lease(State, Container, Blob, ProposedId, Duration, Options, Timeou lease_container(State, Name, Mode) -> lease_container(State, Name, Mode, []). -lease_container(State, Name, Mode, Options) -> - lease_container(State, Name, Mode, Options, ?gen_server_call_default_timeout). -lease_container(State, Name, Mode, Options, Timeout) when is_atom(Mode); is_list(Options); is_integer(Timeout) -> +lease_container(State, Name, Mode, Options) when is_atom(Mode), is_list(Options) -> ServiceContext = new_service_context(?blob_service, State), Params = [{comp, lease}, {res_type, container}, @@ -579,8 +518,8 @@ lease_container(State, Name, Mode, Options, Timeout) when is_atom(Mode); is_list %%==================================================================== list_tables(State) -> - list_tables(State, [], ?gen_server_call_default_timeout). -list_tables(State, Options, Timeout) when is_list(Options); is_integer(Timeout)-> + list_tables(State, []). +list_tables(State, Options) when is_list(Options) -> ServiceContext = new_service_context(?table_service, State), ReqOptions = [{path, "Tables"}, {params, Options}], @@ -738,19 +677,19 @@ build_uri_base(Service, #state{account = Account}) -> get_host(Service, Account) -> lists:concat([Account, ".", erlang:atom_to_list(Service), ".core.windows.net"]). + get_host(State, Service, []) -> Domain = ".core.windows.net", - Timeout = ?gen_server_call_default_timeout, - get_host(State, Service, Domain, Timeout); + do_get_host(State, Service, Domain); get_host(State, Service, Timeout) when is_integer(Timeout) -> Domain = ".core.windows.net", - get_host(State, Service, Domain, Timeout); + do_get_host(State, Service, Domain); get_host(State, Service, Domain) when is_list(Domain) -> - Timeout = ?gen_server_call_default_timeout, - get_host(State, Service, Domain, Timeout). -get_host(State, Service, Domain, Timeout) when is_list(Domain), is_integer(Timeout) -> + do_get_host(State, Service, Domain). + +do_get_host(State, Service, Domain) when is_list(Domain), is_atom(Service) -> Account = State#state.account, - lists:concat([Account, ".", erlang:atom_to_list(Service), Domain]). + lists:concat([Account, ".", atom_to_list(Service), Domain]). -spec canonicalize_headers([string()]) -> string(). canonicalize_headers(Headers) -> From 4f786651a5c3a9989ea8facbb30159026243aa3a Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Thu, 6 Jun 2024 16:19:18 -0300 Subject: [PATCH 15/17] refactor: remove unused argument --- src/erlazure.erl | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/erlazure.erl b/src/erlazure.erl index afa1372..611d431 100644 --- a/src/erlazure.erl +++ b/src/erlazure.erl @@ -143,7 +143,7 @@ set_queue_acl(State, Queue, SignedId=#signed_id{}, Options) when is_list(Options ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_no_content, created). + return_response(Code, Body, ?http_no_content, created). -spec get_queue_acl(state(), string()) -> {ok, no_acl} | {ok, signed_id()}. get_queue_acl(State, Queue) -> @@ -187,7 +187,7 @@ delete_queue(State, Queue, Options) when is_list(Options) -> ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_no_content, deleted). + return_response(Code, Body, ?http_no_content, deleted). put_message(State, Queue, Message) -> put_message(State, Queue, Message, []). @@ -200,7 +200,7 @@ put_message(State, Queue, Message, Options) when is_list(Options) -> ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_created, created). + return_response(Code, Body, ?http_created, created). get_messages(State, Queue) -> get_messages(State, Queue, []). @@ -234,7 +234,7 @@ delete_message(State, Queue, MessageId, PopReceipt, Options) when is_list(Option ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_no_content, deleted). + return_response(Code, Body, ?http_no_content, deleted). clear_messages(State, Queue) -> clear_messages(State, Queue, []). @@ -246,7 +246,7 @@ clear_messages(State, Queue, Options) when is_list(Options) -> ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_no_content, deleted). + return_response(Code, Body, ?http_no_content, deleted). update_message(State, Queue, UpdatedMessage=#queue_message{}, VisibilityTimeout) -> update_message(State, Queue, UpdatedMessage, VisibilityTimeout, []). @@ -261,7 +261,7 @@ update_message(State, Queue, UpdatedMessage=#queue_message{}, VisibilityTimeout, ReqContext = new_req_context(?queue_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_no_content, updated). + return_response(Code, Body, ?http_no_content, updated). %%==================================================================== %% Blob @@ -306,7 +306,7 @@ delete_container(State, Name, Options) when is_list(Options) -> RequestContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, RequestContext), - return_response(Code, Body, State, ?http_accepted, deleted). + return_response(Code, Body, ?http_accepted, deleted). put_block_blob(State, Container, Name, Data) -> put_block_blob(State, Container, Name, Data, []). @@ -323,7 +323,7 @@ put_block_blob(State, Container, Name, Data, Options) when is_list(Options) -> end, {Code, Body} = execute_request(ServiceContext, ReqContext1), - return_response(Code, Body, State, ?http_created, created). + return_response(Code, Body, ?http_created, created). put_page_blob(State, Container, Name, ContentLength) -> put_page_blob(State, Container, Name, ContentLength, []). @@ -337,7 +337,7 @@ put_page_blob(State, Container, Name, ContentLength, Options) when is_list(Optio ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_created, created). + return_response(Code, Body, ?http_created, created). put_append_blob(State, Container, Name) -> put_append_blob(State, Container, Name, []). @@ -354,7 +354,7 @@ put_append_blob(State, Container, Name, Options) when is_list(Options) -> end, {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_created, created). + return_response(Code, Body, ?http_created, created). append_block(State, Container, Name, Data) -> append_block(State, Container, Name, Data, []). @@ -368,7 +368,7 @@ append_block(State, Container, Name, Data, Options) when is_list(Options) -> ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_created, appended). + return_response(Code, Body, ?http_created, appended). list_blobs(State, Container) -> list_blobs(State, Container, []). @@ -411,7 +411,7 @@ snapshot_blob(State, Container, Blob, Options) when is_list(Options) -> ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_created, created). + return_response(Code, Body, ?http_created, created). copy_blob(State, Container, Blob, Source) -> copy_blob(State, Container, Blob, Source, []). @@ -423,7 +423,7 @@ copy_blob(State, Container, Blob, Source, Options) when is_list(Options) -> ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_accepted, created). + return_response(Code, Body, ?http_accepted, created). delete_blob(State, Container, Blob) -> delete_blob(State, Container, Blob, []). @@ -435,7 +435,7 @@ delete_blob(State, Container, Blob, Options) when is_list(Options) -> ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_accepted, deleted). + return_response(Code, Body, ?http_accepted, deleted). put_block(State, Container, Blob, BlockId, BlockContent) -> put_block(State, Container, Blob, BlockId, BlockContent, []). @@ -450,7 +450,7 @@ put_block(State, Container, Blob, BlockId, BlockContent, Options) when is_list(O ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_created, created). + return_response(Code, Body, ?http_created, created). put_block_list(State, Container, Blob, BlockRefs) -> put_block_list(State, Container, Blob, BlockRefs, []). @@ -464,7 +464,7 @@ put_block_list(State, Container, Blob, BlockRefs, Options0) when is_list(Options ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_created, created). + return_response(Code, Body, ?http_created, created). get_block_list(State, Container, Blob) -> get_block_list(State, Container, Blob, []). @@ -496,7 +496,7 @@ acquire_blob_lease(State, Container, Blob, ProposedId, Duration, Options) when i ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_created, acquired). + return_response(Code, Body, ?http_created, acquired). lease_container(State, Name, Mode) -> lease_container(State, Name, Mode, []). @@ -511,7 +511,7 @@ lease_container(State, Name, Mode, Options) when is_atom(Mode), is_list(Options) ReqContext = new_req_context(?blob_service, ReqOptions, State), {Code, Body} = execute_request(ServiceContext, ReqContext), - return_response(Code, Body, State, ?http_accepted, deleted). + return_response(Code, Body, ?http_accepted, deleted). %%==================================================================== %% Table @@ -539,7 +539,7 @@ new_table(State, TableName) when is_binary(TableName) -> ReqContext = new_req_context(?table_service, ReqOptions, State), ReqContext1 = ReqContext#req_context{ content_type = ?json_content_type }, {Code, Body} = execute_request(ServiceContext, ReqContext1), - return_response(Code, Body, State, ?http_created, created). + return_response(Code, Body, ?http_created, created). delete_table(State, TableName) when is_binary(TableName) -> delete_table(State, binary_to_list(TableName)); @@ -839,7 +839,7 @@ get_req_common_param_specs() -> #param_spec{ id = ?req_param_include, type = uri, name = "include" }, #param_spec{ id = ?req_param_marker, type = uri, name = "marker" }]. -return_response(Code, Body, State, ExpectedResponseCode, SuccessAtom) -> +return_response(Code, Body, ExpectedResponseCode, SuccessAtom) -> case Code of ExpectedResponseCode -> {ok, SuccessAtom}; From fb10fff6030bde2c539f227f4ca3b1ebd6ab2b02 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Thu, 6 Jun 2024 17:52:48 -0300 Subject: [PATCH 16/17] fix: `execute_request` typespec return type --- src/erlazure.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/erlazure.erl b/src/erlazure.erl index 611d431..414eb17 100644 --- a/src/erlazure.erl +++ b/src/erlazure.erl @@ -556,7 +556,7 @@ delete_table(State, TableName) when is_list(TableName) -> %% Private functions %%-------------------------------------------------------------------- --spec execute_request(service_context(), req_context()) -> {non_neg_integer(), binary()}. +-spec execute_request(service_context(), req_context()) -> {non_neg_integer(), binary()} | {error, any()}. execute_request(ServiceContext = #service_context{}, ReqContext = #req_context{}) -> DateHeader = if (ServiceContext#service_context.service =:= ?table_service) -> {"Date", httpd_util:rfc1123_date()}; From eac4bb8ea0d06668aec4f472b548fcc7e84740ab Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Thu, 6 Jun 2024 18:04:05 -0300 Subject: [PATCH 17/17] fix: `erlazure_blob:parse_container_list` typespec types --- src/erlazure_blob.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/erlazure_blob.erl b/src/erlazure_blob.erl index 61008d7..681b41b 100644 --- a/src/erlazure_blob.erl +++ b/src/erlazure_blob.erl @@ -38,7 +38,7 @@ -export([parse_container_list/1, parse_blob_list/1, get_request_body/1, parse_block_list/1, get_request_param_specs/0, parse_blob_response/1]). --spec parse_container_list(string()) -> {error, bad_response} | {[blob_container()], list()}. +-spec parse_container_list(string() | binary()) -> {error, bad_response} | {ok, {[blob_container()], list()}}. parse_container_list(Response) -> ParserSpec = #enum_parser_spec { rootKey = 'Containers', elementKey = 'Container',