From 01d38d8b26e9010e7926a66345f41570fe3d4433 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Wed, 17 Sep 2025 14:15:19 +0200 Subject: [PATCH 1/6] chore: fixed warning in 1.18 --- CHANGELOG.md | 6 + lib/ewebmachine/builder.handlers.ex | 32 +++--- lib/ewebmachine/core.ex | 140 +++++++++++------------ lib/ewebmachine/core.utils.ex | 28 ++--- lib/ewebmachine/plug.error_as_forward.ex | 2 +- mix.exs | 2 +- test/run_core_test.exs | 100 ++++++++-------- 7 files changed, 160 insertions(+), 150 deletions(-) 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..e8cd1d6 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,[]) @@ -106,7 +106,7 @@ defmodule Ewebmachine.Builder.Handlers 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: _)) @@ -115,12 +115,12 @@ defmodule Ewebmachine.Builder.Handlers do @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 +142,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 +171,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..fbe26e6 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,10 +569,10 @@ 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 end @@ -581,7 +581,7 @@ defmodule Ewebmachine.Core do else new_path end - + conn = if !get_resp_header(conn, "location") do set_resp_header(conn, "location", base_uri <> new_path) 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..054b20b 100644 --- a/lib/ewebmachine/plug.error_as_forward.ex +++ b/lib/ewebmachine/plug.error_as_forward.ex @@ -10,7 +10,7 @@ defmodule Ewebmachine.Plug.ErrorAsForward do @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("/") + path = pattern |> String.slice(1..-1//-1) |> String.replace(":status", to_string(code)) |> String.split("/") %{ conn | path_info: path, method: "GET", state: :unset } end def call(conn, _), do: conn 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..7e8a4b1 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) + plug(:resource_match) + plug(Ewebmachine.Plug.Run) + plug(Ewebmachine.Plug.Send) + plug(:error_404) + 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,13 +181,13 @@ 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, do: {state.path, conn, state} content_types_accepted do: ["text/plain": :from_text] - defh from_text(conn, state), do: {true, conn, state |> Map.put(:path, "titus")} + defh from_text, do: {true, conn, state |> Map.put(:path, "titus")} end end conn = app.call(conn(:post,"/orders","titus") |> put_req_header("content-type", "text/plain"), []) @@ -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} From 1fc5b379dd548ba1a7983764d092ada734c9420f Mon Sep 17 00:00:00 2001 From: Gberger-98 <62150055+Gberger-98@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:46:11 +0200 Subject: [PATCH 2/6] Update test/run_core_test.exs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nathanaƫl --- test/run_core_test.exs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/run_core_test.exs b/test/run_core_test.exs index 7e8a4b1..2bab689 100644 --- a/test/run_core_test.exs +++ b/test/run_core_test.exs @@ -8,10 +8,10 @@ defmodule CommonMacros do name, quote do use Ewebmachine.Builder.Resources - plug(:resource_match) - plug(Ewebmachine.Plug.Run) - plug(Ewebmachine.Plug.Send) - plug(:error_404) + plug :resource_match + plug Ewebmachine.Plug.Run + plug Ewebmachine.Plug.Send + plug :error_404 defp error_404(conn, _), do: conn |> send_resp(404, "") |> halt unquote(body) end, From db69d4fb57f964926543ce30c7985e073f5699a9 Mon Sep 17 00:00:00 2001 From: Gberger-98 <62150055+Gberger-98@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:46:20 +0200 Subject: [PATCH 3/6] Update test/run_core_test.exs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Nathanaƫl --- test/run_core_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/run_core_test.exs b/test/run_core_test.exs index 2bab689..be00bca 100644 --- a/test/run_core_test.exs +++ b/test/run_core_test.exs @@ -12,7 +12,7 @@ defmodule CommonMacros do 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, Macro.Env.location(__ENV__) From 15199d11493c03b1949db83c3907c9909c081c43 Mon Sep 17 00:00:00 2001 From: Nathanael Date: Thu, 18 Sep 2025 16:20:10 +0200 Subject: [PATCH 4/6] chore: prevent String.slice/2 warning on Range's negative steps. --- lib/ewebmachine/core.ex | 10 +++++----- lib/ewebmachine/plug.error_as_forward.ex | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/ewebmachine/core.ex b/lib/ewebmachine/core.ex index fbe26e6..5682fbd 100644 --- a/lib/ewebmachine/core.ex +++ b/lib/ewebmachine/core.ex @@ -572,20 +572,20 @@ defmodule Ewebmachine.Core do {base_uri, conn, state} = resource_call(conn, state, :base_uri) base_uri = if String.last(base_uri) == "/" do - String.slice(base_uri,0..-2//-1) + 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 diff --git a/lib/ewebmachine/plug.error_as_forward.ex b/lib/ewebmachine/plug.error_as_forward.ex index 054b20b..72f85b7 100644 --- a/lib/ewebmachine/plug.error_as_forward.ex +++ b/lib/ewebmachine/plug.error_as_forward.ex @@ -9,8 +9,9 @@ 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//-1) |> String.replace(":status", to_string(code)) |> String.split("/") + def call(%{status: code, state: :set} = conn, pattern) when code > 399 do + # This should be `path = String.split(String.replace(pattern, ":status", to_string(code)), "/", trim: true) + path = pattern |> String.slice(1..-1//1) |> String.replace(":status", to_string(code)) |> String.split("/") %{ conn | path_info: path, method: "GET", state: :unset } end def call(conn, _), do: conn From ecd07333668fd1cafbec106ed5cdeff43821221d Mon Sep 17 00:00:00 2001 From: Nathanael Date: Thu, 18 Sep 2025 16:25:55 +0200 Subject: [PATCH 5/6] refacto: remove the unusual use of `String.slice` to trim the generated path --- lib/ewebmachine/plug.error_as_forward.ex | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/ewebmachine/plug.error_as_forward.ex b/lib/ewebmachine/plug.error_as_forward.ex index 72f85b7..6026ea1 100644 --- a/lib/ewebmachine/plug.error_as_forward.ex +++ b/lib/ewebmachine/plug.error_as_forward.ex @@ -9,10 +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 - # This should be `path = String.split(String.replace(pattern, ":status", to_string(code)), "/", trim: true) - path = pattern |> String.slice(1..-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 From 138ab1282ca61bfbc6f85f6844331c214a644ebc Mon Sep 17 00:00:00 2001 From: Nathanael Date: Thu, 18 Sep 2025 16:31:33 +0200 Subject: [PATCH 6/6] fix: avoid circular reference error from elixir type system Restored tests to define conn and state variable, as well as pattern matching with multiple pattern that include the conn or state variable. --- lib/ewebmachine/builder.handlers.ex | 25 ++++++++++++++++++++++++- test/run_core_test.exs | 4 ++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/lib/ewebmachine/builder.handlers.ex b/lib/ewebmachine/builder.handlers.ex index e8cd1d6..833ad0b 100644 --- a/lib/ewebmachine/builder.handlers.ex +++ b/lib/ewebmachine/builder.handlers.ex @@ -100,9 +100,19 @@ 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 @@ -112,6 +122,19 @@ defmodule Ewebmachine.Builder.Handlers 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`. diff --git a/test/run_core_test.exs b/test/run_core_test.exs index be00bca..4c9c52a 100644 --- a/test/run_core_test.exs +++ b/test/run_core_test.exs @@ -185,9 +185,9 @@ defmodule EwebmachineTest do allowed_methods do: ["POST"] post_is_create do: true # Check modified state is propagated - defh create_path, 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, do: {true, conn, state |> Map.put(:path, "titus")} + defh from_text(conn, state), do: {true, conn, state |> Map.put(:path, "titus")} end end conn = app.call(conn(:post,"/orders","titus") |> put_req_header("content-type", "text/plain"), [])