about summary refs log tree commit diff
path: root/assessments/semiprimes/server/lib
diff options
context:
space:
mode:
authorWilliam Carroll <wpcarro@gmail.com>2020-12-12T02·43+0000
committerWilliam Carroll <wpcarro@gmail.com>2020-12-12T02·43+0000
commit8c5e4e77edacf082b5ab2d8aede49aa0ddfb9b5e (patch)
treeb216303b8755d7d945a35a2a3d12d77b3828ceeb /assessments/semiprimes/server/lib
parent45877a8b9cbe728e2dd66c361b3d6625810b31a0 (diff)
Expose functions at API layer
Creating a simple HTTP RESTful API for exposing our `Server.semiprime`
function. It supports some help messages, primitive parsing and error handling,
and singular vs. batch processing of arguments.

For more sophisticated parsing and error-checking, I prefer to use Haskell's
Servant library.
Diffstat (limited to 'assessments/semiprimes/server/lib')
-rw-r--r--assessments/semiprimes/server/lib/router.ex86
-rw-r--r--assessments/semiprimes/server/lib/sup.ex4
2 files changed, 89 insertions, 1 deletions
diff --git a/assessments/semiprimes/server/lib/router.ex b/assessments/semiprimes/server/lib/router.ex
new file mode 100644
index 000000000000..cb55520920de
--- /dev/null
+++ b/assessments/semiprimes/server/lib/router.ex
@@ -0,0 +1,86 @@
+defmodule Router do
+  use Plug.Router
+  use Plug.Debugger
+  require Logger
+
+  plug(Plug.Logger, log: :debug)
+  plug(Plug.Parsers, parsers: [:urlencoded])
+  plug(:match)
+  plug(:dispatch)
+
+  @usage """
+  Usage: Try querying some of the following endpoints...
+    GET /
+    GET /help
+    GET /semiprime?number=<integer>
+    GET /semiprimes?numbers=<comma-separated-integers>
+  """
+
+  get "/" do
+    send_resp(conn, 200, "Welcome to Semiprimes Service!\n\n#{@usage}")
+  end
+
+  get "/help" do
+    send_resp(conn, 200, @usage)
+  end
+
+  get "/semiprime" do
+    case conn |> Map.get(:query_params) |> Map.get("number") do
+      nil ->
+        send_resp(conn, 400, "You must pass an integer as a query parameter. #{@usage}")
+
+      val ->
+        case Integer.parse(val) do
+          {n, ""} ->
+            send_resp(conn, 200, semiprime_response(n))
+
+          _ ->
+            send_resp(conn, 400, "We could not parse the number you provided.\n\n#{@usage}")
+        end
+    end
+  end
+
+  get "/semiprimes" do
+    case conn |> Map.get(:query_params) |> Map.get("numbers") do
+      nil ->
+        send_resp(
+          conn,
+          400,
+          "You must pass a comma-separated list of integers as a query parameter.\n\n#{@usage}"
+        )
+
+      xs ->
+        response =
+          xs
+          |> String.split(",")
+          |> Stream.map(&Integer.parse/1)
+          |> Stream.filter(fn
+            {n, ""} -> true
+            _ -> false
+          end)
+          |> Stream.map(fn {n, ""} -> semiprime_response(n) end)
+          |> Enum.join("\n")
+
+        send_resp(conn, 200, response)
+    end
+  end
+
+  match _ do
+    send_resp(conn, 404, "Not found.")
+  end
+
+  ################################################################################
+  # Utils
+  ################################################################################
+
+  defp semiprime_response(n) do
+    case Server.semiprime(n) do
+      nil ->
+        "#{n} is not a semiprime. Try another number!"
+
+      {hit_or_miss, factors} ->
+        response = "#{n} is a semiprime! Its factors are #{Enum.join(factors, " and ")}."
+        "Cache #{Atom.to_string(hit_or_miss)} - #{response}"
+    end
+  end
+end
diff --git a/assessments/semiprimes/server/lib/sup.ex b/assessments/semiprimes/server/lib/sup.ex
index ea02ce30e7b9..13a6ab374ff6 100644
--- a/assessments/semiprimes/server/lib/sup.ex
+++ b/assessments/semiprimes/server/lib/sup.ex
@@ -5,6 +5,7 @@ defmodule Sup do
   """
 
   use Supervisor
+  alias Plug.Adapters.Cowboy
 
   def start_link(opts \\ []) do
     Supervisor.start_link(__MODULE__, :ok, opts)
@@ -13,7 +14,8 @@ defmodule Sup do
   @impl true
   def init(:ok) do
     children = [
-      Cache
+      Cache,
+      Cowboy.child_spec(scheme: :http, plug: Router, options: [port: 8000])
     ]
 
     Supervisor.init(children, strategy: :one_for_one)