From 8c5e4e77edacf082b5ab2d8aede49aa0ddfb9b5e Mon Sep 17 00:00:00 2001 From: William Carroll Date: Sat, 12 Dec 2020 02:43:40 +0000 Subject: [PATCH] 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. --- assessments/semiprimes/server/lib/router.ex | 86 +++++++++++++++++++++ assessments/semiprimes/server/lib/sup.ex | 4 +- assessments/semiprimes/server/mix.exs | 6 +- assessments/semiprimes/server/mix.lock | 10 +++ 4 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 assessments/semiprimes/server/lib/router.ex diff --git a/assessments/semiprimes/server/lib/router.ex b/assessments/semiprimes/server/lib/router.ex new file mode 100644 index 000000000..cb5552092 --- /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= + GET /semiprimes?numbers= + """ + + 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 ea02ce30e..13a6ab374 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) diff --git a/assessments/semiprimes/server/mix.exs b/assessments/semiprimes/server/mix.exs index 2bbd357c3..9062f927e 100644 --- a/assessments/semiprimes/server/mix.exs +++ b/assessments/semiprimes/server/mix.exs @@ -22,7 +22,11 @@ defmodule Server.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:cortex, "~> 0.1", only: [:dev, :test]} + {:cortex, "~> 0.1", only: [:dev, :test]}, + {:plug_cowboy, "~> 2.4.1"}, + {:cowboy, "~> 2.8.0"}, + {:plug, "~> 1.11.0"}, + {:poison, "~> 4.0.1"} ] end end diff --git a/assessments/semiprimes/server/mix.lock b/assessments/semiprimes/server/mix.lock index 066a7cadd..2ae7efbb3 100644 --- a/assessments/semiprimes/server/mix.lock +++ b/assessments/semiprimes/server/mix.lock @@ -1,4 +1,14 @@ %{ "cortex": {:hex, :cortex, "0.6.0", "8094830fae266eb0ae34d1a58983c0c49484341f5044fb4dfb81746647bd2993", [:mix], [{:file_system, "~> 0.2", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "d0ef5a2b1269626149118684dc4ea77dbfbc67017f4b4065b71dcefa26cfcc49"}, + "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"}, + "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"}, + "cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"}, + "plug": {:hex, :plug, "1.11.0", "f17217525597628298998bc3baed9f8ea1fa3f1160aa9871aee6df47a6e4d38e", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2d9c633f0499f9dc5c2fd069161af4e2e7756890b81adcbb2ceaa074e8308876"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.4.1", "779ba386c0915027f22e14a48919a9545714f849505fa15af2631a0d298abf0f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d72113b6dff7b37a7d9b2a5b68892808e3a9a752f2bf7e503240945385b70507"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"}, + "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, + "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, + "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, }