diff --git a/CHANGELOG.md b/CHANGELOG.md index a019934..6eb426d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [2.3.2] (2025-09-17) + +### Changed + +* Code janitoring to be compliant with elixir 1.18 + ## [2.3.2] (2023-08-04) ### Changed diff --git a/lib/ewebmachine/builder.handlers.ex b/lib/ewebmachine/builder.handlers.ex index 3a63974..833ad0b 100644 --- a/lib/ewebmachine/builder.handlers.ex +++ b/lib/ewebmachine/builder.handlers.ex @@ -5,19 +5,19 @@ defmodule Ewebmachine.Builder.Handlers do you an `:add_handler` local function plug which adds to the conn the locally defined ewebmachine handlers (see `Ewebmachine.Handlers`). - So : + So : - Construct your automate decision handler through multiple `:add_handler` plugs - Pipe the plug `Ewebmachine.Plug.Run` to run the HTTP automate which - will call these handlers to take decisions. + will call these handlers to take decisions. - Pipe the plug `Ewebmachine.Plug.Send` to send and halt any conn previsously passed through an automate run. - + To define handlers, use the following helpers : - the handler specific macros (like `Ewebmachine.Builder.Handlers.resource_exists/1`) - the macro `defh/2` to define any helpers, usefull for body - producing handlers or to have multiple function clauses + producing handlers or to have multiple function clauses - in handler implementation `conn` and `state` binding are available - the response of the handler implementation is wrapped, so that returning `:my_response` is the same as returning `{:my_response,conn,state}` @@ -25,7 +25,7 @@ defmodule Ewebmachine.Builder.Handlers do Below a full example : ``` - defmodule MyJSONApi do + defmodule MyJSONApi do use Ewebmachine.Builder.Handlers plug :cors plug :add_handlers, init: %{} @@ -33,11 +33,11 @@ defmodule Ewebmachine.Builder.Handlers do content_types_provided do: ["application/json": :to_json] defh to_json, do: Poison.encode!(state[:json_obj]) - defp cors(conn,_), do: + defp cors(conn,_), do: put_resp_header(conn,"Access-Control-Allow-Origin","*") end - defmodule GetUser do + defmodule GetUser do use Ewebmachine.Builder.Handlers plug MyJSONApi plug :add_handlers @@ -46,7 +46,7 @@ defmodule Ewebmachine.Builder.Handlers do resource_exists do: pass( !is_nil(user=DB.User.get(conn.params["q"])), json_obj: user) end - defmodule GetOrder do + defmodule GetOrder do use Ewebmachine.Builder.Handlers plug MyJSONApi plug :add_handlers @@ -58,7 +58,7 @@ defmodule Ewebmachine.Builder.Handlers do defmodule API do use Plug.Router - plug :match + plug :match plug :dispatch get "/get/user", do: GetUser.call(conn,[]) @@ -100,27 +100,50 @@ defmodule Ewebmachine.Builder.Handlers do defp sig_to_sigwhen({name,_,_}), do: {name,[quote(do: _),quote(do: _)],true} defp handler_quote(name,body,guard,conn_match,state_match) do + conn_match = case var_in_patterns?(conn_match, :conn) do + true -> conn_match + false -> quote(do: unquote(conn_match) = var!(conn)) + end + + state_match = case var_in_patterns?(state_match, :state) do + true -> state_match + false -> quote(do: unquote(state_match) = var!(state)) + end + quote do @resource_handlers Map.put(@resource_handlers,unquote(name),__MODULE__) - def unquote(name)(unquote(conn_match)=var!(conn),unquote(state_match)=var!(state)) when unquote(guard) do + def unquote(name)(unquote(conn_match), unquote(state_match)) when unquote(guard) do res = unquote(body) wrap_response(res,var!(conn),var!(state)) end - end + end end defp handler_quote(name,body) do handler_quote(name,body,true,quote(do: _),quote(do: _)) end + defp var_in_patterns?(ast, name) do + case ast do + {:=, meta, [pat1, pat2]} when is_list(meta) -> + var_in_patterns?(pat1, name) or var_in_patterns?(pat2, name) + + {^name, meta, context} when is_list(meta) and is_atom(context) -> + true + + _ -> + false + end + end + @doc """ define a resource handler function as described at `Ewebmachine.Handlers`. - + Since there is a specific macro in this module for each handler, - this macro is useful : + this macro is useful : - to define body producing and body processing handlers (the one - referenced in the response of `Ewebmachine.Handlers.content_types_provided/2` or + referenced in the response of `Ewebmachine.Handlers.content_types_provided/2` or `Ewebmachine.Handlers.content_types_accepted/2`) - to explicitly take the `conn` and the `state` parameter, which allows you to add guards and pattern matching for instance to @@ -142,13 +165,13 @@ defmodule Ewebmachine.Builder.Handlers do end for resource_fun_name<-@resource_fun_names do - Module.eval_quoted(Ewebmachine.Builder.Handlers, quote do + Code.eval_quoted(quote do @doc "see `Ewebmachine.Handlers.#{unquote(resource_fun_name)}/2`" defmacro unquote(resource_fun_name)(do_block) do name = unquote(resource_fun_name) handler_quote(name,do_block[:do]) end - end) + end, [], __ENV__) end @doc false @@ -171,7 +194,7 @@ defmodule Ewebmachine.Builder.Handlers do {true,conn,%{id: "arnaud", current_user: %User{id: "arnaud"}}} """ defmacro pass(response,update_state) do - quote do + quote do {unquote(response),var!(conn),Enum.into(unquote(update_state),var!(state))} end end diff --git a/lib/ewebmachine/core.ex b/lib/ewebmachine/core.ex index ee41c2a..5682fbd 100644 --- a/lib/ewebmachine/core.ex +++ b/lib/ewebmachine/core.ex @@ -25,7 +25,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 503) end end - + ## "see `v3b13/2`" decision v3b13b(conn, state) do {reply, conn, state} = resource_call(conn, state, :service_available) @@ -35,7 +35,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 503) end end - + ## "Known method?" decision v3b12(conn, state) do {methods, conn, state} = resource_call(conn, state, :known_methods) @@ -45,7 +45,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 501) end end - + ## "URI too long?" decision v3b11(conn, state) do {reply, conn, state} = resource_call(conn, state, :uri_too_long) @@ -55,7 +55,7 @@ defmodule Ewebmachine.Core do v3b10(conn, state) end end - + ## "Method allowed?" decision v3b10(conn, state) do {methods, conn, state} = resource_call(conn, state, :allowed_methods) @@ -66,7 +66,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 405) end end - + ## "Content-MD5 present?" decision v3b9(conn, state) do if get_header_val(conn, "content-md5") do @@ -75,7 +75,7 @@ defmodule Ewebmachine.Core do v3b9b(conn, state) end end - + ## "Content-MD5 valid?" decision v3b9a(conn, state) do case resource_call(conn, state, :validate_content_checksum) do @@ -97,7 +97,7 @@ defmodule Ewebmachine.Core do v3b9b(conn, state) end end - + ## "Malformed?" decision v3b9b(conn, state) do case resource_call(conn, state, :malformed_request) do @@ -107,7 +107,7 @@ defmodule Ewebmachine.Core do v3b8(conn, state) end end - + ## "Authorized?" decision v3b8(conn, state) do case resource_call(conn, state, :is_authorized) do @@ -118,7 +118,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 401) end end - + ## "Forbidden?" decision v3b7(conn, state) do case resource_call(conn, state, :forbidden) do @@ -128,7 +128,7 @@ defmodule Ewebmachine.Core do v3b6(conn, state) end end - + ## "Okay Content-* Headers?" decision v3b6(conn, state) do {reply, conn, state} = resource_call(conn, state, :valid_content_headers) @@ -138,7 +138,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 501) end end - + ## "Known Content-Type?" decision v3b5(conn, state) do {reply, conn, state} = resource_call(conn, state, :known_content_type) @@ -148,7 +148,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 415) end end - + ## "Req Entity Too Large?" decision v3b4(conn, state) do {reply, conn, state} = resource_call(conn, state, :valid_entity_length) @@ -158,7 +158,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 413) end end - + ## "OPTIONS?" decision v3b3(conn, state) do case method(conn) do @@ -196,7 +196,7 @@ defmodule Ewebmachine.Core do v3d4(conn, state) end end - + ## "Accept-Language exists?" decision v3d4(conn, state) do if get_header_val(conn, "accept-language") do @@ -205,7 +205,7 @@ defmodule Ewebmachine.Core do v3e5(conn, state) end end - + ## "Acceptable Language available? %% WMACH-46 (do this as proper conneg)" decision v3d5(conn, state) do {reply, conn, state} = resource_call(conn, state, :language_available) @@ -215,7 +215,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 406) end end - + ## "Accept-Charset exists?" decision v3e5(conn, state) do case get_header_val(conn, "accept-charset") do @@ -228,7 +228,7 @@ defmodule Ewebmachine.Core do _ -> v3e6(conn, state) end end - + ## "Acceptable Charset available?" decision v3e6(conn, state) do accept = get_header_val(conn, "accept-charset") @@ -238,7 +238,7 @@ defmodule Ewebmachine.Core do _ -> v3f6(conn, state) end end - + ## Accept-Encoding exists? ## also, set content-type header here, now that charset is chosen) decision v3f6(conn, state) do @@ -256,7 +256,7 @@ defmodule Ewebmachine.Core do _ -> v3f7(conn, state) end end - + ## "Acceptable encoding available?" decision v3f7(conn, state) do accept = get_header_val(conn, "accept-encoding") @@ -266,7 +266,7 @@ defmodule Ewebmachine.Core do _ -> v3g7(conn, state) end end - + ## "Resource exists?" decision v3g7(conn, state) do ## his is the first place after all conneg, so set Vary here @@ -284,7 +284,7 @@ defmodule Ewebmachine.Core do v3h7(conn, state) end end - + ## "If-Match exists?" decision v3g8(conn, state) do if get_header_val(conn, "if-match") do @@ -293,7 +293,7 @@ defmodule Ewebmachine.Core do v3h10(conn, state) end end - + ## "If-Match: * exists" decision v3g9(conn, state) do if get_header_val(conn, "if-match") == "*" do @@ -302,7 +302,7 @@ defmodule Ewebmachine.Core do v3g11(conn, state) end end - + ## "ETag in If-Match" decision v3g11(conn, state) do etags = split_quoted_strings(get_header_val(conn, "if-match")) @@ -313,7 +313,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 412) end end - + ## "If-Match exists" decision v3h7(conn, state) do if get_header_val(conn, "if-match") do @@ -322,7 +322,7 @@ defmodule Ewebmachine.Core do v3i7(conn, state) end end - + ## "If-unmodified-since exists?" decision v3h10(conn, state) do if get_header_val(conn, "if-unmodified-since") do @@ -331,7 +331,7 @@ defmodule Ewebmachine.Core do v3i12(conn, state) end end - + ## "I-UM-S is valid date?" decision v3h11(conn, state) do iums_date = get_header_val(conn, "if-unmodified-since") @@ -341,7 +341,7 @@ defmodule Ewebmachine.Core do v3h12(conn, state) end end - + ## "Last-Modified > I-UM-S?" decision v3h12(conn, state) do req_date = get_header_val(conn, "if-unmodified-since") @@ -353,7 +353,7 @@ defmodule Ewebmachine.Core do v3i12(conn, state) end end - + ## "Moved permanently? (apply PUT to different URI)" decision v3i4(conn, state) do {reply, conn, state} = resource_call(conn, state, :moved_permanently) @@ -365,7 +365,7 @@ defmodule Ewebmachine.Core do v3p3(conn, state) end end - + ## "PUT?" decision v3i7(conn, state) do if method(conn) == "PUT" do @@ -374,7 +374,7 @@ defmodule Ewebmachine.Core do v3k7(conn, state) end end - + ## "If-none-match exists?" decision v3i12(conn, state) do if get_header_val(conn, "if-none-match") do @@ -383,7 +383,7 @@ defmodule Ewebmachine.Core do v3l13(conn, state) end end - + ## "If-None-Match: * exists?" decision v3i13(conn, state) do if get_header_val(conn, "if-none-match") == "*" do @@ -392,7 +392,7 @@ defmodule Ewebmachine.Core do v3k13(conn, state) end end - + ## "GET or HEAD?" decision v3j18(conn, state) do if method(conn) in ["GET","HEAD"] do @@ -401,7 +401,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 412) end end - + ## "Moved permanently?" decision v3k5(conn, state) do case resource_call(conn, state, :moved_permanently) do @@ -412,7 +412,7 @@ defmodule Ewebmachine.Core do v3l5(conn, state) end end - + ## "Previously existed?" decision v3k7(conn, state) do {reply, conn, state} = resource_call(conn, state, :previously_existed) @@ -422,7 +422,7 @@ defmodule Ewebmachine.Core do v3l7(conn, state) end end - + ## "Etag in if-none-match?" decision v3k13(conn, state) do etags = split_quoted_strings(get_header_val(conn, "if-none-match")) @@ -436,7 +436,7 @@ defmodule Ewebmachine.Core do v3l13(conn, state) end end - + ## "Moved temporarily?" decision v3l5(conn, state) do case resource_call(conn, state, :moved_temporarily) do @@ -447,7 +447,7 @@ defmodule Ewebmachine.Core do v3m5(conn, state) end end - + ## "POST?" decision v3l7(conn, state) do if method(conn) == "POST" do @@ -456,7 +456,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 404) end end - + ## "IMS exists?" decision v3l13(conn, state) do if get_header_val(conn, "if-modified-since") do @@ -465,7 +465,7 @@ defmodule Ewebmachine.Core do v3m16(conn, state) end end - + ## "IMS is valid date?" decision v3l14(conn, state) do ims_date = get_header_val(conn, "if-modified-since") @@ -475,7 +475,7 @@ defmodule Ewebmachine.Core do v3l15(conn, state) end end - + ## "IMS > Now?" decision v3l15(conn, state) do now_date_time = :calendar.universal_time @@ -487,7 +487,7 @@ defmodule Ewebmachine.Core do v3l17(conn, state) end end - + ## "Last-Modified > IMS?" decision v3l17(conn, state) do req_date = get_header_val(conn, "if-modified-since") @@ -499,7 +499,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 304) end end - + ## "POST?" decision v3m5(conn, state) do if method(conn) == "POST" do @@ -508,7 +508,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 410) end end - + ## "Server allows POST to missing resource?" decision v3m7(conn, state) do {amp, conn, state} = resource_call(conn, state, :allow_missing_post) @@ -518,7 +518,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 404) end end - + ## "DELETE?" decision v3m16(conn, state) do if method(conn) == "DELETE" do @@ -527,7 +527,7 @@ defmodule Ewebmachine.Core do v3n16(conn, state) end end - + ## "DELETE enacted immediately? Also where DELETE is forced" decision v3m20(conn, state) do {result, conn, state} = resource_call(conn, state, :delete_resource) @@ -540,7 +540,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 500) end end - + decision v3m20b(conn, state) do {reply, conn, state} = resource_call(conn, state, :delete_completed) if reply do @@ -549,7 +549,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 202) end end - + ## "Server allows POST to missing resource?" decision v3n5(conn, state) do {reply, conn, state} = resource_call(conn, state, :allow_missing_post) @@ -559,7 +559,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 410) end end - + ## "Redirect?" decision v3n11(conn, state) do {reply, conn, state} = resource_call(conn, state, :post_is_create) @@ -569,23 +569,23 @@ defmodule Ewebmachine.Core do if is_nil(new_path), do: raise "post_is_create w/o create_path" if !is_binary(new_path), do: raise "create_path not a string (#{inspect new_path})" - + {base_uri, conn, state} = resource_call(conn, state, :base_uri) base_uri = if String.last(base_uri) == "/" do - String.slice(base_uri,0..-2) + String.slice(base_uri, 0..-2//1) else - base_uri + base_uri end new_path = if !match?("/"<>_, new_path) do - "#{path(conn)}/#{new_path}" + "#{path(conn)}/#{new_path}" else - new_path + new_path end - + conn = if !get_resp_header(conn, "location") do set_resp_header(conn, "location", base_uri <> new_path) else - conn + conn end redirect_helper(conn, state) else @@ -594,7 +594,7 @@ defmodule Ewebmachine.Core do redirect_helper(conn, state) end end - + ## "POST?" decision v3n16(conn, state) do if method(conn) == "POST" do @@ -603,18 +603,18 @@ defmodule Ewebmachine.Core do v3o16(conn, state) end end - + ## "Conflict?" decision v3o14(conn, state) do case resource_call(conn, state, :is_conflict) do {true, conn, state} -> respond(conn, state, 409) - {_, conn, state} -> + {_, conn, state} -> {_, conn, state} = accept_helper(conn, state) v3p11(conn, state) end end - + ## "PUT?" decision v3o16(conn, state) do if method(conn) == "PUT" do @@ -623,7 +623,7 @@ defmodule Ewebmachine.Core do v3o18(conn, state) end end - + ## Multiple representations? ## also where body generation for GET and HEAD is done) decision v3o18(conn, state) do @@ -647,7 +647,7 @@ defmodule Ewebmachine.Core do v3o18b(conn, state) end end - + decision v3o18b(conn, state) do {mc, conn, state} = resource_call(conn, state, :multiple_choices) if mc do @@ -656,7 +656,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 200) end end - + ## "Response includes an entity?" decision v3o20(conn, state) do if has_resp_body(conn) do @@ -665,7 +665,7 @@ defmodule Ewebmachine.Core do respond(conn, state, 204) end end - + ## "Conflict?" decision v3p3(conn, state) do {reply, conn, state} = resource_call(conn, state, :is_conflict) @@ -676,7 +676,7 @@ defmodule Ewebmachine.Core do v3p11(conn, state) end end - + ## "New resource? (at this point boils down to \"has location header\")" decision v3p11(conn, state) do if get_resp_header(conn, "location") do @@ -685,7 +685,7 @@ defmodule Ewebmachine.Core do v3o20(conn, state) end end - + ### ### Helpers ### @@ -707,13 +707,13 @@ defmodule Ewebmachine.Core do {variances, conn, state} = resource_call(conn, state, :variances) {accept ++ accept_enc ++ accept_char ++ variances, conn, state} end - + def accept_helper(conn, state) do ct = get_header_val(conn, "content-type") || "application/octet-stream" {_, _, h_params} = ct = normalize_mtype(ct) conn = set_metadata(conn, :mediaparams, h_params) {ct_accepted, conn, state} = resource_call(conn, state, :content_types_accepted) - + mtfun = Enum.find_value(ct_accepted, fn {accept,f} -> fuzzy_mt_match(ct,normalize_mtype(accept)) && f end) @@ -726,7 +726,7 @@ defmodule Ewebmachine.Core do throw {:halt, conn} end end - + def encode_body_if_set(conn, state) do if has_resp_body(conn) do body = resp_body(conn) @@ -737,7 +737,7 @@ defmodule Ewebmachine.Core do {:ok, conn, state} end end - + def encode_body(conn, state, body) do chosen_cset = get_metadata(conn, :'chosen-charset') {charsetter, conn, state} = case resource_call(conn, state, :charsets_provided) do @@ -761,7 +761,7 @@ defmodule Ewebmachine.Core do end {body, conn, state} end - + def choose_encoding(conn, state, acc_enc_hdr) do {enc_provided, conn, state} = resource_call(conn, state, :encodings_provided) encs = for {enc, _} <- enc_provided, do: to_string(enc) @@ -774,7 +774,7 @@ defmodule Ewebmachine.Core do conn = set_metadata(conn, :'content-encoding', chosen_enc) {chosen_enc, conn, state} end - + def choose_charset(conn, state, acc_char_hdr) do case resource_call(conn, state, :charsets_provided) do {:no_charset, conn, state} -> @@ -802,13 +802,13 @@ defmodule Ewebmachine.Core do v3p11(conn, state) end end - + def respond(conn, state, code) do {conn, state} = if (code == 304) do conn = remove_resp_header(conn, "content-type") {etag, conn, state} = resource_call(conn, state, :generate_etag) conn = if etag, do: set_resp_header(conn, "etag", quoted_string(etag)), else: conn - + {exp, conn, state} = resource_call(conn, state, :expires) conn = if exp, do: set_resp_header(conn, "expires", rfc1123_date(exp)), else: conn {conn, state} @@ -816,6 +816,6 @@ defmodule Ewebmachine.Core do {conn, state} end conn = set_response_code(conn, code) - resource_call(conn, state, :finish_request) + resource_call(conn, state, :finish_request) end end diff --git a/lib/ewebmachine/core.utils.ex b/lib/ewebmachine/core.utils.ex index f94f7d4..b8fa260 100644 --- a/lib/ewebmachine/core.utils.ex +++ b/lib/ewebmachine/core.utils.ex @@ -29,8 +29,8 @@ defmodule Ewebmachine.Core.Utils do """ @spec fuzzy_mt_match(norm_content_type, norm_content_type) :: boolean def fuzzy_mt_match({h_type, h_subtype, h_params}, {a_type, a_subtype, a_params}) do - (a_type == h_type or a_type == "*" ) and - (a_subtype == h_subtype or a_subtype=="*") and + (a_type == h_type or a_type == "*" ) and + (a_subtype == h_subtype or a_subtype=="*") and Enum.all?(a_params, fn {k, v} -> h_params[k] == v end) end @@ -44,7 +44,7 @@ defmodule Ewebmachine.Core.Utils do end @doc """ - HTTP Content negociation, get the content type to send from : + HTTP Content negociation, get the content type to send from : - `accept_header`, the HTTP header `Accept` - `ct_provided`, the list of provided content types @@ -58,7 +58,7 @@ defmodule Ewebmachine.Core.Utils do e -> e end) |> Enum.map(&Plug.Conn.Utils.media_type/1) - accepts = for {:ok, type, subtype, params} <- accepts do + accepts = for {:ok, type, subtype, params} <- accepts do q = case Float.parse(params["q"] || "1") do {q, _} -> q _ -> 1 @@ -96,7 +96,7 @@ defmodule Ewebmachine.Core.Utils do def rfc1123_date({{yyyy, mm, dd}, {hour, min, sec}}) do day_number = :calendar.day_of_the_week({yyyy, mm, dd}) args = [:httpd_util.day(day_number), dd, :httpd_util.month(mm), yyyy, hour, min, sec] - :io_lib.format('~s, ~2.2.0w ~3.s ~4.4.0w ~2.2.0w:~2.2.0w:~2.2.0w GMT', args) |> IO.iodata_to_binary() + :io_lib.format(~c"~s, ~2.2.0w ~3.s ~4.4.0w ~2.2.0w:~2.2.0w:~2.2.0w GMT", args) |> IO.iodata_to_binary() end @doc """ @@ -105,13 +105,13 @@ defmodule Ewebmachine.Core.Utils do @spec convert_request_date(String.t) :: {{year :: integer, month :: integer, day :: integer}, {h :: integer, min :: integer, sec :: integer}} | :bad_date def convert_request_date(date) do try do - :httpd_util.convert_request_date('#{date}') + :httpd_util.convert_request_date(~c"#{date}") catch _, _ -> :bad_date end end @doc """ - HTTP Encoding negociation, get the encoding to use from : + HTTP Encoding negociation, get the encoding to use from : - `acc_enc_hdr`, the HTTP header `Accept-Encoding` - `encs`, the list of supported encoding @@ -122,7 +122,7 @@ defmodule Ewebmachine.Core.Utils do end @doc """ - HTTP Charset negociation, get the charset to use from : + HTTP Charset negociation, get the charset to use from : - `acc_char_hdr`, the HTTP header `Accept-Charset` - `charsets`, the list of supported charsets @@ -183,7 +183,7 @@ defmodule Ewebmachine.Core.Utils do ### Priv ### alias Ewebmachine.Compat - + defp choose(choices, header, default) do ## sorted set of {prio,value} prios = prioritized_values(header) @@ -192,14 +192,14 @@ defmodule Ewebmachine.Core.Utils do default_prio = Enum.find_value(prios, fn {prio, v} -> v == default && prio end) start_prio = Enum.find_value(prios, fn {prio, v} -> v == "*" && prio end) default_ok = case default_prio do - nil -> start_prio !== 0.0 - 0.0 -> false + nil -> start_prio !== +0.0 + +0.0 -> false _ -> true end - any_ok = start_prio not in [nil, 0.0] + any_ok = start_prio not in [nil, +0.0] # remove choices where prio == 0.0 - {zero_prios, prios} = Compat.Enum.split_with(prios, fn {prio, _} -> prio == 0.0 end) + {zero_prios, prios} = Compat.Enum.split_with(prios, fn {prio, _} -> prio == +0.0 end) choices_to_remove = Enum.map(zero_prios, &elem(&1, 1)) choices = Enum.filter(choices, &!(String.downcase(&1) in choices_to_remove)) @@ -218,7 +218,7 @@ defmodule Ewebmachine.Core.Utils do end defp prioritized_values(header) do - header + header |> Plug.Conn.Utils.list |> Enum.map(fn e-> {q,v} = case String.split(e,~r"\s;\s", parts: 2) do diff --git a/lib/ewebmachine/plug.error_as_forward.ex b/lib/ewebmachine/plug.error_as_forward.ex index 445bce8..6026ea1 100644 --- a/lib/ewebmachine/plug.error_as_forward.ex +++ b/lib/ewebmachine/plug.error_as_forward.ex @@ -9,9 +9,15 @@ defmodule Ewebmachine.Plug.ErrorAsForward do def init(opts), do: (opts[:forward_pattern] || "/error/:status") @doc false - def call(%{status: code, state: :set}=conn, pattern) when code > 399 do - path = pattern |> String.slice(1..-1) |> String.replace(":status", to_string(code)) |> String.split("/") - %{ conn | path_info: path, method: "GET", state: :unset } + def call(%Plug.Conn{status: code, state: :set} = conn, pattern) when code > 399 do + # `path_info` info is the request path split as segments. + path_info = + # Generate a path according to the status code. + String.replace(pattern, ":status", to_string(code)) + # Transform it into segments. + |> String.split("/", trim: true) + + %{conn | path_info: path_info, method: "GET", state: :unset} end def call(conn, _), do: conn end diff --git a/mix.exs b/mix.exs index 2a7b1dd..7505a0e 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule Ewebmachine.Mixfile do use Mix.Project - def version, do: "2.3.2" + def version, do: "2.3.3" @description """ Ewebmachine contains macros and plugs to allow you to compose diff --git a/test/run_core_test.exs b/test/run_core_test.exs index 68f23a9..4c9c52a 100644 --- a/test/run_core_test.exs +++ b/test/run_core_test.exs @@ -1,27 +1,31 @@ Code.require_file "test_helper.exs", __DIR__ defmodule CommonMacros do - defmacro resources([do: body]) do + defmacro resources([do: body]) do name = :"#{inspect make_ref()}" - quote do - defmodule unquote(name) do + + {:module, module, _, _} = Module.create( + name, + quote do use Ewebmachine.Builder.Resources plug :resource_match plug Ewebmachine.Plug.Run plug Ewebmachine.Plug.Send plug :error_404 - defp error_404(conn,_), do: - (conn |> send_resp(404,"") |> halt) + defp error_404(conn, _), do: conn |> send_resp(404, "") |> halt() unquote(body) - end - unquote(name) - end + end, + Macro.Env.location(__ENV__) + ) + + module end end defmodule EwebmachineTest do use ExUnit.Case - use Plug.Test + import Plug.Test + import Plug.Conn import CommonMacros test "Simple Handlers builder with only to_html default GET" do @@ -30,7 +34,7 @@ defmodule EwebmachineTest do plug :add_handlers, init: %{} plug Ewebmachine.Plug.Run plug Ewebmachine.Plug.Send - + defh to_html, do: "Hello World" end conn = SimpleHtml.call(conn(:get, "/"), []) @@ -39,7 +43,7 @@ defmodule EwebmachineTest do assert conn.resp_body == "Hello World" assert conn.state == :sent end - + test "default plugs" do defmodule SimpleResources do use Ewebmachine.Builder.Resources, default_plugs: true @@ -51,11 +55,11 @@ defmodule EwebmachineTest do assert conn.resp_body == "toto" assert conn.state == :sent end - + test "Simple resource builder with XML and path match param" do app = resources do - resource "/hello/:name" do %{name: name} after - content_types_provided do: ['application/xml': :to_xml] + resource "/hello/:name" do %{name: name} after + content_types_provided do: ["application/xml": :to_xml] defh to_xml, do: "#{state.name}" end end @@ -65,10 +69,10 @@ defmodule EwebmachineTest do assert Enum.into(conn.resp_headers,%{})["content-type"] == "application/xml" assert conn.resp_body == "arnaud" end - + test "Implement not exists" do app = resources do - resource "/hello/:name" do %{name: name} after + resource "/hello/:name" do %{name: name} after resource_exists do: state.name !== "idonotexist" defh to_html, do: state.name end @@ -76,20 +80,20 @@ defmodule EwebmachineTest do assert app.call(conn(:get, "/hello/arnaud"), []).status == 200 assert app.call(conn(:get, "/hello/idonotexist"), []).status == 404 end - + test "Service not available" do app = resources do - resource "/notok" do %{} after + resource "/notok" do %{} after service_available do: false end - resource "/ok" do %{} after + resource "/ok" do %{} after service_available do: true end end assert app.call(conn(:get, "/notok"), []).status == 503 assert app.call(conn(:get, "/ok"), []).status == 200 end - + test "Unknown method" do app = resources do resource "/notok" do %{} after known_methods(do: ["TOTO"]) end @@ -98,7 +102,7 @@ defmodule EwebmachineTest do assert app.call(conn(:get, "/notok"), []).status == 501 assert app.call(conn(:get, "/ok"), []).status == 200 end - + test "Url too long" do app = resources do resource "/notok" do %{} after uri_too_long(do: true) end @@ -107,7 +111,7 @@ defmodule EwebmachineTest do assert app.call(conn(:get, "/notok"), []).status == 414 assert app.call(conn(:get, "/ok"), []).status == 200 end - + test "Method allowed ?" do app = resources do resource "/notok" do %{} after allowed_methods(do: ["POST"]) end @@ -116,10 +120,10 @@ defmodule EwebmachineTest do assert app.call(conn(:get, "/notok"), []).status == 405 assert app.call(conn(:get, "/ok"), []).status == 200 end - + test "Content MD5 check" do app = resources do - resource "/" do %{} after + resource "/" do %{} after allowed_methods do: ["PUT"] content_types_accepted do: ["application/json": :from_json] defh from_json, do: true @@ -130,7 +134,7 @@ defmodule EwebmachineTest do headers = [{"content-type","application/json"},{"content-md5","sZRqySSS0jR8YjW00mERhA=="}] assert app.call(%{conn(:put,"/","hello\n")|req_headers: headers},[]).status == 204 end - + test "Malformed ?" do app = resources do resource "/notok" do %{} after malformed_request(do: true) end @@ -139,7 +143,7 @@ defmodule EwebmachineTest do assert app.call(conn(:get, "/notok"), []).status == 400 assert app.call(conn(:get, "/ok"), []).status == 200 end - + test "Is authorized ?" do app = resources do resource "/notok" do %{} after is_authorized(do: "myrealm") end @@ -150,10 +154,10 @@ defmodule EwebmachineTest do assert conn.status == 401 assert get_resp_header(conn,"www-authenticate") == ["myrealm"] end - + test "Encoding base64" do app = resources do - resource "/" do %{} after + resource "/" do %{} after encodings_provided do: [base64: &Base.encode64/1, identity: &(&1)] defh to_html, do: "hello" end @@ -166,10 +170,10 @@ defmodule EwebmachineTest do assert get_resp_header(conn,"content-encoding") == [] assert conn.resp_body == "hello" end - + test "POST create path" do app = resources do - resource "/orders" do %{} after + resource "/orders" do %{} after allowed_methods do: ["POST"] post_is_create do: true create_path do: "/titus" @@ -177,11 +181,11 @@ defmodule EwebmachineTest do defh from_text, do: {true,put_private(conn,:body_post,read_body(conn)),state} end - resource "/orders2" do %{} after + resource "/orders2" do %{} after allowed_methods do: ["POST"] post_is_create do: true # Check modified state is propagated - defh create_path(conn, state), do: {state.path, conn, state} + defh create_path(conn, %{path: path} = state), do: {path, conn, state} content_types_accepted do: ["text/plain": :from_text] defh from_text(conn, state), do: {true, conn, state |> Map.put(:path, "titus")} end @@ -193,10 +197,10 @@ defmodule EwebmachineTest do conn = app.call(conn(:post,"/orders2","titus") |> put_req_header("content-type", "text/plain"), []) assert get_resp_header(conn,"location") == ["http://www.example.com/orders2/titus"] end - + test "POST process post" do app = resources do - resource "/orders" do %{} after + resource "/orders" do %{} after allowed_methods do: ["POST"] process_post do: {true,put_private(conn,:body_post,"yes"),state} @@ -206,13 +210,13 @@ defmodule EwebmachineTest do assert conn.status == 204 assert "yes" = conn.private[:body_post] end - + test "Cache if modified" do app = resources do - resource "/notcached" do %{} after + resource "/notcached" do %{} after last_modified do: {{2013,1,1},{0,0,0}} end - resource "/cached" do %{} after + resource "/cached" do %{} after last_modified do: {{2012,12,31},{0,0,0}} end end @@ -221,13 +225,13 @@ defmodule EwebmachineTest do conn = app.call(%{conn(:get,"/notcached")|req_headers: [{"if-modified-since","Sat, 31 Dec 2012 19:43:31 GMT"}]}, []) assert conn.status == 200 end - + test "Cache etag" do app = resources do - resource "/notcached" do %{} after + resource "/notcached" do %{} after generate_etag do: "titi" end - resource "/cached" do %{} after + resource "/cached" do %{} after generate_etag do: "toto" end end @@ -236,10 +240,10 @@ defmodule EwebmachineTest do conn = app.call(%{conn(:get,"/notcached")|req_headers: [{"if-none-match","toto"}]}, []) assert conn.status == 200 end - + test "halt test" do app = resources do - resource "/error" do %{} after + resource "/error" do %{} after content_types_provided do: {:halt,407} defh to_html, do: "toto" end @@ -251,7 +255,7 @@ defmodule EwebmachineTest do test "fuzzy acceptance" do app = resources do - resource "/" do %{} after + resource "/" do %{} after allowed_methods do: ["PUT"] content_types_accepted do: %{"application/*"=> :from_app, {"text/*",%{"pretty"=>"true"}}=> :from_pretty} defh from_app, do: {:halt,601}