status.ex (4862B)
1 defmodule Plug.Conn.Status do 2 @moduledoc """ 3 Conveniences for working with status codes. 4 """ 5 6 custom_statuses = Application.compile_env(:plug, :statuses, %{}) 7 8 statuses = %{ 9 100 => "Continue", 10 101 => "Switching Protocols", 11 102 => "Processing", 12 103 => "Early Hints", 13 200 => "OK", 14 201 => "Created", 15 202 => "Accepted", 16 203 => "Non-Authoritative Information", 17 204 => "No Content", 18 205 => "Reset Content", 19 206 => "Partial Content", 20 207 => "Multi-Status", 21 208 => "Already Reported", 22 226 => "IM Used", 23 300 => "Multiple Choices", 24 301 => "Moved Permanently", 25 302 => "Found", 26 303 => "See Other", 27 304 => "Not Modified", 28 305 => "Use Proxy", 29 306 => "Switch Proxy", 30 307 => "Temporary Redirect", 31 308 => "Permanent Redirect", 32 400 => "Bad Request", 33 401 => "Unauthorized", 34 402 => "Payment Required", 35 403 => "Forbidden", 36 404 => "Not Found", 37 405 => "Method Not Allowed", 38 406 => "Not Acceptable", 39 407 => "Proxy Authentication Required", 40 408 => "Request Timeout", 41 409 => "Conflict", 42 410 => "Gone", 43 411 => "Length Required", 44 412 => "Precondition Failed", 45 413 => "Request Entity Too Large", 46 414 => "Request-URI Too Long", 47 415 => "Unsupported Media Type", 48 416 => "Requested Range Not Satisfiable", 49 417 => "Expectation Failed", 50 418 => "I'm a teapot", 51 421 => "Misdirected Request", 52 422 => "Unprocessable Entity", 53 423 => "Locked", 54 424 => "Failed Dependency", 55 425 => "Too Early", 56 426 => "Upgrade Required", 57 428 => "Precondition Required", 58 429 => "Too Many Requests", 59 431 => "Request Header Fields Too Large", 60 451 => "Unavailable For Legal Reasons", 61 500 => "Internal Server Error", 62 501 => "Not Implemented", 63 502 => "Bad Gateway", 64 503 => "Service Unavailable", 65 504 => "Gateway Timeout", 66 505 => "HTTP Version Not Supported", 67 506 => "Variant Also Negotiates", 68 507 => "Insufficient Storage", 69 508 => "Loop Detected", 70 510 => "Not Extended", 71 511 => "Network Authentication Required" 72 } 73 74 reason_phrase_to_atom = fn reason_phrase -> 75 reason_phrase 76 |> String.downcase() 77 |> String.replace("'", "") 78 |> String.replace(~r/[^a-z0-9]/, "_") 79 |> String.to_atom() 80 end 81 82 status_map_to_doc = fn statuses -> 83 statuses 84 |> Enum.sort_by(&elem(&1, 0)) 85 |> Enum.map(fn {code, reason_phrase} -> 86 atom = reason_phrase_to_atom.(reason_phrase) 87 " * `#{inspect(atom)}` - #{code}\n" 88 end) 89 end 90 91 custom_status_doc = 92 if custom_statuses != %{} do 93 """ 94 ## Custom status codes 95 96 #{status_map_to_doc.(custom_statuses)} 97 """ 98 end 99 100 @doc """ 101 Returns the status code given an integer or a known atom. 102 103 ## Known status codes 104 105 The following status codes can be given as atoms with their 106 respective value shown next: 107 108 #{status_map_to_doc.(statuses)} 109 #{custom_status_doc} 110 """ 111 @spec code(integer | atom) :: integer 112 def code(integer_or_atom) 113 114 def code(integer) when integer in 100..999 do 115 integer 116 end 117 118 for {code, reason_phrase} <- statuses do 119 atom = reason_phrase_to_atom.(reason_phrase) 120 def code(unquote(atom)), do: unquote(code) 121 end 122 123 # This ensures that both the default and custom statuses will work 124 for {code, reason_phrase} <- custom_statuses do 125 atom = reason_phrase_to_atom.(reason_phrase) 126 def code(unquote(atom)), do: unquote(code) 127 end 128 129 @doc """ 130 Returns the atom for given integer. 131 132 See `code/1` for the mapping. 133 """ 134 @spec reason_atom(integer) :: atom 135 def reason_atom(code) 136 137 for {code, reason_phrase} <- Map.merge(statuses, custom_statuses) do 138 atom = reason_phrase_to_atom.(reason_phrase) 139 def reason_atom(unquote(code)), do: unquote(atom) 140 end 141 142 def reason_atom(code) do 143 raise ArgumentError, "unknown status code #{inspect(code)}" 144 end 145 146 @spec reason_phrase(integer) :: String.t() 147 def reason_phrase(integer) 148 149 for {code, phrase} <- Map.merge(statuses, custom_statuses) do 150 def reason_phrase(unquote(code)), do: unquote(phrase) 151 end 152 153 def reason_phrase(code) do 154 raise ArgumentError, """ 155 unknown status code #{inspect(code)} 156 157 Custom codes can be defined in the configuration for the :plug application, 158 under the :statuses key (which contains a map of status codes as keys and 159 reason phrases as values). For example: 160 161 config :plug, :statuses, %{998 => "Not An RFC Status Code"} 162 163 After defining the config for custom statuses, Plug must be recompiled for 164 the changes to take place using: 165 166 MIX_ENV=dev mix deps.clean plug --build 167 168 Doing this will allow the use of the integer status code 998 as 169 well as the atom :unavailable_for_legal_reasons in many Plug functions. 170 For example: 171 172 put_status(conn, :not_an_rfc_status_code) 173 """ 174 end 175 end