From bc1b8fd15631c96827fae06bddccf4fa96a5af30 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 6 Jul 2023 17:33:56 +0200 Subject: [PATCH 01/20] docs: datetime_handler --- lib/arke/utils/datetime_handler.ex | 39 ++++++++++++++++++++++++++++-- mix.exs | 31 +++++++++++++++++++++--- mix.lock | 28 ++++++++++----------- 3 files changed, 78 insertions(+), 20 deletions(-) diff --git a/lib/arke/utils/datetime_handler.ex b/lib/arke/utils/datetime_handler.ex index 4deeaa5..2d4c32f 100644 --- a/lib/arke/utils/datetime_handler.ex +++ b/lib/arke/utils/datetime_handler.ex @@ -13,9 +13,12 @@ # limitations under the License. defmodule Arke.DatetimeHandler do + @moduledoc """ + This module is responsible for managing datetime, date and time values + """ use Timex - @datetime_msg "must be %DateTime | %NaiveDatetime{} | ~N[YYYY-MM-DDTHH:MM:SS] | ~N[YYYY-MM-DD HH:MM:SS] | ~U[YYYY-MM-DD HH:MM:SS] format" + @datetime_msg "must be %DateTime{} | %NaiveDatetime{} | ~N[YYYY-MM-DDTHH:MM:SS] | ~N[YYYY-MM-DD HH:MM:SS] | ~U[YYYY-MM-DD HH:MM:SS] format" @date_msg "must be %Date{} | ~D[YYYY-MM-DD] | iso8601 (YYYY-MM-DD) format" @@ -67,8 +70,22 @@ defmodule Arke.DatetimeHandler do end # ----- DATETIME ----- + @doc """ + Returns a %DateTime{} | %Date{}| %Time{} representing the current moment in time. + """ + @spec now(:datetime | :date | :time) :: DateTime.t() | Date.t() | Time.t() def now(:datetime), do: Timex.set(Timex.now(), microsecond: 0) + + @doc """ + Parse the given value to a %DateTime{}. + Usually returns `{:ok, value}` except if the `only_value` parameter is set to `true`. In this case it will returns only the value but it still returns `{:error, msg}` if an error occured + Returns `nil` if `nil` is given + """ + @spec parse_datetime( + value :: DateTime.t() | NaiveDateTime.t() | nil | String.t(), + only_value :: boolean() + ) :: nil | {:ok, DateTime.t()} | DateTime.t() | {:error, term()} def parse_datetime(value, only_value \\ false) def parse_datetime(value, true) when is_nil(value), do: value def parse_datetime(value, _only_value) when is_nil(value), do: {:ok, value} @@ -78,7 +95,6 @@ defmodule Arke.DatetimeHandler do def parse_datetime(%NaiveDateTime{} = value, only_value), do: check_datetime(value, only_value) def parse_datetime(value, only_value) do - case Timex.parse(value, "{ISO:Extended:Z}") do {:ok, datetime} -> check_datetime(datetime, only_value) {:error, _} -> {:error, @datetime_msg} @@ -87,6 +103,16 @@ defmodule Arke.DatetimeHandler do # ----- DATE ----- def now(:date), do: Timex.now() |> Timex.to_date() + + @doc """ + Parse the given value to a %Date{}. + Usually returns `{:ok, value}` except if the `only_value` parameter is set to `true`. In this case it will returns only the value but it still returns `{:error, msg}` if an error occured + Returns `nil` if `nil` is given + """ + @spec parse_date( + value :: Date.t() | nil | String.t(), + only_value :: boolean() + ) :: nil | {:ok, Date.t()} | Date.t() | {:error, term()} def parse_date(value, only_value \\ false) def parse_date(value, true) when is_nil(value), do: nil def parse_date(value, _only_value) when is_nil(value), do: {:ok, nil} @@ -103,6 +129,15 @@ defmodule Arke.DatetimeHandler do # ----- TIME ----- def now(:time), do: Time.utc_now() |> Time.truncate(:second) + @doc """ + Parse the given value to a %Time{}. + Usually returns `{:ok, value}` except if the `only_value` parameter is set to `true`. In this case it will returns only the value but it still returns `{:error, msg}` if an error occured + Returns `nil` if `nil` is given + """ + @spec parse_time( + value :: Time.t() | nil | String.t(), + only_value :: boolean() + ) :: nil | {:ok, Date.t()} | Date.t() | {:error, term()} def parse_time(value, only_value \\ false) def parse_time(value, true) when is_nil(value), do: nil def parse_time(value, _only_value) when is_nil(value), do: {:ok, nil} diff --git a/mix.exs b/mix.exs index 66aa09a..d6f054b 100644 --- a/mix.exs +++ b/mix.exs @@ -1,11 +1,16 @@ defmodule Arke.MixProject do use Mix.Project + @version "0.1.8" + @scm_url "https://github.com/arkemishub/arke" + @site_url "https://arkehub.com" + @logo_url "public/arke-logo.png" + def project do [ app: :arke, name: "Arke", - version: "0.1.8", + version: @version, build_path: "./_build", deps_path: "./deps", lockfile: "./mix.lock", @@ -17,8 +22,16 @@ defmodule Arke.MixProject do test_coverage: [tool: ExCoveralls], aliases: aliases(), deps: deps(), + source_url: @scm_url, + homepage_url: @site_url, elixirc_paths: elixirc_paths(Mix.env()), - elixirc_options: [warnings_as_errors: false] + docs: [ + # The main page in the docs + main: "Arke", + logo: @logo_url, + extras: ["README.md", "LICENSE"], + groups_for_modules: groups_for_modules() + ] ] end @@ -70,9 +83,19 @@ defmodule Arke.MixProject do # These are the default files included in the package licenses: ["Apache-2.0"], links: %{ - "Website" => "https://arkehub.com", - "Github" => "https://github.com/arkemishub/arke" + "Website" => @site_url, + "Github" => @scm_url } ] end + + defp groups_for_modules() do + [ + System: [~r"Arke.System"], + Parameter: [~r"Arke.Core.Parameter"], + Query: [~r"Arke.Core.Query"], + Core: [~r"Arke.Core."], + Utils: [~r"Arke.Utils."] + ] + end end diff --git a/mix.lock b/mix.lock index 28c8cc8..965d72f 100644 --- a/mix.lock +++ b/mix.lock @@ -1,16 +1,16 @@ %{ "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, - "castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"}, + "castore": {:hex, :castore, "1.0.3", "7130ba6d24c8424014194676d608cb989f62ef8039efd50ff4b3f33286d06db8", [:mix], [], "hexpm", "680ab01ef5d15b161ed6a95449fac5c6b8f60055677a8e79acf01b27baa4390b"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, - "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"}, - "ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"}, - "excoveralls": {:hex, :excoveralls, "0.15.1", "83c8cf7973dd9d1d853dce37a2fb98aaf29b564bf7d01866e409abf59dac2c0e", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f8416bd90c0082d56a2178cf46c837595a06575f70a5624f164a1ffe37de07e7"}, + "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"}, + "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, + "excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"}, "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "finch": {:hex, :finch, "0.14.0", "619bfdee18fc135190bf590356c4bf5d5f71f916adb12aec94caa3fa9267a4bc", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5459acaf18c4fdb47a8c22fb3baff5d8173106217c8e56c5ba0b93e66501a8dd"}, - "gettext": {:hex, :gettext, "0.22.1", "e7942988383c3d9eed4bdc22fc63e712b655ae94a672a27e4900e3d4a2c43581", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "ad105b8dab668ee3f90c0d3d94ba75e9aead27a62495c101d94f2657a190ac5d"}, + "finch": {:hex, :finch, "0.16.0", "40733f02c89f94a112518071c0a91fe86069560f5dbdb39f9150042f44dcfb1a", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5"}, + "gettext": {:hex, :gettext, "0.22.3", "c8273e78db4a0bb6fba7e9f0fd881112f349a3117f7f7c598fa18c66c888e524", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "935f23447713954a6866f1bb28c3a878c4c011e802bcd68a726f5e558e4b64bd"}, "google_api_storage": {:hex, :google_api_storage, "0.34.0", "4811461a5065f8a5d59a7fe71efd9c491ba593d20743f0d6f4714153c1dd575f", [:mix], [{:google_gax, "~> 0.4", [hex: :google_gax, repo: "hexpm", optional: false]}], "hexpm", "b343f2f1688acd2e8a2302bed399a24d93626f8e8c809b358214bb0fb7d2669a"}, "google_gax": {:hex, :google_gax, "0.4.1", "310105070626013712c56f8007b6ff7b4ead02ecad1efe7888350c6eaba52783", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 3.0.0 and < 5.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "aef7dce7e04840c0e611f962475e3223d27d50ebd5e7d8e9e963c5e9e3b1ca79"}, "goth": {:hex, :goth, "1.3.1", "f3e08a7f23ea8992ab92d2e1d5c72ea1a8fbd2fe3a46ad1b08d0620f71374fdc", [:mix], [{:finch, "~> 0.9", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:jose, "~> 1.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "90326c2c0a7acda7fb75fc4a4f0cba84945d8fcb22694d36c9967cec8949937c"}, @@ -21,20 +21,20 @@ "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, "jose": {:hex, :jose, "1.11.5", "3bc2d75ffa5e2c941ca93e5696b54978323191988eb8d225c2e663ddfefd515e", [:mix, :rebar3], [], "hexpm", "dcd3b215bafe02ea7c5b23dafd3eb8062a5cd8f2d904fd9caa323d37034ab384"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, - "nimble_options": {:hex, :nimble_options, "0.5.2", "42703307b924880f8c08d97719da7472673391905f528259915782bb346e0a1b", [:mix], [], "hexpm", "4da7f904b915fd71db549bcdc25f8d56f378ef7ae07dc1d372cbe72ba950dce0"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, - "nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"}, + "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, + "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "tesla": {:hex, :tesla, "1.5.1", "f2ba04f5e6ace0f1954f1fb4375f55809a5f2ff491c18ccb09fbc98370d4280b", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2815d4f6550973d1ed65692d545d079174f6a1f8cb4775f6eb606cbc0666a9de"}, + "tesla": {:hex, :tesla, "1.7.0", "a62dda2f80d4f8a925eb7b8c5b78c461e0eb996672719fe1a63b26321a5f8b4e", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2e64f01ebfdb026209b47bc651a0e65203fcff4ae79c11efb73c4852b00dc313"}, "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, "typed_struct": {:hex, :typed_struct, "0.2.1", "e1993414c371f09ff25231393b6430bd89d780e2a499ae3b2d2b00852f593d97", [:mix], [], "hexpm", "8f5218c35ec38262f627b2c522542f1eae41f625f92649c0af701a6fab2e11b3"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, From 66d5ec6b8096df5d8b0a3e94b58c9d52a2ccef72 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 7 Jul 2023 15:47:55 +0200 Subject: [PATCH 02/20] docs: querymanager --- lib/arke/query_manager.ex | 238 ++++++++++++++++++++------------------ 1 file changed, 127 insertions(+), 111 deletions(-) diff --git a/lib/arke/query_manager.ex b/lib/arke/query_manager.ex index 0be8ea0..741c144 100644 --- a/lib/arke/query_manager.ex +++ b/lib/arke/query_manager.ex @@ -17,24 +17,25 @@ defmodule Arke.QueryManager do Module to manage the CRUD operations to create the below elements and also manage the query to get the elements from db. ## `arke` - - Parameter => `Arke.Core.Parameter` - - Arke => `Arke.Core.Arke` - - Unit => `Arke.Core.Unit` - - Group => `Arke.Core.Group` - - ## `operators` - - eq => equal => `=` - - contains => contains a value (Case sensitive) => `LIKE %word%` - - icontains => contains a value (Not case sensitive) => `LIKE %word%` - - startswith => starts with the given value (Case sensitive) => `LIKE %word` - - istartswith => starts with the given value (Not case sensitive) => `LIKE %word` - - endswith => ends with the given value (Case sensitive) => `LIKE word%` - - iendswith => ends with the given value (Not case sensitive) => `LIKE word%` - - lte => less than or equal => `<=` - - lt => less than => `<` - - gt => greater than => `>` - - gte => greater than or equal => `>=` - - in => value is in a collection => `IN` + - Parameter -> `Arke.Core.Parameter` + - Arke -> `Arke.Core.Arke` + - Unit -> `Arke.Core.Unit` + - Group -> `Arke.Core.Group` + + ## `Operators` + - `eq` -> equal -> `=` + - `contains` -> contains a value (Case sensitive) -> `LIKE %word%` + - `icontains` -> contains a value (Not case sensitive) -> `ILIKE %word%` + - `startswith` -> starts with the given value (Case sensitive) -> `LIKE %word` + - `istartswith` -> starts with the given value (Not case sensitive) -> `ILIKE %word` + - `endswith` -> ends with the given value (Case sensitive) -> `LIKE word%` + - `iendswith` -> ends with the given value (Not case sensitive) -> `ILIKE word%` + - `lte` -> less than or equal -> `<=` + - `lt` -> less than -> `<` + - `gt` -> greater than -> `>` + - `gte` -> greater than or equal -> `>=` + - `in` -> value is in a collection -> `IN` + - `isnull` -> value is_nil -> `IS NULL` """ alias Arke.Boundary.{ArkeManager, ParameterManager, GroupManager, ParamsManager} @@ -62,15 +63,13 @@ defmodule Arke.QueryManager do | :isnull @doc """ - Create a new query - - ## Parameter - - opts => %{map} || [keyword: value] || key1: value1, key2: value2 => map containing the project and the arke where to apply the query + Create a new query. ## Example iex> Arke.QueryManager.query(project: :public) """ - @spec query(list()) :: Query.t() + @spec query(opts :: [project: String.t() | atom()]) :: + Query.t() def query(opts) do project = Keyword.get(opts, :project) arke = get_arke(Keyword.get(opts, :arke), project) @@ -81,12 +80,12 @@ defmodule Arke.QueryManager do Create a new topology query ## Parameter - - query => refer to `query/1` - - unit => %{arke_struct} => struct of the unit used as reference for the query - - opts => [keyword: value] => KeywordList containing the link conditions - - depth => int => max depth of the topoplogy - - direction => :atom => :child/:parent => the direction of the link. From parent to child or viceversa - - connection_type => string => name of the connection where to search + - `query` -> refer to `query/1` + - `unit` -> struct of the unit used as reference for the query + - `opts` -> KeywordList containing the link conditions + - `depth` max depth of the topoplogy + - `direction` --> the direction of the link. From parent to child or viceversa + - `connection_type` -> name of the connection to search ## Example @@ -98,7 +97,11 @@ defmodule Arke.QueryManager do %Arke.Core.Query{} """ - @spec link(Query.t(), unit :: Unit.t(), opts :: list()) :: Query.t() + @spec link( + Query.t(), + unit :: Unit.t(), + opts :: [direction: :child | :parent, depth: integer(), type: String.t()] + ) :: Query.t() def link(query, unit, opts \\ []) do direction = Keyword.get(opts, :direction, :child) depth = Keyword.get(opts, :depth, 500) @@ -120,12 +123,14 @@ defmodule Arke.QueryManager do defp parse_link_direction(direction), do: direction @doc """ - Function to create an element + Function to create a Unit. + + It calls the persistence_function `:create` set under (:arke -> :persistence -> :arke_postgres -> :create) in the configuration file ## Parameters - - project => :atom => identify the `Arke.Core.Project` - - arke => {arke_struct} => identify the struct of the element we want to create - - args => [list] => list of key: value we want to assign to the {arke_struct} above + - `project` -> identify the `Arke.Core.Project` + - `arke` -> identify the struct of the element we want to create + - `args` -> list of key: value we want to assign to the {arke_struct} above ## Example iex> string = ArkeManager.get(:string, :default) @@ -136,7 +141,7 @@ defmodule Arke.QueryManager do """ - @spec create(project :: atom(), arke :: Arke.t(), args :: list()) :: func_return() + @spec create(project :: atom(), arke :: Arke.t(), args :: [...]) :: func_return() def create(project, arke, args) do persistence_fn = @persistence[:arke_postgres][:create] @@ -151,12 +156,14 @@ defmodule Arke.QueryManager do end @doc """ - Function to update an element + Function to create a Unit. + + It calls the persistence_function `:update` set under (:arke -> :persistence -> :arke_postgres -> :update) in the configuration file ## Parameters - - project => :atom => identify the `Arke.Core.Project` - - unit => %{arke_struct} => unit to update - - args => [list] => list of key: value to update + - `project` -> identify the `Arke.Core.Project` + - `unit` -> unit to update + - `args` -> list of key: value to update ## Example iex> name = QueryManager.get_by(id: "name") @@ -184,12 +191,15 @@ defmodule Arke.QueryManager do @doc """ Function to delete a given unit + + It calls the persistence_function `:delete` set under (:arke -> :persistence -> :arke_postgres -> :delete) in the configuration file + ## Parameters - - project => :atom => identify the `Arke.Core.Project` - - unit => %{arke_struct} => the unit to delete + - `project` -> identify the `Arke.Core.Project` + - `unit` -> the unit to delete ## Example - iex> element = Arke.QueryManager.get_by(id: "name") - ...> Arke.QueryManager.delete(element) + iex> unit = Arke.QueryManager.get_by(id: "name") + ...> Arke.QueryManager.delete(:test_project, unit) ## Returns {:ok, _} @@ -208,9 +218,10 @@ defmodule Arke.QueryManager do end @doc """ - Function to get a single element identified by the opts. Use `Arke.QueryManager.filter_by` if more than one element is returned + Create a query which is used to get a single element which match the given criteria + ## Parameters - - opts => %{map} || [keyword: value] || key1: value1, key2: value2 => identify the element to get + - `opts` -> used to identify the element to get ## Example iex> Arke.QueryManager.get_by(id: "name") @@ -219,10 +230,10 @@ defmodule Arke.QueryManager do def get_by(opts \\ []), do: basic_query(opts) |> one @doc """ - Function to get all the element which match the given criteria + Create a query which is used to get all the element which match the given criteria + ## Parameters - - opts => %{map} || [keyword: value] || key1: value1, key2: value2 => identify the element to get - - operator => :atom => refer to [operators](#module-operators) + - `opts` -> identify the element to get ## Example iex> Arke.QueryManager.filter_by(id: "name") @@ -230,7 +241,7 @@ defmodule Arke.QueryManager do ## Return [ Arke.Core.Unit{}, ...] """ - @spec filter_by(opts :: list()) :: [Unit.t()] | [] + @spec filter_by(opts :: [...]) :: [Unit.t()] | [] def filter_by(opts \\ []), do: basic_query(opts) |> all defp basic_query(opts) when is_map(opts), do: Map.to_list(opts) |> basic_query @@ -261,9 +272,9 @@ defmodule Arke.QueryManager do Add an `:and` logic to a query ## Parameter - - query => refer to `query/1` - - negate => boolean => used to figure out whether the condition is to be denied - - filters => refer to `condition/3 | conditions/1` + - query -> refer to `query/1` + - negate -> boolean -> used to figure out whether the condition is to be denied + - filters -> refer to `condition/3 | conditions/1` ## Example iex> query = QueryManager.query(arke: nil, project: :arke_system) @@ -282,9 +293,9 @@ defmodule Arke.QueryManager do Add an `:or` logic to a query ## Parameter - - query => refer to `query/1` - - negate => boolean => used to figure out whether the condition is to be denied - - filters => refer to `condition/3 | conditions/1` + - query -> refer to `query/1` + - negate -> boolean -> used to figure out whether the condition is to be denied + - filters -> refer to `condition/3 | conditions/1` ## Example iex> query = QueryManager.query(arke: nil, project: :arke_system) @@ -310,10 +321,10 @@ defmodule Arke.QueryManager do Create a `Arke.Core.Query.BaseFilter` ## Parameters - - parameter => :atom | %Arke.Core.Arke{} => the parameter where to check the condition - - operator => :atom => refer to [operators](#module-operators) - - value => string | boolean | nil => the value the parameter and operator must check - - negate => boolean => used to figure out whether the condition is to be denied + - `parameter` -> the parameter where to check the condition + - `operator` -> refer to [operators](#module-operators) + - `value` -> it will be parsed against the parameter type else it will return an error. + - `negate` -> used to figure out whether the condition is to be denied ## Example iex> QueryManager.condition(:string, :eq, "test") @@ -322,9 +333,9 @@ defmodule Arke.QueryManager do %Arke.Core.Query.BaseFilter{} """ @spec condition( - parameter :: Arke.t() | atom(), + parameter :: Unit.t(), negate :: boolean(), - value :: String.t() | boolean() | nil, + value :: String.t() | boolean() | number() | nil, negate :: boolean() ) :: Query.BaseFilter.t() def condition(parameter, operator, value, negate \\ false), @@ -334,10 +345,11 @@ defmodule Arke.QueryManager do Create a list of `Arke.Core.Query.BaseFilter` ## Parameter - - opts => %{map} || [keyword: value] || key1: value1, key2: value2 => the condtions used to create the BaseFilters + - `opts` -> the condtions used to create the BaseFilters. + The key of the opts must be written as: parameter__operator ## Example - iex> QueryManager.conditions(parameter__eq: "test", string__contains: "t") + iex> QueryManager.conditions(name__eq: "test", string__contains: "t") ## Return [ %Arke.Core.Query.BaseFilter{}, ...] @@ -351,11 +363,11 @@ defmodule Arke.QueryManager do end @doc """ - Create query with specific options + Create query with specific filter. For the `opts` refer to `conditions/1` ## Parameters - - query => refer to `query/1` - - opts => %{map} || [keyword: value] || key1: value1, key2: value2 => keyword list containing the filter to apply + - `query` -> refer to `query/1` + - `opts` -> keyword list containing the filter to apply ## Example iex> query = Arke.QueryManager.query() @@ -376,39 +388,25 @@ defmodule Arke.QueryManager do end @doc """ - Filter of the query + It creates a filter for the given query ## Parameters - - query => refer to `query/1` - - filter => refer to `Arke.Core.Query.Filter` + - `query` -> refer to `query/1` + - `filter` -> refer to `Arke.Core.Query.Filter` ## Example iex> query = Arke.QueryManager.Query.t ...> parameter = Arke.Boundary.ParameterManager.get(:id,:arke_system) ...> Arke.Core.Query.new_filter(parameter,:equal,"name",false) - ...> Arke.Core.Query.filter(query, filter + ...> Arke.Core.Query.filter(query, filter) + """ @spec filter(query :: Query.t(), filter :: Query.Filter.t()) :: Query.t() def filter(query, filter), do: Query.add_filter(query, filter) @doc """ - Filter of the query - - ## Parameters - - query => refer to `query/1` - - parameter => %{arke_struct} => arke struct of the parameter - - operator => :atom => refer to [operators](#module-operators) - - value => string | boolean | nil => the value the parameter and operator must check - - negate => boolean => used to figure out whether the condition is to be denied - - ## Example - iex> query = Arke.QueryManager.query() - ...> QueryManager.filter(query, Arke.Core.Query.new_filter(Arke.Boundary.ParameterManager.get(:id,:default),:equal,"name",false)) - - ## Return - %Arke.Core.Query{...} - + It creates a filter for the given query """ @spec filter( query :: Query.t(), @@ -452,8 +450,9 @@ defmodule Arke.QueryManager do Define a criteria to order the element returned from a query ## Parameter - - query => refer to `query/1` - - order => int => number of element to return + - `query` -> refer to `query/1` + - `parameter1 -> used to order the query + - `direction` -> way of sorting the results ## Example iex> query = QueryManager.query() @@ -464,7 +463,7 @@ defmodule Arke.QueryManager do @spec order( query :: Query.t(), parameter :: Arke.t() | String.t() | atom(), - direction :: atom() + direction :: :asc | :desc ) :: Query.t() def order(query, parameter, direction), do: Query.add_order(query, get_parameter(query, parameter), direction) @@ -473,8 +472,8 @@ defmodule Arke.QueryManager do Set the offset of the query ## Parameter - - query => refer to `query/1` - - offset => int => offset of the query + - `query` -> refer to `query/1` + - `offset` -> offset of the query ## Example iex> query = QueryManager.query() @@ -488,17 +487,32 @@ defmodule Arke.QueryManager do Set the limit of the element to be returned from a query ## Parameter - - query => refer to `query/1` - - limit => int => number of element to return + - `query` -> refer to `query/1` + - `limit` -> number of element to return ## Example - iex> query = QueryManager..query() + iex> query = QueryManager.query() ...> QueryManager.where(query, id: "name") |> QueryManager.limit(1) """ @spec limit(query :: Query.t(), limit :: integer()) :: Query.t() def limit(query, limit), do: Query.set_limit(query, limit) + @doc """ + Get both the total count of the elements and the elements returned from the query + + ## Parameter + - `query` -> refer to `query/1` + - `offset` -> offset of the query + - `limit` -> number of element to return + + ## Example + iex> query = QueryManager.query() + ...> QueryManager.pagination(query, 0,5) + + """ + @spec pagination(query :: Query.t(), offset :: integer(), limit :: integer()) :: + {count :: integer(), elements :: [] | [Unit.t()]} def pagination(query, offset, limit) do tmp_query = %{query | orders: []} count = count(tmp_query) @@ -507,53 +521,55 @@ defmodule Arke.QueryManager do end @doc """ - Return all the results from a query + Run the `execute_query` set in the configuration under (:arke -> :persistence -> :arke_postgres -> :execute_query) with `:all` as the second parameter ## Parameter - - query => refer to `query/1` + - query -> refer to `query/1` + + ## Example + iex> query = QueryManager.query() + ...> QueryManager.where(query, id: "name") |> QueryManager.all() """ @spec all(query :: Query.t()) :: [Unit.t()] | [] def all(query), do: execute_query(query, :all) @doc """ - Return the first result of a query + + Run the `execute_query` set in the configuration under (:arke -> :persistence -> :arke_postgres -> :execute_query) with `:one` as the second parameter ## Parameter - - query => refer to `query/1` + - `query` -> refer to `query/1` + """ @spec one(query :: Query.t()) :: Unit.t() | nil def one(query), do: execute_query(query, :one) @doc """ - Return the query as a string + Run the `execute_query` set in the configuration under (:arke -> :persistence -> :arke_postgres -> :execute_query) with `:raw` as the second parameter ## Parameter - - query => refer to `query/1` + - `query` -> refer to `query/1` """ @spec raw(query :: Query.t()) :: String.t() def raw(query), do: execute_query(query, :raw) @doc """ - Return the count of the element returned from a query + Run the `execute_query` set in the configuration under (:arke -> :persistence -> :arke_postgres -> :execute_query) with `:count` as the second parameter ## Parameter - - query => refer to `query/1` + - `query` -> refer to `query/1` + + ## Returns + integer """ @spec count(query :: Query.t()) :: integer() def count(query), do: execute_query(query, :count) @doc """ - Return a string which represent the query itself + Run the `execute_query` set in the configuration under (:arke -> :persistence -> :arke_postgres -> :execute_query) with `:pseudo_query` as the second parameter ## Parameter - - query => refer to `query/1` - - ## Example - iex> query = QueryManager.query() - ...> QueryManager.where(query, id: "name") |> QueryManager.pseudo_query - - ## Return - #Ecto.Query<> + - `query` -> refer to `query/1` """ @spec pseudo_query(query :: Query.t()) :: Ecto.Query.t() def pseudo_query(query), do: execute_query(query, :pseudo_query) From 80a49f4fe084bd282161df946e1354aa4e736fce Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 7 Jul 2023 16:21:34 +0200 Subject: [PATCH 03/20] docs: struct_manager --- lib/arke/struct_manager.ex | 72 ++++++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 18 deletions(-) diff --git a/lib/arke/struct_manager.ex b/lib/arke/struct_manager.ex index 9ae718a..e8a4519 100644 --- a/lib/arke/struct_manager.ex +++ b/lib/arke/struct_manager.ex @@ -38,17 +38,18 @@ defmodule Arke.StructManager do Function that encodes a Unit or list of Unit ## Parameters - - unit => [%{arke_struct}] | %{arke_struct} => unit or list of units that we want to encode - - type => :json => desired encode type + - `unit` -> unit or list of units that we want to encode + - `type` -> desired encode type ## Example iex> units = QueryManager.filter_by(arke_id: id) ...> StructManager.encode(units, type: :json) + ## Returns + All the given units encoded based on the given type + """ - @spec encode(unit :: Unit.t(), format :: atom()) :: %{ - key: String.t() | number() | boolean() | atom() - } + @spec encode(unit :: [Unit.t(), ...], format :: :json) :: %{atom() => String.t()} | [...] def encode(unit, opts \\ []) def encode(unit, opts) do @@ -110,8 +111,6 @@ defmodule Arke.StructManager do defp handle_load_link(_, _, false, _opts), do: [] defp handle_load_link(unit, project, true, opts) do - # IO.inspect("query done for link load") - get_link_id_list(unit) |> get_link_units(project, opts) end @@ -161,6 +160,34 @@ defmodule Arke.StructManager do end def encode(_unit, _format), do: raise("Must pass a valid unit") + + + @doc """ + Validates the given data for links parameter + + ## Parameters + - `id` -> parameter to valorize + - `value` -> desired value + - `arke` -> Arke to look for the parameter + - `opts` -> options + + ## Example + iex> units = QueryManager.filter_by(arke_id: id) + ...> StructManager.encode(units, type: :json) + + ## Returns + All the given units encoded based on the given type + + """ + @spec validate_data( + id :: String.t() | atom(), + value :: any(), + arke :: Unit.t(), + opts :: [] | [...] + ) :: %{ + parameters: [parameter()], + label: String.t() + } def validate_data(id, value, arke, opts \\ []) def validate_data(id, value, arke, opts) do @@ -202,10 +229,10 @@ defmodule Arke.StructManager do Function that decodes data into a Unit or list of Unit ## Parameters - - project => :atom => identify the `Arke.Core.Project` - - arke_id => atom | string => arke id - - json => %{key: value} => json data that we want to decode - - type => :json => data input type + - `project` -> identify the `Arke.Core.Project` + - `arke_id` -> arke id + - `json` -> json data that we want to decode + - `type` -> data input type ## Example iex> StructManager.decode(:arke, my_json_data, :json) @@ -282,10 +309,10 @@ defmodule Arke.StructManager do defp handle_default_value(_, value), do: value @doc """ - Function that returns a Struct that describes an Arke or a Unit + Function to get a Unit Struct ## Parameters - - unit => %Unit{} | %Arke{} => unit or arke struct + - `unit` -> unit struct ## Example iex> arke = ArkeManager.get(:test, :default) @@ -296,6 +323,13 @@ defmodule Arke.StructManager do %{parameters: get_struct_parameters(ArkeManager.get_parameters(arke), %{}), label: data.label} end + @doc """ + Function to get a Unit Struct + """ + @spec get_struct(arke :: Unit.t(), unit :: Unit.t(), opts :: [] | [...]) :: %{ + parameters: [parameter()], + label: String.t() + } def get_struct(arke, %{data: data} = unit, opts) do %{ parameters: get_struct_parameters(ArkeManager.get_parameters(arke), unit, opts), @@ -303,6 +337,13 @@ defmodule Arke.StructManager do } end + @doc """ + Function to get a Unit Struct + """ + @spec get_struct(arke :: Unit.t(), unit :: Unit.t()) :: %{ + parameters: [parameter()], + label: String.t() + } def get_struct(arke, %{data: data} = unit) do %{ parameters: get_struct_parameters(ArkeManager.get_parameters(arke), unit, %{}), @@ -310,10 +351,6 @@ defmodule Arke.StructManager do } end - @spec get_struct(arke :: Unit.t(), opts :: list()) :: %{ - parameters: [parameter()], - label: String.t() - } def get_struct(%{arke_id: :arke, data: data} = arke, opts) do %{ parameters: get_struct_parameters(ArkeManager.get_parameters(arke), opts), @@ -454,7 +491,6 @@ defmodule Arke.StructManager do defp add_type_fields( {%{arke_id: :link, data: data, metadata: %{project: project}} = _parameter, base_data} ) do - Map.merge(base_data, %{ default: data.default_link, multiple: data.multiple, From a41c04deead736b5f62b1ff5cba07dd875a25ca4 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 7 Jul 2023 16:21:52 +0200 Subject: [PATCH 04/20] docs: add logo --- public/arke-logo.png | Bin 0 -> 150708 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/arke-logo.png diff --git a/public/arke-logo.png b/public/arke-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d09ba60c24081eac047ee0885ae27a192922ddd3 GIT binary patch literal 150708 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 Date: Mon, 10 Jul 2023 17:35:42 +0200 Subject: [PATCH 05/20] docs: add unit_manager --- lib/arke/unit_manager.ex | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/arke/unit_manager.ex b/lib/arke/unit_manager.ex index a427232..22605bb 100644 --- a/lib/arke/unit_manager.ex +++ b/lib/arke/unit_manager.ex @@ -13,6 +13,13 @@ # limitations under the License. defmodule Arke.UnitManager do + @moduledoc """ + Module for the GenServer management o a Unit + """ + @doc """ + Macro that is used to standardize the creation of GenServer. + It makes available a set of functions useful for arke management + """ defmacro __using__(_) do quote do use GenServer @@ -80,6 +87,9 @@ defmodule Arke.UnitManager do } end + @doc """ + Get all the actives GenServers in a given project + """ def get_all(project \\ :arke_system) do supervisor_name |> DynamicSupervisor.which_children() @@ -87,8 +97,16 @@ defmodule Arke.UnitManager do |> Enum.flat_map(&active_managers(&1, project, nil)) end + @doc """ + Terminate the GenServer corresponding to the given unit + """ + @spec remove(unit :: Unit.t()) :: {:error, msg :: String.t()} | :ok def remove(%{id: id, metadata: %{project: project}} = unit), do: remove(id, project) + @doc """ + Terminate the GenServer corresponding to the given unit + """ + @spec remove(unit_id :: atom(), project :: atom()) :: {:error, msg :: String.t()} | :ok def remove(unit_id, project) do case get_pid(unit_id, project) do {:error, msg} -> @@ -104,6 +122,9 @@ defmodule Arke.UnitManager do end end + @doc """ + Start a new GenServer for the given unit + """ def create(%{id: id, metadata: %{project: project}} = unit), do: create(unit, project) def create(unit, project) do @@ -115,6 +136,9 @@ defmodule Arke.UnitManager do ) end + @doc """ + Overridable function called in `QueryManager.create/3` + """ def before_create(unit, project), do: {unit, project} defp child_pid?({:undefined, pid, :worker, [__MODULE__]}) when is_pid(pid), do: true @@ -144,6 +168,9 @@ defmodule Arke.UnitManager do |> Enum.any?() end + @doc """ + Get server process id + """ def get_pid(%{id: id, metadata: %{project: project}} = unit), do: get_pid(id, project) def get_pid(unit_id, project) do @@ -158,8 +185,8 @@ defmodule Arke.UnitManager do Return a struct with all the parameter associated for the given double schema_id, project in its GenServer. ## Parameters - - arke_id => :atom => identify the schema - - project => :atom => identify the schema's project + - `arke_id` -> identify the schema + - `project` -> identify the schema's project ## Examples iex> ArkeManager.get_schema(:arke_schema, :default) @@ -180,6 +207,9 @@ defmodule Arke.UnitManager do end end + @doc """ + Function called when a unit is being updated. It will update the status stored in the GenServer + """ def update(%{id: id, metadata: %{project: project}} = unit, new_unit), do: update(id, project, new_unit) @@ -188,6 +218,9 @@ defmodule Arke.UnitManager do GenServer.call(via({unit_id, project}), {:update, new_unit}) end + @doc """ + Get the name of the module where the Arke struct is defined + """ def get_module(%{id: id, metadata: %{project: project}} = unit), do: get_module(id, project) def get_module(unit_id, project) do From cdf5b16c1a863eaadf4f01f6a9c5ea50cf98bcda Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 10 Jul 2023 17:46:57 +0200 Subject: [PATCH 06/20] docs: add validator --- lib/arke/validator.ex | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/lib/arke/validator.ex b/lib/arke/validator.ex index 4de3c9a..cff70ff 100644 --- a/lib/arke/validator.ex +++ b/lib/arke/validator.ex @@ -28,22 +28,29 @@ defmodule Arke.Validator do Function to check the given data based on the fields in the reference schema. ## Parameters - - unit => => %Arke.Core.Unit{} => unit to add - - persistence_fn => fun.() => function containng the action that will be performed on the Repo - - project => :atom => identify the `Arke.Core.Project` + - `unit` -> unit to validate + - `persistence_fn` -> operation identifiers + - `project` -> `Arke.Core.Project` ## Example - iex> schema = Arke.Core.Arke.new - ...> param = Arke.Core.Parameter.new(%{type: :string,opts: [id: :name]}) - ...> schema = Arke.Core.Arke.add_parameter(schema, param) - ...> Arke.Validator.validate(%{arke: schema, data: %{name: "test"}}) + iex> arke = ArkeManager.get(:arke, :arke_system) + ...> unit = Arke.Core.Unit.load(arke, %{ + id: :test, + label: "Test", + type: "arke", + active: true, + parameters: [], + metadata: %{} + }) + + ...> Arke.Validator.validate(unit, :update, :test_schema) ## Return %{:ok,_} %{:error, [message]} """ - @spec validate(unit :: Unit.t(), peristence_fn :: (() -> any()), project :: atom()) :: + @spec validate(unit :: Unit.t(), peristence_fn :: :create | :update, project :: atom()) :: func_return() def validate(%{arke_id: arke_id} = unit, persistence_fn, project \\ :arke_system) do with {:ok, unit} <- check_duplicate_unit(unit, project, persistence_fn) do @@ -113,13 +120,14 @@ defmodule Arke.Validator do Check if the value can be assigned to a given parameter in a specific schema struct. ## Parameters - - schema_struct => %{arke_struct} => the element where to find and check the field - - field => :atom => the id of the paramater - - value => any => the value we want to assign to the above field - - project => :atom => identify the `Arke.Core.Project` + - `schema_struct` -> the element where to find and check the field + - `field` -> the id of the paramater + - `value` -> the value we want to assign to the above field + - `project` -> identify the `Arke.Core.Project` ## Example - iex> Arke.Boundary.ArkeValidator.validate_field(schema_struct, :field_id, value_to_check) + iex> arke = ArkeManager.get(:arke, :arke_system) + ...> Arke.Boundary.ArkeValidator.validate_field(arke, :label, "test") ## Returns {value,[]} if success @@ -128,7 +136,7 @@ defmodule Arke.Validator do @spec validate_parameter( arke :: Arke.t(), parameter :: Sring.t() | atom() | Parameter.parameter_struct(), - value :: String.t() | number() | atom() | boolean() | map() | list(), + value :: any(), project :: atom() ) :: func_return() def validate_parameter(arke, parameter, value, project \\ :arke_system) @@ -162,8 +170,8 @@ defmodule Arke.Validator do {value, errors} end - def get_default_value(parameter, value) when is_nil(value), do: handle_default_value(parameter) - def get_default_value(parameter, value), do: value + defp get_default_value(parameter, value) when is_nil(value), do: handle_default_value(parameter) + defp get_default_value(parameter, value), do: value defp parse_value(%{arke_id: :integer, data: %{multiple: false} = data} = _, value) when not is_integer(value) and not is_nil(value) do From 351ec87574881fa0c7f292c589cc82e367764284 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 10 Jul 2023 18:07:58 +0200 Subject: [PATCH 07/20] docs: add system --- lib/arke/core/file.ex | 1 + lib/arke/system.ex | 94 ++++++++++++++++++++++++++++--------------- 2 files changed, 62 insertions(+), 33 deletions(-) diff --git a/lib/arke/core/file.ex b/lib/arke/core/file.ex index 41b08a6..82a284f 100644 --- a/lib/arke/core/file.ex +++ b/lib/arke/core/file.ex @@ -44,6 +44,7 @@ defmodule Arke.Core.File do end def before_update(_, %{binary: binary} = unit) when is_nil(binary), do: {:ok, unit} + def before_update(_, %{data: %{name: name, path: path, binary: binary}} = unit) do case Gcp.upload_file("#{path}/#{name}", binary) do {:ok, _object} -> {:ok, unit} diff --git a/lib/arke/system.ex b/lib/arke/system.ex index c26c609..4c4ab49 100644 --- a/lib/arke/system.ex +++ b/lib/arke/system.ex @@ -13,6 +13,15 @@ # limitations under the License. defmodule Arke.System do + @moduledoc """ + Module which manage the creation of every Arke struct + """ + + @doc """ + Macro to simplify the creation of a new Arke + + use Arke.System + """ defmacro __using__(_) do quote do # @after_compile __MODULE__ @@ -37,16 +46,59 @@ defmodule Arke.System do unit.data.parameters end + @doc """ + Overridable function in order to be able to edit data during the unit load + """ def on_load(data, _persistence_fn), do: {:ok, data} + + @doc """ + Overridable function in order to be able to edit data before the load + """ def before_load(data, _persistence_fn), do: {:ok, data} + + @doc """ + Overridable function in order to be able to edit data during the validation + """ def on_validate(arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data before the validation + """ def before_validate(arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data during the creation + """ def on_create(arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data before the creation + """ def before_create(arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data during the encoding + """ def on_struct_encode(unit, _), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data on the update + """ def on_update(arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data before the update + """ def before_update(arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data during the deletion + """ def on_delete(arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data before the deletion + """ def before_delete(arke, unit), do: {:ok, unit} defoverridable on_load: 2, @@ -63,21 +115,12 @@ defmodule Arke.System do end end - # defmacro __before_compile__(env) do - # end - # - # def compile(translations) do - # - # end - ###################################################################################################################### # ARKE MACRO ######################################################################################################### ###################################################################################################################### @doc """ - Macro to create an arke struct with the given parameters. - Usable only via `code` and not `iex`. - + Macro to create an arke struct with the given parameters ## Example arke do @@ -85,11 +128,9 @@ defmodule Arke.System do parameter :custom_parameter2, :string, required: true, values: ["value1", "value2"] parameter :custom_parameter3, :integer, required: true, values: [%{label: "option 1", value: 1},%{label: "option 2", value: 2}] parameter :custom_parameter4, :dict, required: true, default: %{"default_dict_key": "default_dict_value"} + parameter :custom_parameter5, :type, ...opts end - ## Return - %Arke.Core.'{arke_struct}'{} - """ @spec arke(args :: list(), Macro.t()) :: %{} defmacro arke(opts \\ [], do: block) do @@ -148,20 +189,6 @@ defmodule Arke.System do defp get_base_arke_parameters(_type), do: nil - # @spec __arke_info__(caller :: caller(), options :: list()) :: [id: atom() | String.t(), label: String.t(), active: boolean(), metadata: map(), type: atom()] - # defp __arke_info__(caller, options) do - # - # id = Keyword.get(options, :id, caller |> to_string |> String.split(".") |> List.last |> Macro.underscore |> String.to_atom) - # label = Keyword.get(options, :label, id |> Atom.to_string |> String.replace("_", " ") |> String.capitalize) - # [ - # id: id, - # label: label, - # active: Keyword.get(options, :active, true), - # metadata: Keyword.get(options, :metadata, %{}), - # type: Keyword.get(options, :type, :arke) - # ] - # end - ###################################################################################################################### # END ARKE MACRO ##################################################################################################### ###################################################################################################################### @@ -227,18 +254,13 @@ defmodule Arke.System.BaseParameter do [%{label "given label", value: given_value}, %{label "given label two ", value: given_value_two}] Keep in mind that if the values are declared as list instead of map the label will be generated from the value itself. - ... omitted code parameter :custom_parameter2, :integer, required: true, values: [1, 2, 3] - ... omitted code - The code above will results in an `{arke_struct}` with the values as follows - - ... omitted code + The code above will be modified to be as follows values: [%{label "1", value: 1}, %{label "2", value: 2}, %{label "3", value: 3}] - ... omitted code """ @spec parameter_options(opts :: list(), id :: atom(), type :: atom()) :: %{ @@ -254,6 +276,12 @@ defmodule Arke.System.BaseParameter do %{type: type, opts: opts} end + @doc """ + It checks if the given values match the type of the parameter. + + """ + @spec check_enum(type :: :string | :integer | :float, opts :: [...] | []) :: + [...] | [] | [%{label: String.t(), value: float() | integer() | String.t()}, ...] def check_enum(type, opts) do enum_parameters = [:string, :integer, :float] From 6be0e3fe3beb440f9eca58ec280f8a864fd6cea7 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 16 May 2024 15:51:57 +0200 Subject: [PATCH 08/20] docs: remove managers modules --- README.md | 12 +++++------- lib/arke.ex | 1 + lib/arke/boundary/arke_manager.ex | 7 ++++--- lib/arke/boundary/group_manager.ex | 6 +++++- lib/arke/boundary/parameter_manager.ex | 7 ++++--- lib/arke/boundary/unit_manager.ex | 4 ++++ lib/arke/unit_manager.ex | 0 mix.lock | 4 ++++ 8 files changed, 27 insertions(+), 14 deletions(-) delete mode 100644 lib/arke/unit_manager.ex diff --git a/README.md b/README.md index 1db9b1f..cac9164 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,12 @@ ![Arke](https://github.com/arkemishub/arke/assets/81776297/7a04d11b-5cd0-4349-8621-d19cf0274585) -## Installation +## Documentation + +In depth documentation can be found at [https://hexdocs.pm/arke](https://hexdocs.pm/arke), while a more generic can be found [here](https://docs.arkehub.com/docs) -If [available in Hex](https://hex.pm/docs/publish), the package can be installed -by adding `arke` to your list of dependencies in `mix.exs`: +## Installation +The package can be installed by adding `arke` to your list of dependencies in `mix.exs`: ```elixir def deps do @@ -15,7 +17,3 @@ def deps do end ``` -Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) -and published on [HexDocs](https://hexdocs.pm). Once published, the docs can -be found at [https://hexdocs.pm/arke](https://hexdocs.pm/arke). - diff --git a/lib/arke.ex b/lib/arke.ex index fbcf7f3..2b3d399 100644 --- a/lib/arke.ex +++ b/lib/arke.ex @@ -13,6 +13,7 @@ # limitations under the License. defmodule Arke do + @moduledoc false alias Arke.Core.Unit alias Arke.Boundary.{ArkeManager, GroupManager, ParameterManager} alias Arke.System.BaseParameter diff --git a/lib/arke/boundary/arke_manager.ex b/lib/arke/boundary/arke_manager.ex index 83d30ac..3a65b14 100644 --- a/lib/arke/boundary/arke_manager.ex +++ b/lib/arke/boundary/arke_manager.ex @@ -13,9 +13,10 @@ # limitations under the License. defmodule Arke.Boundary.ArkeManager do - @moduledoc """ - This module manage the gen servers for the element specified in `Arke.Core.Arke` - """ + @moduledoc false && + """ + It extends `Arke.Boundary.UnitManager` by providing more functions specific to the Arkes. + """ alias Arke.Utils.ErrorGenerator, as: Error use Arke.Boundary.UnitManager diff --git a/lib/arke/boundary/group_manager.ex b/lib/arke/boundary/group_manager.ex index 14dc073..32af1ff 100644 --- a/lib/arke/boundary/group_manager.ex +++ b/lib/arke/boundary/group_manager.ex @@ -13,7 +13,11 @@ # limitations under the License. defmodule Arke.Boundary.GroupManager do - @moduledoc false + @moduledoc false && + """ + It extends `Arke.Boundary.UnitManager` by providing more functions specific to the Groups. + """ + use Arke.Boundary.UnitManager alias Arke.Utils.ErrorGenerator, as: Error diff --git a/lib/arke/boundary/parameter_manager.ex b/lib/arke/boundary/parameter_manager.ex index 08fcb5d..b19dc47 100644 --- a/lib/arke/boundary/parameter_manager.ex +++ b/lib/arke/boundary/parameter_manager.ex @@ -13,12 +13,13 @@ # limitations under the License. defmodule Arke.Boundary.ParameterManager do - @moduledoc false + @moduledoc false && + """ + It extends `Arke.Boundary.UnitManager` by providing more functions specific to the Parameters. + """ use Arke.Boundary.UnitManager manager_id(:parameter) - # set_registry_name(:parameter_registry) - # set_supervisor_name(:parameter_supervisor) end diff --git a/lib/arke/boundary/unit_manager.ex b/lib/arke/boundary/unit_manager.ex index 1fe19c4..159fae1 100644 --- a/lib/arke/boundary/unit_manager.ex +++ b/lib/arke/boundary/unit_manager.ex @@ -13,6 +13,10 @@ # limitations under the License. defmodule Arke.Boundary.UnitManager do + @moduledoc false &&""" + This module handle all the managers providing functions to create/delete/edit such managers. + The managers stores the struct of a Unit. Such struct is used in the validation process. + """ defmacro __using__(_) do quote do use GenServer diff --git a/lib/arke/unit_manager.ex b/lib/arke/unit_manager.ex deleted file mode 100644 index e69de29..0000000 diff --git a/mix.lock b/mix.lock index 965d72f..5ea3344 100644 --- a/mix.lock +++ b/mix.lock @@ -1,10 +1,12 @@ %{ "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, + "calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.1.201603 or ~> 0.5.20 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"}, "castore": {:hex, :castore, "1.0.3", "7130ba6d24c8424014194676d608cb989f62ef8039efd50ff4b3f33286d06db8", [:mix], [], "hexpm", "680ab01ef5d15b161ed6a95449fac5c6b8f60055677a8e79acf01b27baa4390b"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, "earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"}, + "erlsom": {:hex, :erlsom, "1.5.1", "c8fe2babd33ff0846403f6522328b8ab676f896b793634cfe7ef181c05316c03", [:rebar3], [], "hexpm", "7965485494c5844dd127656ac40f141aadfa174839ec1be1074e7edf5b4239eb"}, "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, "excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"}, "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, @@ -20,6 +22,7 @@ "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, "jose": {:hex, :jose, "1.11.5", "3bc2d75ffa5e2c941ca93e5696b54978323191988eb8d225c2e663ddfefd515e", [:mix, :rebar3], [], "hexpm", "dcd3b215bafe02ea7c5b23dafd3eb8062a5cd8f2d904fd9caa323d37034ab384"}, + "libcluster": {:hex, :libcluster, "3.3.3", "a4f17721a19004cfc4467268e17cff8b1f951befe428975dd4f6f7b84d927fe0", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7c0a2275a0bb83c07acd17dab3c3bfb4897b145106750eeccc62d302e3bdfee5"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, @@ -40,4 +43,5 @@ "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"}, + "xlsxir": {:hex, :xlsxir, "1.6.4", "d1e69439cbd9edc1190950f9f883ac364e1f31641e0395ccdb27761791b169a3", [:mix], [{:erlsom, "~> 1.5", [hex: :erlsom, repo: "hexpm", optional: false]}], "hexpm", "38e91f65eb8a4c8dea07d941c8b7e21baf8c8d4938232395c9ffd19d2eb071f2"}, } From feb4b957e1991b3d083d592349b60e56cd259117 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 16 May 2024 16:08:10 +0200 Subject: [PATCH 09/20] docs: remove starter module --- lib/examples/starter.ex | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/examples/starter.ex b/lib/examples/starter.ex index b29671c..7fc5c47 100644 --- a/lib/examples/starter.ex +++ b/lib/examples/starter.ex @@ -13,9 +13,10 @@ # limitations under the License. defmodule Arke.Examples.Starter do - @moduledoc """ - Module to start all the defaults gen server - """ + @moduledoc false && + """ + Module to start all the defaults gen server + """ alias Arke.Boundary.{ArkeManager} @doc """ From f1ebd2f53581479d44bba1060c6eea46d9135488 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 16 May 2024 16:49:16 +0200 Subject: [PATCH 10/20] docs: query_manager --- lib/arke/query_manager.ex | 167 ++++++-------------------------------- 1 file changed, 26 insertions(+), 141 deletions(-) diff --git a/lib/arke/query_manager.ex b/lib/arke/query_manager.ex index 14f8350..35f4f5f 100644 --- a/lib/arke/query_manager.ex +++ b/lib/arke/query_manager.ex @@ -21,7 +21,7 @@ defmodule Arke.QueryManager do - Arke -> `Arke.Core.Arke` - Unit -> `Arke.Core.Unit` - Group -> `Arke.Core.Group` - + - Link -> `Arke.Core.Link` ## `Operators` - `eq` -> equal -> `=` - `contains` -> contains a value (Case sensitive) -> `LIKE %word%` @@ -66,10 +66,7 @@ defmodule Arke.QueryManager do | :isnull @doc """ - Create a new query. - - ## Example - iex> Arke.QueryManager.query(project: :public) + Create a new query """ @spec query(opts :: [project: String.t() | atom()]) :: Query.t() @@ -80,24 +77,15 @@ defmodule Arke.QueryManager do end @doc """ - Create a new topology query + Create a new topology query. It will get all the units related to the first one ## Parameter - `query` -> refer to `query/1` - `unit` -> struct of the unit used as reference for the query - `opts` -> KeywordList containing the link conditions - - `depth` max depth of the topoplogy - - `direction` --> the direction of the link. From parent to child or viceversa - - `connection_type` -> name of the connection to search - - ## Example - - iex> Arke.QueryManager.query(project: :public) - ...> unit = QueryManager.get_by([project: :arke_system, id: "test"]) - ...> QueryManager.link(query, unit) - - ## Return - %Arke.Core.Query{} + - `depth` max depth of the recursion. + - `direction` --> the direction of the link. One of `child` or `parent` + - `connection_type` -> name of the connection to search """ @spec link( @@ -126,23 +114,11 @@ defmodule Arke.QueryManager do defp parse_link_direction(direction), do: direction @doc """ - Function to create a Unit. - - It calls the persistence_function `:create` set under (:arke -> :persistence -> :arke_postgres -> :create) in the configuration file - + Function to create a Unit. It will load a Unit based on the given arke, which is used as a model, and the given data. ## Parameters - `project` -> identify the `Arke.Core.Project` - `arke` -> identify the struct of the element we want to create - - `args` -> list of key: value we want to assign to the {arke_struct} above - - ## Example - iex> string = ArkeManager.get(:string, :default) - ...> Arke.QueryManager.create(:default, string, [id: "name", label: "Nome"]) - - ## Returns - {:ok, %Arke.Core.Unit{}} - - + - `args` -> the data of the new element we want to create """ @spec create(project :: atom(), arke :: Arke.t(), args :: [...]) :: func_return() def create(project, arke, args) do @@ -241,23 +217,13 @@ defmodule Arke.QueryManager do def check_group_manager_functions_errors(unit), do: {:ok, unit} @doc """ - Function to create a Unit. - - It calls the persistence_function `:update` set under (:arke -> :persistence -> :arke_postgres -> :update) in the configuration file + Function to update a Unit. ## Parameters - `project` -> identify the `Arke.Core.Project` - `unit` -> unit to update - `args` -> list of key: value to update - ## Example - iex> name = QueryManager.get_by(id: "name") - ...> QueryManager.update(:default, name, [max_length: 20]) - - ## Returns - {:ok, %Arke.Core.Unit{} } - {:error, [msg]} - """ @spec update(Unit.t(), args :: list()) :: func_return() def update(%{arke_id: arke_id, metadata: %{project: project}, data: data} = current_unit, args) do @@ -281,20 +247,10 @@ defmodule Arke.QueryManager do {:ok, Unit.update(unit, updated_at: updated_at)} end @doc """ - Function to delete a given unit - - It calls the persistence_function `:delete` set under (:arke -> :persistence -> :arke_postgres -> :delete) in the configuration file - + Function to delete a given unit. It will delete the manager, if any, and the db record ## Parameters - `project` -> identify the `Arke.Core.Project` - `unit` -> the unit to delete - ## Example - iex> unit = Arke.QueryManager.get_by(id: "name") - ...> Arke.QueryManager.delete(:test_project, unit) - - ## Returns - {:ok, _} - """ @spec delete(project :: atom(), Unit.t()) :: {:ok, any()} def delete(project, %{arke_id: arke_id} = unit) do @@ -311,30 +267,16 @@ defmodule Arke.QueryManager do end @doc """ - Create a query which is used to get a single element which match the given criteria - - ## Parameters - - `opts` -> used to identify the element to get - - ## Example - iex> Arke.QueryManager.get_by(id: "name") + Create a query which is used to get a single element which match the given criteria. + If more are returned then an exception will be raised """ - @spec get_by(opts :: list()) :: Unit.t() | nil + @spec get_by(opts :: [{:project,atom} | {atom,any}]) :: Unit.t() | nil def get_by(opts \\ []), do: basic_query(opts) |> one @doc """ Create a query which is used to get all the element which match the given criteria - - ## Parameters - - `opts` -> identify the element to get - - ## Example - iex> Arke.QueryManager.filter_by(id: "name") - - ## Return - [ Arke.Core.Unit{}, ...] """ - @spec filter_by(opts :: [...]) :: [Unit.t()] | [] + @spec filter_by(opts :: [{:project,atom} | {atom,any}]) :: [Unit.t()] | [] def filter_by(opts \\ []), do: basic_query(opts) |> all defp basic_query(opts) when is_map(opts), do: Map.to_list(opts) |> basic_query @@ -372,9 +314,6 @@ defmodule Arke.QueryManager do ## Example iex> query = QueryManager.query(arke: nil, project: :arke_system) ...> query = QueryManager.and_(query, false, QueryManager.conditions(parameter__eq: "value")) - - ## Return - %Arke.Core.Query{} """ @spec and_(query :: Query.t(), negate :: boolean(), filters :: list()) :: Query.t() def and_(query, negate, filters) when is_list(filters), @@ -394,8 +333,6 @@ defmodule Arke.QueryManager do iex> query = QueryManager.query(arke: nil, project: :arke_system) ...> query = QueryManager.or_(query, false, QueryManager.conditions(parameter__eq: "value")) - ## Return - %Arke.Core.Query{} """ @spec or_(query :: Query.t(), negate :: boolean(), filters :: list()) :: Query.t() def or_(query, negate, filters) when is_list(filters), @@ -421,9 +358,6 @@ defmodule Arke.QueryManager do ## Example iex> QueryManager.condition(:string, :eq, "test") - - ## Return - %Arke.Core.Query.BaseFilter{} """ @spec condition( parameter :: Unit.t(), @@ -443,9 +377,6 @@ defmodule Arke.QueryManager do ## Example iex> QueryManager.conditions(name__eq: "test", string__contains: "t") - - ## Return - [ %Arke.Core.Query.BaseFilter{}, ...] """ @spec conditions(opts :: list()) :: [Query.BaseFilter.t()] def conditions(opts \\ []) do @@ -465,10 +396,6 @@ defmodule Arke.QueryManager do ## Example iex> query = Arke.QueryManager.query() ...> QueryManager.where(query, [id__contains: "me", id__contains: "n"]) - - ## Return - %Arke.Core.Query{ %Arke.Core.Query.Filter{ ... base_filters: %Arke.Core.Query.BaseFilter{ ... }}} - """ @spec where(query :: Query.t(), opts :: list()) :: Query.t() def where(query, opts \\ []) do @@ -481,31 +408,23 @@ defmodule Arke.QueryManager do end @doc """ - It creates a filter for the given query + It adds a filter for the given query ## Parameters - `query` -> refer to `query/1` - `filter` -> refer to `Arke.Core.Query.Filter` - - ## Example - iex> query = Arke.QueryManager.Query.t - ...> parameter = Arke.Boundary.ParameterManager.get(:id,:arke_system) - ...> Arke.Core.Query.new_filter(parameter,:equal,"name",false) - ...> Arke.Core.Query.filter(query, filter) - - """ @spec filter(query :: Query.t(), filter :: Query.Filter.t()) :: Query.t() def filter(query, filter), do: Query.add_filter(query, filter) @doc """ - It creates a filter for the given query + It adds a filter for the given query """ @spec filter( query :: Query.t(), parameter :: Arke.t() | String.t() | atom(), operator :: operator(), - value :: String.t() | boolean() | number(), + value :: any, negate :: boolean() ) :: Query.t() def filter(query, parameter, operator, value, negate \\ false), @@ -542,15 +461,9 @@ defmodule Arke.QueryManager do Define a criteria to order the element returned from a query ## Parameter - - `query` -> refer to `query/1` - - `parameter1 -> used to order the query - - `direction` -> way of sorting the results - - ## Example - iex> query = QueryManager.query() - ...> parameter = Arke.Boundary.ParameterManager.get(:id,:default) - ...> QueryManager.order(query, parameter, :asc) - + - `query` => refer to `query/1` + - `parameter => used to order the query + - `direction` => way of sorting the results (ascending or descending) """ @spec order( query :: Query.t(), @@ -566,26 +479,16 @@ defmodule Arke.QueryManager do ## Parameter - `query` -> refer to `query/1` - `offset` -> offset of the query - - ## Example - iex> query = QueryManager.query() - ...> QueryManager.where(query, id: "name") |> QueryManager.offset(5) - """ @spec offset(query :: Query.t(), offset :: integer()) :: Query.t() def offset(query, offset), do: Query.set_offset(query, offset) @doc """ - Set the limit of the element to be returned from a query + Set the limit of the results of a query ## Parameter - `query` -> refer to `query/1` - `limit` -> number of element to return - - ## Example - iex> query = QueryManager.query() - ...> QueryManager.where(query, id: "name") |> QueryManager.limit(1) - """ @spec limit(query :: Query.t(), limit :: integer()) :: Query.t() def limit(query, limit), do: Query.set_limit(query, limit) @@ -597,11 +500,6 @@ defmodule Arke.QueryManager do - `query` -> refer to `query/1` - `offset` -> offset of the query - `limit` -> number of element to return - - ## Example - iex> query = QueryManager.query() - ...> QueryManager.pagination(query, 0,5) - """ @spec pagination(query :: Query.t(), offset :: integer(), limit :: integer()) :: {count :: integer(), elements :: [] | [Unit.t()]} @@ -613,22 +511,15 @@ defmodule Arke.QueryManager do end @doc """ - Run the `execute_query` set in the configuration under (:arke -> :persistence -> :arke_postgres -> :execute_query) with `:all` as the second parameter - + Run the given query and return all the results ## Parameter - query -> refer to `query/1` - - ## Example - iex> query = QueryManager.query() - ...> QueryManager.where(query, id: "name") |> QueryManager.all() """ @spec all(query :: Query.t()) :: [Unit.t()] | [] def all(query), do: execute_query(query, :all) @doc """ - - Run the `execute_query` set in the configuration under (:arke -> :persistence -> :arke_postgres -> :execute_query) with `:one` as the second parameter - + Run the given query and return only the first result ## Parameter - `query` -> refer to `query/1` @@ -637,8 +528,7 @@ defmodule Arke.QueryManager do def one(query), do: execute_query(query, :one) @doc """ - Run the `execute_query` set in the configuration under (:arke -> :persistence -> :arke_postgres -> :execute_query) with `:raw` as the second parameter - + Return the given query as a string ## Parameter - `query` -> refer to `query/1` """ @@ -646,20 +536,15 @@ defmodule Arke.QueryManager do def raw(query), do: execute_query(query, :raw) @doc """ - Run the `execute_query` set in the configuration under (:arke -> :persistence -> :arke_postgres -> :execute_query) with `:count` as the second parameter - + Run the given query and return only the number of the records that have been found ## Parameter - `query` -> refer to `query/1` - - ## Returns - integer """ @spec count(query :: Query.t()) :: integer() def count(query), do: execute_query(query, :count) @doc """ - Run the `execute_query` set in the configuration under (:arke -> :persistence -> :arke_postgres -> :execute_query) with `:pseudo_query` as the second parameter - + Return the given query as Ecto pseudo query ## Parameter - `query` -> refer to `query/1` """ From 174efee6e738a73c42d8a8674478619fbfbcf71d Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 16 May 2024 17:35:14 +0200 Subject: [PATCH 11/20] docs: query --- lib/arke/core/query.ex | 193 +++++++++++++---------------------------- 1 file changed, 59 insertions(+), 134 deletions(-) diff --git a/lib/arke/core/query.ex b/lib/arke/core/query.ex index 5d8949d..01c57a4 100644 --- a/lib/arke/core/query.ex +++ b/lib/arke/core/query.ex @@ -23,9 +23,9 @@ defmodule Arke.Core.Query do defmodule LinkFilter do @moduledoc """ Base struct of a LinkFilter: - - unit => %Arke.Core.`{arke_struct}`{} => the `arke_struct` of the unit which we want to filter on. See `Arke.Struct` - - depth => integer => how many results we want to have at max - - direction => "child" | "parent" => the direction the query will use to search, + - unit => the starting unit for the query + - depth => how many lookups or lookdowns the recursive query must do + - direction => the direction the query will use to search, - type => the name of the connection we want to look at \n It is used to define a common filter struct which will be applied on an arke_link Query """ @@ -62,21 +62,13 @@ defmodule Arke.Core.Query do Create a new BaseParameter ## Parameters - - parameter => %Arke.Core.Parameter.`ParameterType` => refer to `Arke.Core.Parameter` + - parameter => Parameter on which the base filter will be applied - operator => refer to [operators](#module-operators) - - value => any => the value that the query will search for - - negate => boolean => used to figure out whether the condition is to be denied \n - - ## Example - iex> filter = Arke.Core.Query.BaseFilter.new(parameter: "name", operator: "eq", value: "John", negate: false) - ...> Arke.Core.Query.BaseFilter.new(person, :default) - - ## Return - %Arke.Core.Query.BaseFilter{} - + - value => the value that the query will search for + - negate => used to figure out whether the condition is to be denied \n """ @spec new( - parameter :: Arke.Core.Parameter.ParameterType, + parameter :: Arke.Core.Parameter.t(), operator :: atom(), value :: any, negate :: boolean @@ -110,8 +102,8 @@ defmodule Arke.Core.Query do defmodule Order do @moduledoc """ Base struct Order: - - parameter => %Arke.Core.Parameter.`ParameterType` => refer to `Arke.Core.Parameter` - - direction => "child" | "parent" => the direction the query will use to search \n + - parameter => Parameter on which the order will be applied + - direction => the direction the query will use to search \n It is used to define the return order of a Query """ defstruct ~w[parameter direction]a @@ -124,14 +116,6 @@ defmodule Arke.Core.Query do ## Parameters - arke => %Arke.Core.`{arke_struct}`{} => the `arke_struct` of the unit which we want to filter on. See `Arke.Struct` - project => :atom => identify the `Arke.Core.Project` - - ## Example - iex> person = Arke.Core.Arke.new(id: "person", label: "Person") - ...> Arke.Core.Query.new(person, :default) - - ## Return - %Arke.Core.Query{} - """ @spec new(arke :: %Arke.Core.Arke{}, project :: atom()) :: Arke.Core.Query.t() def new(arke, project), @@ -146,28 +130,19 @@ defmodule Arke.Core.Query do } @doc """ - Add a new link filter + Add a new link filter in order to make a recursive query in the topology table. ## Parameters - query => refer to `new/1` - - unit => %Arke.Core.`{arke_struct}`{} => the `arke_struct` of the unit which we want to filter on. See `Arke.Struct` - - depth => integer => how many results we want to have at max - - direction => "child" | "parent" => the direction the query will use to search + - unit => the starting unit for the query + - depth => how many lookups or lookdowns the recursive query must do + - direction => the direction the query will use to search - type => the name of the link we want to look at - - ## Example - - iex> person = Arke.Core.Arke.new(id: :person, label: "Person") - ...> query = Arke.Core.Query.new(person, :arke_system) - ...> Arke.Core.Query.add_link_filter(query, person, 0, "child", "link") - - ## Return - %Arke.Core.Query{... link: %Arke.Core.Query.LinkFilter{} ... } """ @spec add_link_filter( query :: Arke.Core.Query.t(), unit :: Arke.Core.Unit.t(), depth :: integer(), - direction :: atom(), + direction :: :child | :parent, connection_type :: String.t() ) :: Arke.Core.Query.t() def add_link_filter(query, unit, depth, direction, type) do @@ -180,16 +155,6 @@ defmodule Arke.Core.Query do ## Parameters - query => refer to `new/1` - filter => refer to `Arke.Core.Query.BaseFilter` - - ## Example - iex> person = Arke.Core.Arke.new(id: :person, label: "Person") - ...> query = Arke.Core.Query.new(person, :arke_system) - ...> parameter = Arke.Boundary.ParameterManager.get(:id,:arke_system) - ...> filter = Arke.Core.Query.new_filter(parameter,:eq, "name", false) - ...> Arke.Core.Query.add_filter(query, filter) - - ## Return - %Arke.Core.Query{... filters: [ %Arke.Core.Query.Filter{} ] ... } """ def add_filter(query, filter) do %{query | filters: [filter | query.filters]} @@ -204,16 +169,6 @@ defmodule Arke.Core.Query do - operator - value - negate - - ## Example - iex> person = Arke.Core.Arke.new(id: :person, label: "Person") - ...> query = Arke.Core.Query.new(person, :arke_system) - ...> parameter = Arke.Boundary.ParameterManager.get(:id,:arke_system) - ...> base_filter = Arke.Core.Query.new_base_filter(parameter, :eq, "name", false) - ...> Arke.Core.Query.add_filter(query, :and, false, base_filter) - - ## Return - %Arke.Core.Query{... filters: [ %Arke.Core.Query.Filter{} ] ... } """ def add_filter(query, parameter, operator, value, negate) do %{query | filters: [new_filter(parameter, operator, value, negate) | query.filters]} @@ -224,19 +179,16 @@ defmodule Arke.Core.Query do ## Parameters - query => refer to `new/1` - - logic => :and | :or => the logic of the filter - - negate => boolean => used to figure out whether the condition is to be denied - - base_filters - - ## Example - iex> person = Arke.new(id: :person, label: "Person") - ...> query = Arke.Core.Query.new(person, :arke_system) - ...> parameter = Arke.Core.ParameterManager.get(:id,:arke_system) - ...> Arke.Core.Query.add_filter(query, parameter, :eq, "name", false) - - ## Return - %Arke.Core.Query{... filters: [ %Arke.Core.Query.Filter{} ] ... } + - logic => the logic of the filter + - negate => used to figure out whether the condition is to be denied + - base_filters => One or a list of `Arke.Core.Query.BaseFilter` """ + @spec add_filter( + query :: Arke.Core.Query.t(), + logic :: :and | :or, + negate :: boolean, + base_filters :: Arke.Core.Query.BaseFilter.t() | [Arke.Core.Query.BaseFilter.t()] + ) :: Arke.Core.Query.t() def add_filter(query, logic, negate, base_filters) do %{query | filters: [new_filter(logic, negate, base_filters) | query.filters]} end @@ -244,18 +196,17 @@ defmodule Arke.Core.Query do @doc """ Create a new filter ## Parameters - - parameter => %Arke.Core.Parameter.`ParameterType` => refer to `Arke.Core.Parameter` + - parameter => Parameter to filter - operator => refer to [operators](#module-operators) - - value => any => the value that the query will search for - - negate => boolean => used to figure out whether the condition is to be denied - - ## Example - iex> parameter = Arke.Boundary.ParameterManager.get(:id,:arke_system) - ...> Arke.Core.Query.new_filter(parameter,:eq, "name", false) - - ## Return - %Arke.Core.Query.Filter{base_filters: [ %Arke.Core.Query.BaseFilter{} ]} + - value => the value that the query will search for + - negate => used to figure out whether the condition is to be denied """ + @spec new_filter( + parameter :: Arke.Core.Parameter.t(), + operator :: Arke.Core.QueryManager.operator(), + value :: any, + negate :: boolean() + ):: Arke.Core.Query.Filter.t() def new_filter(parameter, operator, value, negate) do %Filter{ logic: :and, @@ -267,17 +218,15 @@ defmodule Arke.Core.Query do @doc """ Create a new filter ## Parameters - - logic => :and | :or => the logic of the filter - - negate => boolean => used to figure out whether the condition is to be denied + - logic => the logic of the filter + - negate => used to figure out whether the condition is to be denied - base_filters => refer to `Arke.Core.Query.BaseFilter` - - ## Example - iex> base_filter = Arke.Core.Query.new_base_filter(parameter, :eq, "name", false) - ...> Arke.Core.Query.new_filter(:and, false, base_filter) - - ## Return - %Arke.Core.Query.Filter{base_filters: [ %Arke.Core.Query.BaseFilter{} ]} """ + @spec new_filter( + logic :: :and | :or, + negate :: boolean(), + base_filters :: Arke.Core.Query.BaseFilter.t() | [Arke.Core.Query.BaseFilter.t()] + ):: Arke.Core.Query.Filter.t() def new_filter(logic, negate, base_filters) do %Filter{base_filters: parse_base_filters(base_filters), logic: logic, negate: negate} end @@ -286,22 +235,18 @@ defmodule Arke.Core.Query do Create a new base filter ## Parameters - - parameter => %Arke.Core.Parameter.`ParameterType` => refer to `Arke.Core.Parameter` + - parameter => Parameter to filter - operator => refer to [operators](#module-operators) - - value => any => the value that the query will search for - - negate => boolean => used to figure out whether the condition is to be denied - - ## Example - iex> parameter = Arke.Boundary.ParameterManager.get(:id,:arke_system) - ...> Arke.Core.Query.new_base_filter(parameter, :eq, "name", false) - - ## Return - %Arke.Core.Query.BaseFilter{} + - value => the value that the query will search for + - negate => used to figure out whether the condition is to be denied """ - # TODO: standardize parameter - # if it is a string convert it to existing atom and get it from paramater manager - # if it is an atom get it from paramater manaager + @spec new_base_filter( + parameter :: Arke.Core.Parameter.t(), + operator :: Arke.Core.QueryManager.operator(), + value :: any, + negate :: boolean() + ) :: Arke.Core.Query.BaseFilter.t() def new_base_filter(parameter, operator, value, negate) do BaseFilter.new(parameter, operator, value, negate) end @@ -313,19 +258,15 @@ defmodule Arke.Core.Query do Get the query result ordered by specific criteria ## Parameters - - query => refer to refer to `new/1` - - parameter => %Arke.Core.Parameter.`ParameterType` => refer to `Arke.Core.Parameter` - - direction => "child" | "parent" => the direction the query will use to search - - ## Example - iex> person = Arke.Core.Arke.new(id: "person", label: "Person") - ...> query = Arke.Core.Query.new(person, :arke_system) - ...> parameter = Arke.Boundary.ParameterManager.get(:id,:arke_system) - ...> Arke.Core.Query.add_order(query, parameter, :asc) - - ## Return - %Arke.Core.Query{ ... orders: [ %Arke.Core.Query.Order{} ] ... } + - query => refer to `new/1` + - parameter => parameter used to order the query results + - direction => the direction the query will use to search """ + @spec add_order( + query :: Arke.Core.Query.t(), + parameter :: Arke.Core.Parameter.t(), + direction :: :asc | :desc + ):: Arke.Core.Query.t() def add_order(query, parameter, direction) do %{ query @@ -338,18 +279,9 @@ defmodule Arke.Core.Query do ## Parameters - query => refer to `new/1` - - offset => integer => define the offset of the query - - ## Example - iex> person = Arke.Core.Arke.new(id: :person, label: "Person") - ...> query = Arke.Core.Query.new(person, :arke_system) - ...> Arke.Core.Query.set_offset(query, 5) - - ## Return - %Arke.Core.Query{... offset: value ...} - + - offset => define the offset of the query """ - + @spec set_offset(query :: Arke.Core.Query.t(), offset :: nil | number() | String.t()):: Arke.Core.Query.t() def set_offset(query, nil), do: query def set_offset(query, offset) when is_binary(offset), @@ -365,15 +297,8 @@ defmodule Arke.Core.Query do ## Parameters - query => refer to `new/1` - limit => integer => set the results limit of the query - - ## Example - iex> person = Arke.Core.Arke.new(id: :person, label: "Person") - ...> query = Arke.Core.Query.new(person, :arke_system) - ...> Arke.Core.Query.set_limit(query, 100) - - ## Return - %Arke.Core.Query{... limit: value ...} """ + @spec set_limit(query :: Arke.Core.Query.t(), offset :: nil | number() | String.t()):: Arke.Core.Query.t() def set_limit(query, nil), do: query def set_limit(query, offset) when is_binary(offset), From a891404cffb51af0a42e29a5553e0cacc50de529 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 16 May 2024 17:38:42 +0200 Subject: [PATCH 12/20] docs: readme configuration --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index cac9164..f7b4f7a 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,20 @@ def deps do end ``` +Also add in our project the configuration below based on your persistence. In this case we use `arke_postgres` +``` +config :arke, + persistence: %{ + arke_postgres: %{ + init: &ArkePostgres.init/0, + create: &ArkePostgres.create/2, + update: &ArkePostgres.update/2, + delete: &ArkePostgres.delete/2, + execute_query: &ArkePostgres.Query.execute/2, + create_project: &ArkePostgres.create_project/1, + delete_project: &ArkePostgres.delete_project/1, + repo: ArkePostgres.Repo, + } + } +``` +This configuration is used to apply all the CRUD operations in the `Arke.QueryManager` module \ No newline at end of file From 03e2dfbca97678f2b6596caffec9d9a5f328fce5 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 16 May 2024 17:51:41 +0200 Subject: [PATCH 13/20] docs: system --- lib/arke/system.ex | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/lib/arke/system.ex b/lib/arke/system.ex index 65e3175..3f67124 100644 --- a/lib/arke/system.ex +++ b/lib/arke/system.ex @@ -14,7 +14,7 @@ defmodule Arke.System do @moduledoc """ - Module which manage the creation of every Arke struct + Module which define all the basics functions of an Arke """ @doc """ @@ -79,6 +79,9 @@ defmodule Arke.System do Overridable function in order to be able to edit data during the encoding """ def on_struct_encode(_, _, data, opts), do: {:ok, data} + @doc """ + Overridable function in order to be able to edit data before the encoding + """ def before_struct_encode(_, unit), do: {:ok, unit} @doc """ Overridable function in order to be able to edit data on the update @@ -97,9 +100,15 @@ defmodule Arke.System do """ def before_delete(arke, unit), do: {:ok, unit} + @doc """ + Overridable function in order to be able to edit data after the encoding + """ def after_get_struct(arke, unit, struct), do: struct def after_get_struct(arke, struct), do: struct + @doc """ + Overridable function used to import arkes from excel file + """ def import(%{runtime_data: %{conn: %{method: "POST"}=conn}, metadata: %{project: project}} = arke) do member = ArkeAuth.Guardian.Plug.current_resource(conn) mode = Map.get(conn.body_params, "mode", "default") @@ -110,6 +119,9 @@ defmodule Arke.System do end end + @doc """ + Overridable function used to import units from excels files + """ defp import_units(arke, project, member, file, mode) do {:ok, ref} = Enum.at(Xlsxir.multi_extract(file.path), 0) all_units = get_all_units_for_import(project) @@ -161,6 +173,9 @@ defmodule Arke.System do defp parse_cell(value) when is_tuple(value), do: Kernel.inspect(value) defp parse_cell(value), do: value + @doc """ + Overridable function used to get the header which will be used to parse the units + """ defp get_header_for_import(project, arke, header_file) do Enum.reduce(Enum.with_index(header_file), [], fn {cell, index}, acc -> case Arke.Boundary.ArkeManager.get_parameter(arke, project, cell) do @@ -183,8 +198,14 @@ defmodule Arke.System do end) end + @doc """ + Overridable function used to get all the units that will be used in the import + """ defp get_all_units_for_import(project), do: [] + @doc """ + Overridable function used to create Units struct from the data in an import file + """ defp load_units(project, arke, header, row, _, "default") do args = Enum.reduce(header, [], fn {parameter_id, index}, acc -> acc = Keyword.put(acc, String.to_existing_atom(parameter_id), Enum.at(row, index)) @@ -195,7 +216,13 @@ defmodule Arke.System do do: {:ok, args}, else: ({:error, errors} -> {:error, args, errors}) end + @doc """ + Overridable function used to get all the units already created + """ defp get_existing_units_for_import(project, arke, header, units_args), do: [] + @doc """ + Overridable function used to check if all the units for the import are valid or not + """ defp check_existing_units_for_import(project, arke, header, units_args, existing_units), do: true defp get_import_value(header, row, column) do index = Enum.find(header, fn {k, v} -> k == column end) |> elem(1) @@ -233,17 +260,13 @@ defmodule Arke.System do ###################################################################################################################### @doc """ - Macro to create an arke struct with the given parameters + Macro to manager an arke and its related functions ## Example - arke do - parameter :custom_parameter, :string, required: true, unique: true - parameter :custom_parameter2, :string, required: true, values: ["value1", "value2"] - parameter :custom_parameter3, :integer, required: true, values: [%{label: "option 1", value: 1},%{label: "option 2", value: 2}] - parameter :custom_parameter4, :dict, required: true, default: %{"default_dict_key": "default_dict_value"} - parameter :custom_parameter5, :type, ...opts + arke id: :some_id do end + From now on all the overridable functions can be edited and all the public funcitons will be used as API custom function """ @spec arke(args :: list(), Macro.t()) :: %{} defmacro arke(opts \\ [], do: block) do From a5c08c4007c8e2e05dabd0cb3fecc686448bff51 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 17 May 2024 11:51:30 +0200 Subject: [PATCH 14/20] docs: system macro example --- lib/arke/core/parameter.ex | 9 ++ lib/arke/query_manager.ex | 4 +- lib/arke/system.ex | 126 +++++++++----------------- lib/examples/starter.ex | 83 ----------------- lib/examples/system_macro_function.ex | 107 ++++++++++++++++++++++ mix.exs | 8 +- mix.lock | 12 +-- 7 files changed, 174 insertions(+), 175 deletions(-) delete mode 100644 lib/examples/starter.ex create mode 100644 lib/examples/system_macro_function.ex diff --git a/lib/arke/core/parameter.ex b/lib/arke/core/parameter.ex index 68d2e58..584d2eb 100644 --- a/lib/arke/core/parameter.ex +++ b/lib/arke/core/parameter.ex @@ -104,6 +104,9 @@ defmodule Arke.Core.Parameter.String do arke id: "string" do end + @doc """ + Before create the Parameter struct check if it has some `values` and if they are formatted correctly + """ def before_load(data, _persistence_fn) do args = Arke.System.BaseParameter.check_enum(:string, Map.to_list(data)) {:ok, Enum.into(args, %{})} @@ -138,6 +141,9 @@ defmodule Arke.Core.Parameter.Integer do arke id: "integer" do end + @doc """ + Before create the Parameter struct check if it has some `values` and if they are formatted correctly + """ def before_load(data, _persistence_fn) do args = Arke.System.BaseParameter.check_enum(:integer, Map.to_list(data)) {:ok, Enum.into(args, %{})} @@ -172,6 +178,9 @@ defmodule Arke.Core.Parameter.Float do arke id: "float" do end + @doc """ + Before create the Parameter struct check if it has some `values` and if they are formatted correctly + """ def before_load(data, _persistence_fn) do args = Arke.System.BaseParameter.check_enum(:float, Map.to_list(data)) {:ok, Enum.into(args, %{})} diff --git a/lib/arke/query_manager.ex b/lib/arke/query_manager.ex index 35f4f5f..2c1e801 100644 --- a/lib/arke/query_manager.ex +++ b/lib/arke/query_manager.ex @@ -14,9 +14,9 @@ defmodule Arke.QueryManager do @moduledoc """ - Module to manage the CRUD operations to create the below elements and also manage the query to get the elements from db. + Module to manage the CRUD operations to create the below `Elements` and also manage the query to get the elements from db. - ## `arke` + ## `Elements` - Parameter -> `Arke.Core.Parameter` - Arke -> `Arke.Core.Arke` - Unit -> `Arke.Core.Unit` diff --git a/lib/arke/system.ex b/lib/arke/system.ex index 3f67124..672e186 100644 --- a/lib/arke/system.ex +++ b/lib/arke/system.ex @@ -14,11 +14,11 @@ defmodule Arke.System do @moduledoc """ - Module which define all the basics functions of an Arke + Core module which handle all the management functions for an Arke """ @doc """ - Macro to simplify the creation of a new Arke + This macro is used whenever we want to edit the default behaviour use Arke.System """ @@ -34,81 +34,56 @@ defmodule Arke.System do import unquote(__MODULE__), only: [arke: 1, arke: 2, parameter: 3, parameter: 2, group: 1, group: 2] - # @before_compile unquote(__MODULE__) - + @doc false def arke_from_attr(), do: Keyword.get(__MODULE__.__info__(:attributes), :arke, []) |> List.first() + @doc false def groups_from_attr(), do: Keyword.get(__MODULE__.__info__(:attributes), :groups, []) + @doc false def base_parameters() do unit = arke_from_attr() unit.data.parameters end - @doc """ - Overridable function in order to be able to edit data during the unit load - """ + @doc false def on_load(data, _persistence_fn), do: {:ok, data} - @doc """ - Overridable function in order to be able to edit data before the load - """ + @doc false def before_load(data, _persistence_fn), do: {:ok, data} - @doc """ - Overridable function in order to be able to edit data during the validation - """ + @doc false def on_validate(arke, unit), do: {:ok, unit} - @doc """ - Overridable function in order to be able to edit data before the validation - """ + @doc false def before_validate(arke, unit), do: {:ok, unit} - @doc """ - Overridable function in order to be able to edit data during the creation - """ + @doc false def on_create(arke, unit), do: {:ok, unit} - @doc """ - Overridable function in order to be able to edit data before the creation - """ + @doc false def before_create(arke, unit), do: {:ok, unit} - @doc """ - Overridable function in order to be able to edit data during the encoding - """ + @doc false def on_struct_encode(_, _, data, opts), do: {:ok, data} - @doc """ - Overridable function in order to be able to edit data before the encoding - """ + @doc false def before_struct_encode(_, unit), do: {:ok, unit} - @doc """ - Overridable function in order to be able to edit data on the update - """ + @doc false def on_update(arke, old_unit, unit), do: {:ok, unit} - @doc """ - Overridable function in order to be able to edit data before the update - """ + @doc false def before_update(arke, unit), do: {:ok, unit} - @doc """ - Overridable function in order to be able to edit data during the deletion - """ + @doc false def on_delete(arke, unit), do: {:ok, unit} - @doc """ - Overridable function in order to be able to edit data before the deletion - """ + @doc false def before_delete(arke, unit), do: {:ok, unit} - @doc """ - Overridable function in order to be able to edit data after the encoding - """ + @doc false def after_get_struct(arke, unit, struct), do: struct + + @doc false def after_get_struct(arke, struct), do: struct - @doc """ - Overridable function used to import arkes from excel file - """ + @doc false def import(%{runtime_data: %{conn: %{method: "POST"}=conn}, metadata: %{project: project}} = arke) do member = ArkeAuth.Guardian.Plug.current_resource(conn) mode = Map.get(conn.body_params, "mode", "default") @@ -119,9 +94,7 @@ defmodule Arke.System do end end - @doc """ - Overridable function used to import units from excels files - """ + @doc false defp import_units(arke, project, member, file, mode) do {:ok, ref} = Enum.at(Xlsxir.multi_extract(file.path), 0) all_units = get_all_units_for_import(project) @@ -173,9 +146,7 @@ defmodule Arke.System do defp parse_cell(value) when is_tuple(value), do: Kernel.inspect(value) defp parse_cell(value), do: value - @doc """ - Overridable function used to get the header which will be used to parse the units - """ + @doc false defp get_header_for_import(project, arke, header_file) do Enum.reduce(Enum.with_index(header_file), [], fn {cell, index}, acc -> case Arke.Boundary.ArkeManager.get_parameter(arke, project, cell) do @@ -198,14 +169,10 @@ defmodule Arke.System do end) end - @doc """ - Overridable function used to get all the units that will be used in the import - """ + @doc false defp get_all_units_for_import(project), do: [] - @doc """ - Overridable function used to create Units struct from the data in an import file - """ + @doc false defp load_units(project, arke, header, row, _, "default") do args = Enum.reduce(header, [], fn {parameter_id, index}, acc -> acc = Keyword.put(acc, String.to_existing_atom(parameter_id), Enum.at(row, index)) @@ -216,13 +183,9 @@ defmodule Arke.System do do: {:ok, args}, else: ({:error, errors} -> {:error, args, errors}) end - @doc """ - Overridable function used to get all the units already created - """ + @doc false defp get_existing_units_for_import(project, arke, header, units_args), do: [] - @doc """ - Overridable function used to check if all the units for the import are valid or not - """ + @doc false defp check_existing_units_for_import(project, arke, header, units_args, existing_units), do: true defp get_import_value(header, row, column) do index = Enum.find(header, fn {k, v} -> k == column end) |> elem(1) @@ -335,14 +298,13 @@ defmodule Arke.System do # PARAMETER MACRO #################################################################################################### ###################################################################################################################### - @doc """ + @doc false && """ Macro used to define parameter in an arke. See example above `arke/2` """ @spec parameter(id :: atom(), type :: atom(), opts :: list()) :: Macro.t() defmacro parameter(id, type, opts \\ []) do - # parameter_dict = Arke.System.BaseParameter.parameter_options(opts, id, type) quote bind_quoted: [id: id, type: type, opts: opts] do opts = Arke.System.BaseParameter.check_enum(type, opts) @parameters %{id: id, arke: type, metadata: opts} @@ -357,10 +319,9 @@ defmodule Arke.System do # GROUP MACRO #################################################################################################### ###################################################################################################################### - @doc """ - Macro used to define parameter in an arke. + @doc false && """ + Assign an arke to a given group. See example above `arke/2` - """ @spec group(id :: atom(), opts :: list()) :: Macro.t() defmacro group(id, opts \\ []) do @@ -383,9 +344,12 @@ defmodule Arke.System.BaseArke do end defmodule Arke.System.BaseParameter do + @moduledoc """ + Default struct for every Parameter type + """ defstruct [:id, :label, :active, :metadata, :type, :parameters] - @doc """ + @doc false && """ Used in the parameter macro to create the map for every parameter which have the `values` option. It check if the given value are the same type as the parameter type and then creates a list of map as follows: @@ -398,12 +362,10 @@ defmodule Arke.System.BaseParameter do The code above will be modified to be as follows values: [%{label "1", value: 1}, %{label "2", value: 2}, %{label "3", value: 3}] - - """ - @spec parameter_options(opts :: list(), id :: atom(), type :: atom()) :: %{ + @spec parameter_options(opts :: [...], id :: atom(), type :: atom()) :: %{ type: atom(), - opts: list() + opts: [...] } def parameter_options(opts, id, type) do opts = @@ -415,13 +377,13 @@ defmodule Arke.System.BaseParameter do end @doc """ - It checks if the given values match the type of the parameter. - + Checks if the given parameter type has enum values and if these values are formatted correctly """ - @spec check_enum(type :: :string | :integer | :float, opts :: [...] | []) :: + @spec check_enum(type :: :atom, opts :: [...] | []) :: [...] | [] | [%{label: String.t(), value: float() | integer() | String.t()}, ...] def check_enum(type, opts) when is_binary(type), do: check_enum(String.to_atom(type),opts) def check_enum(type, opts) do + #todo: move check_enum in another module because the BaseParameter struct is unused enum_parameters = [:string, :integer, :float] case type in enum_parameters do true -> @@ -483,8 +445,8 @@ defmodule Arke.System.BaseParameter do Keyword.put_new(opts, key, default) end - def __enum_parameter__(opts, type) when is_map(opts), do: __enum_parameter__(Map.to_list(opts),type) - def __enum_parameter__(opts, type) do + defp __enum_parameter__(opts, type) when is_map(opts), do: __enum_parameter__(Map.to_list(opts),type) + defp __enum_parameter__(opts, type) do case Keyword.has_key?(opts, :values) do true -> __validate_values__(opts, opts[:values], type) false -> @@ -518,7 +480,7 @@ defmodule Arke.System.BaseParameter do true -> __create_map_values__(__check_map__(values), opts, type, condition) - # FARE RAISE ECCEZIONE DA GESTIRE. CHIAVI DEVONO ESSERE TUTTE UGUALI + # RAISE EXCEPTION TO HANDLE. KEYS MUST ALL BE THE SAME _ -> Keyword.update(opts, :values, nil, fn _current_value -> nil end) end @@ -547,7 +509,7 @@ defmodule Arke.System.BaseParameter do defp __check_map__(values), do: values defp __create_map_values__(values, opts, type, condition) do - # FARE RAISE ECCEZIONE DA GESTIRE. CHIAVI DEVONO ESSERE TUTTE UGUALI + # RAISE EXCEPTION TO HANDLE. KEYS MUST ALL BE THE SAME with true <- Enum.all?(values, fn %{label: l, value: v} -> condition.(l, v) end) do new_values = Enum.map(values, fn k -> @@ -564,7 +526,7 @@ defmodule Arke.System.BaseParameter do defp __get_map_value__(value, _), do: value defp __values_from_list__(values, opts, condition) do - # FARE RAISE ECCEZIONE DA GESTIRE. CHIAVI DEVONO ESSERE TUTTE UGUALI + # RAISE EXCEPTION TO HANDLE. KEYS MUST ALL BE THE SAME with true <- Enum.all?(values, &condition.(&1)) do new_values = Enum.map(values, fn k -> %{label: String.capitalize(to_string(k)), value: k} end) diff --git a/lib/examples/starter.ex b/lib/examples/starter.ex deleted file mode 100644 index 7fc5c47..0000000 --- a/lib/examples/starter.ex +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright 2023 Arkemis S.r.l. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -defmodule Arke.Examples.Starter do - @moduledoc false && - """ - Module to start all the defaults gen server - """ - alias Arke.Boundary.{ArkeManager} - - @doc """ - It starts all the defaults gen server which will contain all the {arke_struct} - """ - def init() do - Enum.each([:arke], fn app -> - {:ok, modules} = :application.get_key(app, :modules) - - Enum.each(modules, fn mod -> - is_arke = Keyword.get(mod.__info__(:attributes), :is_arke, false) - init_arke_by_struct(mod, is_arke) - end) - end) - - Arke.Boundary.ParameterManager.get_from_persistence() - init_arke_by_persistence() - init_arke_parameters_by_persistence() - end - - defp init_arke_by_struct(struct, [true]) do - args = struct.get_info() - - parameters = - Enum.reduce(struct.get_parameters(), [], fn [p], parameters -> - parameter = Arke.Core.Parameter.new(p) - Arke.Boundary.ParameterManager.create(parameter, :arke_system) - [parameter | parameters] - end) - - args = Keyword.put_new(args, :parameters, parameters) - ArkeManager.set_manager(Arke.Core.Arke.new(args), :arke_system) - end - - defp init_arke_by_struct(_, _), do: nil - - defp init_arke_by_persistence() do - arke = ArkeManager.get(:arke, :arke_system) - arkes = Arke.QueryManager.filter_by(project: :arke_system, arke: "arke") - - Enum.map(arkes, fn %{data: data} -> - ArkeManager.create( - Arke.Core.Arke.new(Map.merge(data, %{parameters: arke.parameters})), - :arke_system - ) - end) - end - - defp init_arke_parameters_by_persistence() do - arke_link = ArkeManager.get(:arke_link, :arke_system) - - arke_parameters = - Arke.QueryManager.query(arke: arke_link) - |> Arke.QueryManager.where(type: "parameter") - |> Arke.QueryManager.all() - - Enum.map(arke_parameters, fn %{data: data} -> - parent_id = String.to_existing_atom(data.parent_id) - child_id = String.to_existing_atom(data.child_id) - ArkeManager.add_parameter(parent_id, :arke_system, child_id, data.metadata) - end) - end - -end diff --git a/lib/examples/system_macro_function.ex b/lib/examples/system_macro_function.ex new file mode 100644 index 0000000..4b14b29 --- /dev/null +++ b/lib/examples/system_macro_function.ex @@ -0,0 +1,107 @@ +defmodule Arke.Example.SystemMacroFunction do + @moduledoc""" + Show all the available overridable functions in the `Arke.System` macro + """ + use Arke.System + @doc """ + Overridable function in order to be able to edit data during the unit load + """ + def on_load(data, _persistence_fn), do: {:ok, data} + + @doc """ + Overridable function in order to be able to edit data before the load + """ + def before_load(data, _persistence_fn), do: {:ok, data} + + @doc """ + Overridable function in order to be able to edit data during the validation + """ + def on_validate(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data before the validation + """ + def before_validate(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data during the creation + """ + def on_create(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data before the creation + """ + def before_create(_arke, unit), do: {:ok, unit} + @doc """ + Overridable function in order to be able to edit data during the encoding + """ + def on_struct_encode(_, _, data, _opts), do: {:ok, data} + @doc """ + Overridable function in order to be able to edit data before the encoding + """ + def before_struct_encode(_, unit), do: {:ok, unit} + @doc """ + Overridable function in order to be able to edit data on the update + """ + def on_update(_arke, _old_unit, unit), do: {:ok, unit} + @doc """ + Overridable function in order to be able to edit data before the update + """ + def before_update(_arke, unit), do: {:ok, unit} + @doc """ + Overridable function in order to be able to edit data during the deletion + """ + def on_delete(_arke, unit), do: {:ok, unit} + @doc """ + Overridable function in order to be able to edit data before the deletion + """ + def before_delete(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data after the encoding + """ + def after_get_struct(_arke, _unit, struct), do: struct + def after_get_struct(_arke, struct), do: struct + + @doc """ + Overridable function used to import arkes from excel file + """ + def import(%{runtime_data: %{conn: %{method: "POST"}=_conn}, metadata: %{project: _project}} = _arke), do: {:ok, %{}, 201} + + @doc """ + Overridable function used to import units from excels files + """ + defp import_units(_arke, _project, _member, _file, _mode), do: {:ok, %{}, 201} + + @doc """ + Overridable function used to get all the units that will be used in the import + """ + defp get_all_units_for_import(_project), do: [] + + @doc """ + Overridable function used to create Units struct from the data in an import file + """ + defp load_units(_project, _arke, _header, _row, _, "default"), do: {:ok, []} + @doc """ + Overridable function used to get all the units already created + """ + defp get_existing_units_for_import(_project, _arke, _header, _units_args), do: [] + @doc """ + Overridable function used to check if all the units for the import are valid or not + """ + defp check_existing_units_for_import(_project, _arke, _header, _units_args, _existing_units), do: true + defp get_import_value(_header, _row, _column), do: "" + + @doc """ + Return the values of `Arke.Parameter.BaseParameter` + """ + def base_parameters(), do: [] + @doc """ + Return the Arke struct for the given module + """ + def arke_from_attr(), do: nil + @doc """ + Return all the groups where the Arke, defined in the module, belongs to + """ + def groups_from_attr(), do: [] +end \ No newline at end of file diff --git a/mix.exs b/mix.exs index 2e7ef2e..dd16b42 100644 --- a/mix.exs +++ b/mix.exs @@ -35,7 +35,8 @@ defmodule Arke.MixProject do main: "Arke", logo: @logo_url, extras: ["README.md", "LICENSE"], - groups_for_modules: groups_for_modules() + groups_for_modules: groups_for_modules(), + extras: extras() ] ] end @@ -62,7 +63,7 @@ defmodule Arke.MixProject do [ {:typed_struct, "~> 0.2.1"}, {:uuid, "~> 1.1"}, - {:ex_doc, "~> 0.28", only: :dev, runtime: false}, + {:ex_doc, "~> 0.32", only: :dev, runtime: false}, {:excoveralls, "~> 0.10", only: :test}, {:credo, "~> 1.6", only: [:dev, :test], runtime: false}, {:timex, "~> 3.7.11"}, @@ -115,4 +116,7 @@ defmodule Arke.MixProject do Utils: [~r"Arke.Utils."] ] end + defp extras do + [] + end end diff --git a/mix.lock b/mix.lock index 5ea3344..efc3cbf 100644 --- a/mix.lock +++ b/mix.lock @@ -5,9 +5,9 @@ "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "erlsom": {:hex, :erlsom, "1.5.1", "c8fe2babd33ff0846403f6522328b8ab676f896b793634cfe7ef181c05316c03", [:rebar3], [], "hexpm", "7965485494c5844dd127656ac40f141aadfa174839ec1be1074e7edf5b4239eb"}, - "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, + "ex_doc": {:hex, :ex_doc, "0.32.2", "f60bbeb6ccbe75d005763e2a328e6f05e0624232f2393bc693611c2d3ae9fa0e", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "a4480305cdfe7fdfcbb77d1092c76161626d9a7aa4fb698aee745996e34602df"}, "excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"}, "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, @@ -23,15 +23,15 @@ "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, "jose": {:hex, :jose, "1.11.5", "3bc2d75ffa5e2c941ca93e5696b54978323191988eb8d225c2e663ddfefd515e", [:mix, :rebar3], [], "hexpm", "dcd3b215bafe02ea7c5b23dafd3eb8062a5cd8f2d904fd9caa323d37034ab384"}, "libcluster": {:hex, :libcluster, "3.3.3", "a4f17721a19004cfc4467268e17cff8b1f951befe428975dd4f6f7b84d927fe0", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7c0a2275a0bb83c07acd17dab3c3bfb4897b145106750eeccc62d302e3bdfee5"}, - "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, From c7866e6e52fb68c0dffac988e3ef859b089ee004 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 17 May 2024 12:15:18 +0200 Subject: [PATCH 15/20] docs: struct_manager --- lib/arke/struct_manager.ex | 90 ++++++++++---------------------------- 1 file changed, 23 insertions(+), 67 deletions(-) diff --git a/lib/arke/struct_manager.ex b/lib/arke/struct_manager.ex index 524e13b..ed313b0 100644 --- a/lib/arke/struct_manager.ex +++ b/lib/arke/struct_manager.ex @@ -25,31 +25,23 @@ defmodule Arke.StructManager do alias Arke.Core.{Unit, Arke} @type parameter :: %{ - default: String.t() | boolean() | atom() | map() | list() | nil, + default: any(), helper_text: String.t() | nil, id: String.t(), label: String.t(), required: boolean() | nil, type: String.t(), - key: String.t() | boolean() | atom() | map() | list() | nil + key: any() } @doc """ - Function that encodes a Unit or list of Unit + Function thate encodes the given Unit/Units to json ## Parameters - `unit` -> unit or list of units that we want to encode - `type` -> desired encode type - - ## Example - iex> units = QueryManager.filter_by(arke_id: id) - ...> StructManager.encode(units, type: :json) - - ## Returns - All the given units encoded based on the given type - """ - @spec encode(unit :: [Unit.t(), ...], format :: :json) :: %{atom() => String.t()} | [...] + @spec encode(unit :: [Unit.t(), ...], format :: :json) :: %{atom() => any} | [...] def encode(unit, opts \\ []) def encode(unit, opts) do @@ -173,36 +165,7 @@ defmodule Arke.StructManager do def encode(_unit, _format), do: raise("Must pass a valid unit") - - @doc """ - Validates the given data for links parameter - - ## Parameters - - `id` -> parameter to valorize - - `value` -> desired value - - `arke` -> Arke to look for the parameter - - `opts` -> options - - ## Example - iex> units = QueryManager.filter_by(arke_id: id) - ...> StructManager.encode(units, type: :json) - - ## Returns - All the given units encoded based on the given type - - """ - @spec validate_data( - id :: String.t() | atom(), - value :: any(), - arke :: Unit.t(), - opts :: [] | [...] - ) :: %{ - parameters: [parameter()], - label: String.t() - } - def validate_data(id, value, arke, opts \\ []) - - def validate_data(id, value, arke, opts) do + defp validate_data(id, value, arke, opts \\ []) do param = ArkeManager.get_parameter(arke, id) new_value = parse_value(value, param, Enum.into(opts, %{})) %{id => new_value} @@ -262,38 +225,30 @@ defmodule Arke.StructManager do end @doc """ - Function that decodes data into a Unit or list of Unit + Function that convert the given json data to a valid Unit struct ## Parameters - `project` -> identify the `Arke.Core.Project` - - `arke_id` -> arke id - - `json` -> json data that we want to decode + - `arke_id` -> the model to use to load the data + - `data` -> json data that we want to decode - `type` -> data input type - ## Example - iex> StructManager.decode(:arke, my_json_data, :json) """ @spec decode( project :: atom(), arke_id :: atom(), - json :: %{key: String.t() | number() | boolean() | atom()}, + data :: %{key: any()}, format :: atom() ) :: Unit.t() - def decode(project, arke_id, json, :json) when is_atom(arke_id) do + def decode(project, arke_id, data, :json) when is_atom(arke_id) do ArkeManager.get(arke_id, project) - |> Unit.load(json) + |> Unit.load(data) end - def decode(project, arke_id, json, :json) when is_binary(arke_id) do - ArkeManager.get(String.to_existing_atom(arke_id), project) - |> Unit.load(json) - end + def decode(project, arke_id, data, :json) when is_binary(arke_id), do: decode(project, String.to_existing_atom(arke_id), data, :json) def decode(_project, _arke_id, _json, _format), do: raise("Must pass valid data") - def load(arke_id, data) do - end - defp handle_default_value( %{arke_id: :string, data: %{default_string: default_string}} = _, value @@ -345,15 +300,11 @@ defmodule Arke.StructManager do defp handle_default_value(_, value), do: value @doc """ - Function to get a Unit Struct + Get an Arke's parameters struct ## Parameters - - `unit` -> unit struct - - ## Example - iex> arke = ArkeManager.get(:test, :default) - ...> StructManager.get_struct(arke) - """ + - `arke` -> Arke struct + """ @spec get_struct(arke :: Unit.t()) :: %{parameters: [parameter()], label: String.t()} def get_struct(%{arke_id: :arke, data: data} = arke) do struct = %{parameters: get_struct_parameters(arke, %{}), label: data.label} @@ -361,9 +312,14 @@ defmodule Arke.StructManager do end @doc """ - Function to get a Unit Struct + Get a Unit Struct with only the data defined by the `opts` + + ## Parameters + - `arke` -> Arke model to load the data + - `unit` -> Unit struct + - `opts` -> Data we want to include or exclude """ - @spec get_struct(arke :: Unit.t(), unit :: Unit.t(), opts :: [] | [...]) :: %{ + @spec get_struct(arke :: Unit.t(), unit :: Unit.t(), opts :: [{:include, [atom]} | {:exclude, [atom]}] | [...] | [] ) :: %{ parameters: [parameter()], label: String.t() } @@ -377,7 +333,7 @@ defmodule Arke.StructManager do end @doc """ - Function to get a Unit Struct + Get a Unit Struct """ @spec get_struct(arke :: Unit.t(), unit :: Unit.t()) :: %{ parameters: [parameter()], From 19d1e169431e5b713463825ea16b3173646ac1ae Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 17 May 2024 12:34:55 +0200 Subject: [PATCH 16/20] docs: parameter --- lib/arke/core/arke.ex | 1 - lib/arke/core/parameter.ex | 240 ++++--------------------------------- 2 files changed, 21 insertions(+), 220 deletions(-) diff --git a/lib/arke/core/arke.ex b/lib/arke/core/arke.ex index 6a2b533..63656fe 100644 --- a/lib/arke/core/arke.ex +++ b/lib/arke/core/arke.ex @@ -19,7 +19,6 @@ defmodule Arke.Core.Arke do defstruct [:id, :label, :active, :type, :parameters] use Arke.System - alias Arke.Core.Parameter alias Arke.Boundary.ArkeManager arke id: :arke do diff --git a/lib/arke/core/parameter.ex b/lib/arke/core/parameter.ex index 584d2eb..110e001 100644 --- a/lib/arke/core/parameter.ex +++ b/lib/arke/core/parameter.ex @@ -14,11 +14,9 @@ defmodule Arke.Core.Parameter do @moduledoc """ - Module to manage the defaults parameter + This module defines the parameter group and its functions. + By default, the Arke members are: - In order to create a new one simply use the .new(opts) - - ## Types - `string` - `integer` - `float` @@ -28,18 +26,9 @@ defmodule Arke.Core.Parameter do - `time` - `datetime` - ## Values - There are two possible ways to declare the values for a parameter: - - list => by giving a list of values \n - values: ["value 1", "value 2", ...."value n"] - - list of map => by giving a list of map. Each map **must** contain `label` and `value`. \n - values: [%{label:"value 1", value: 1},... %{label: "value 999", value: 999}] - - The result will always be a list of map containing `label` and `value`. Keep in mind that if the values are provided using a list the `label` will be autogenerated. - Remember also that all the values provided must be the same type as the [ParameterType](#module-types) and only `string`, `integer` and `float` support the `values` declaration - - ## Get list of attributes definable in opts during creation: - iex> Arke.Core.Parameter.'ParameterType'.get_parameters() + It is used to share common gen servers functions across the Arkes. In this way we have a single point of + access to the ParameterManager and all its CRUD operations. It also does not interfere with all the `Arke.System.__using__/1` + overridable functions defined in each module """ alias Arke.Boundary.ParameterManager @@ -51,25 +40,23 @@ defmodule Arke.Core.Parameter do | Parameter.Integer.t() | Parameter.Float.t() - @doc """ - Macro defining a shared struct of parameter used across Arkes - """ + use Arke.System.Group group id: "parameter" do end - def on_unit_create(_arke, %{id: id, metadata: %{project: project}} = unit) do + def on_unit_create(_arke, %{id: _id, metadata: %{project: _project}} = unit) do ParameterManager.create(unit) {:ok, unit} end - def on_unit_update(_, %{id: id, metadata: %{project: project}} = unit) do + def on_unit_update(_arke, %{id: id, metadata: %{project: project}} = unit) do ParameterManager.update(id, project, unit) {:ok, unit} end - def on_unit_delete(_, %{id: id, metadata: %{project: project}} = unit) do + def on_unit_delete(_arke, %{id: _id, metadata: %{project: _project}} = unit) do ParameterManager.remove(unit) {:ok, unit} end @@ -77,25 +64,7 @@ defmodule Arke.Core.Parameter do end defmodule Arke.Core.Parameter.String do - @moduledoc """ - Module that define the struct of a String by extending the Arke.Core.Parameter.base_parameters() - {arke_struct} = Parameter.String - - ## Element added - - `min_length` => :atom => define the min_length the string could have. It will check during creation - - `max_length` => :atom => define the max_length the string could have. It will check during creation - - `values` => [list] || [%{label: string, value: any}, ...] => use this to create a parameter with only certain values assignable. (Values must be the same type as the parameter we want to create) - - `multiple` => boolean => relevant only if values are set. It makes possible to assign more than a values defined in values - - `unique` => boolean => check if there is an existing record in the database with the same value before creating one - - `default` => String => default value - - ## Example - iex> params = [id: :string_test, min_length: 1, values: ["value1", "value2"], multiple: true] - ...> Arke.Core.Parameter.new(%{type: :string, opts: params}) - - ## Return - %Arke.Core.Parameter.String{} - """ + @moduledoc false alias Arke.Core.Parameter alias Arke.Boundary.ParameterManager @@ -104,9 +73,6 @@ defmodule Arke.Core.Parameter.String do arke id: "string" do end - @doc """ - Before create the Parameter struct check if it has some `values` and if they are formatted correctly - """ def before_load(data, _persistence_fn) do args = Arke.System.BaseParameter.check_enum(:string, Map.to_list(data)) {:ok, Enum.into(args, %{})} @@ -115,24 +81,7 @@ defmodule Arke.Core.Parameter.String do end defmodule Arke.Core.Parameter.Integer do - @moduledoc """ - Module that define the struct of a Integer by extending the Arke.Core.Parameter.base_parameters() - {arke_struct} = Parameter.Integer - - ## Element added - - `min` => :atom => define the mix value the parammeter could have - - `max` => :atom => define the max the parammeter could have - - `values` => [list] || [%{label: string, value: any}, ...] => use this to create a parameter with only certain values assignable. (Values must be the same type as the parameter we want to create) - - `multiple` => boolean => relevant only if values are set. It makes possible to assign more than a values defined in values - - `default` => Integer => default value - - ## Example - iex> params = [id: :integer_test, min: 3, max: 7.5, values: [%{label: "option 1", value: 1}, %{label: "option 2", value: 2}]] - ...> Arke.Core.Parameter.new(%{type: :integer, opts: params}) - - ## Return - %Arke.Core.Parameter.Float{} - """ + @moduledoc false alias Arke.Core.Parameter alias Arke.Boundary.ParameterManager @@ -141,9 +90,6 @@ defmodule Arke.Core.Parameter.Integer do arke id: "integer" do end - @doc """ - Before create the Parameter struct check if it has some `values` and if they are formatted correctly - """ def before_load(data, _persistence_fn) do args = Arke.System.BaseParameter.check_enum(:integer, Map.to_list(data)) {:ok, Enum.into(args, %{})} @@ -152,24 +98,7 @@ defmodule Arke.Core.Parameter.Integer do end defmodule Arke.Core.Parameter.Float do - @moduledoc """ - Module that define the struct of a Float by extending the Arke.Core.Parameter.base_parameters() - {arke_struct} = Parameter.Float - - ## Element added - - `min` => :atom => define the mix value the parammeter could have - - `max` => :atom => define the max the parammeter could have - - `values` => [list] || [%{label: string, value: any}, ...] => use this to create a parameter with only certain values assignable. (Values must be the same type as the parameter we want to create) - - `multiple` => boolean => relevant only if values are set. It makes possible to assign more than a values defined in values - - `default` => Float => default value - - ## Example - iex> params = [id: :float_test, min: 3, max: 7.5] - ...> Arke.Core.Parameter.new(%{type: :float, opts: params}) - - ## Return - %Arke.Core.Parameter.Float{} - """ + @moduledoc false alias Arke.Core.Parameter alias Arke.Boundary.ParameterManager @@ -178,9 +107,6 @@ defmodule Arke.Core.Parameter.Float do arke id: "float" do end - @doc """ - Before create the Parameter struct check if it has some `values` and if they are formatted correctly - """ def before_load(data, _persistence_fn) do args = Arke.System.BaseParameter.check_enum(:float, Map.to_list(data)) {:ok, Enum.into(args, %{})} @@ -189,20 +115,7 @@ defmodule Arke.Core.Parameter.Float do end defmodule Arke.Core.Parameter.Boolean do - @moduledoc """ - Module that define the struct of a Boolean by extending the Arke.Core.Parameter.base_parameters() - {arke_struct} = Parameter.Boolean - - ## Element added - - `default` => Boolean => default value - - ## Example - iex> params = [id: :boolean_test, default: false] - ...> Arke.Core.Parameter.Boolean(%{type: :boolean, opts: params}) - - ## Return - %Arke.Core.Parameter.Boolean{} - """ + @moduledoc false alias Arke.Core.Parameter alias Arke.Boundary.ParameterManager @@ -213,20 +126,7 @@ defmodule Arke.Core.Parameter.Boolean do end defmodule Arke.Core.Parameter.Dict do - @moduledoc """ - Module that define the struct of a Dict by extending the Arke.Core.Parameter.base_parameters() - {arke_struct} = Parameter.Dict - - ## Element added - - `default` => Dict => default value - - ## Example - iex> params = [id: :dict_test, default: %{default_key: default_value}] - ...> Arke.Core.Parameter.Dict(%{type: :dict, opts: params}) - - ## Return - %Arke.Core.Parameter.Dict{} - """ + @moduledoc false alias Arke.Core.Parameter alias Arke.Boundary.ParameterManager @@ -237,20 +137,7 @@ defmodule Arke.Core.Parameter.Dict do end defmodule Arke.Core.Parameter.List do - @moduledoc """ - Module that define the struct of a Dict by extending the Arke.Core.Parameter.base_parameters() - {arke_struct} = Parameter.Dict - - ## Element added - - `default` => Dict => default value - - ## Example - iex> params = [id: :dict_test, default: %{default_key: default_value}] - ...> Arke.Core.Parameter.Dict(%{type: :dict, opts: params}) - - ## Return - %Arke.Core.Parameter.Dict{} - """ + @moduledoc false alias Arke.Core.Parameter alias Arke.Boundary.ParameterManager @@ -261,27 +148,7 @@ defmodule Arke.Core.Parameter.List do end defmodule Arke.Core.Parameter.Date do - @moduledoc """ - Module that define the struct of a Date by extending the Arke.Core.Parameter.base_parameters(). - - ## Accepted values - Date accepts the following format as values: - - string => "YYYY-MM-DD" (separator must be - hyphen) - - sigil => ~D[YYYY-MM-DD] (separator must be - hyphen) - - struct => %Date{} - - {arke_struct} = Parameter.Date - - ## Element added - - `default` => [values](#module-accepted-values) => default value - - ## Example - iex> params = [id: :date_test, default: "1999-09-03"] - ...> Arke.Core.Parameter.new(%{type: :date, opts: params}) - - ## Return - %Arke.Core.Parameter.Date{} - """ + @moduledoc false alias Arke.Core.Parameter alias Arke.Boundary.ParameterManager @@ -293,27 +160,8 @@ defmodule Arke.Core.Parameter.Date do end defmodule Arke.Core.Parameter.Time do - @moduledoc """ - Module that define the struct of a Time by extending the Arke.Core.Parameter.base_parameters(). - - ## Accepted values - Date accepts the following format as values: - - string => "HH:MM:SS" (separator must be - hyphen) - - sigil => ~T[HH:MM:SS] (separator must be - hyphen) - - struct => %Time{} - - {arke_struct} = Parameter.Date + @moduledoc false - ## Element added - - `default` => [values](#module-accepted-values) => default value - - ## Example - iex> params = [id: :time_test, default: "23:14:15"] - ...> Arke.Core.Parameter.new(%{type: :time, opts: params}) - - ## Return - %Arke.Core.Parameter.Time{} - """ alias Arke.Core.Parameter alias Arke.Boundary.ParameterManager @@ -325,29 +173,7 @@ defmodule Arke.Core.Parameter.Time do end defmodule Arke.Core.Parameter.DateTime do - @moduledoc """ - Module that define the struct of a DateTime by extending the Arke.Core.Parameter.base_parameters(). - - ## Accepted values - Date accepts the following format as values: - - string => "YYYY-MM-DDTHH:MM:SSZ" | "YYYY-MM-DD HH:MM:SSZ" | "YYYY-MM-DD HH:MM:SS" (separator must be - hyphen for Date and colon : for Time) - - sigil => ~U[YYYY-MM-DDTHH:MM:SSZ] | ~U[YYYY-MM-DD HH:MM:SSZ] | ~N[YYYY-MM-DDTHH:MM:SSZ] | ~N[YYYY-MM-DD HH:MM:SSZ] | ~N[YYYY-MM-DD HH:MM:SS] (separator must be - hyphen for Date and colon : for Time) - - struct => %DateTime{} | %NaiveDateTime{} - - The T separator is optional. If no offset is provided (Z will be added at the end) - - {arke_struct} = Parameter.Date - - ## Element added - - `default` => [values](#module-accepted-values) => default value - - ## Example - iex> params = [id: :time_test, default: "1999-12-12 09:08:07"] - ...> Arke.Core.Parameter.new(%{type: :datetime, opts: params}) - - ## Return - %Arke.Core.Parameter.DateTime{} - """ + @moduledoc false alias Arke.Core.Parameter alias Arke.Boundary.ParameterManager @@ -359,20 +185,7 @@ defmodule Arke.Core.Parameter.DateTime do end defmodule Arke.Core.Parameter.Link do - @moduledoc """ - Module that define the struct of a Link by extending the Arke.Core.Parameter.base_parameters() - {arke_struct} = Parameter.Link - - ## Element added - - `link` => Link => link handler - - ## Example - iex> params = [id: :dict_test, default: %{default_key: default_value}] - ...> Arke.Core.Parameter.Dict(%{type: :dict, opts: params}) - - ## Return - %Arke.Core.Parameter.Link{} - """ + @moduledoc false alias Arke.Core.Parameter alias Arke.Boundary.ParameterManager @@ -384,13 +197,7 @@ defmodule Arke.Core.Parameter.Link do end defmodule Arke.Core.Parameter.Dynamic do - @moduledoc """ - Module that define the struct of a Dynamic by extending the Arke.Core.Parameter.base_parameters() - {arke_struct} = Parameter.Dynamic - - ## Return - %Arke.Core.Parameter.Dynamic{} - """ + @moduledoc false alias Arke.Core.Parameter alias Arke.Boundary.ParameterManager @@ -401,13 +208,8 @@ defmodule Arke.Core.Parameter.Dynamic do end defmodule Arke.Core.Parameter.Binary do - @moduledoc """ - Module that define the struct of a Binary by extending the Arke.Core.Parameter.base_parameters() - {arke_struct} = Parameter.Binary + @moduledoc false - ## Return - %Arke.Core.Parameter.Binary{} - """ alias Arke.Core.Parameter alias Arke.Boundary.ParameterManager use Arke.System From 4fdd21d830561a7c6428b5977ce9b278ffe3d9de Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 17 May 2024 17:29:38 +0200 Subject: [PATCH 17/20] docs: arke system using macro docs: group system using macro core: typo --- lib/arke/core/link.ex | 12 +- lib/arke/core/parameter.ex | 39 ------ lib/arke/core/query.ex | 10 +- lib/arke/group.ex | 68 ++++++---- lib/arke/query_manager.ex | 28 ++-- lib/arke/struct_manager.ex | 10 +- lib/arke/system.ex | 12 +- lib/arke/validator.ex | 6 +- lib/examples/system/macro_arke.ex | 178 ++++++++++++++++++++++++++ lib/examples/system/macro_group.ex | 110 ++++++++++++++++ lib/examples/system_macro_function.ex | 107 ---------------- mix.exs | 4 +- 12 files changed, 371 insertions(+), 213 deletions(-) create mode 100644 lib/examples/system/macro_arke.ex create mode 100644 lib/examples/system/macro_group.ex delete mode 100644 lib/examples/system_macro_function.ex diff --git a/lib/arke/core/link.ex b/lib/arke/core/link.ex index f984f6f..208adf2 100644 --- a/lib/arke/core/link.ex +++ b/lib/arke/core/link.ex @@ -61,8 +61,8 @@ defmodule Arke.Core.Link do def on_create( _, %{ - data: %{type: type, parent_id: parent_id, child_id: child_id}, - metadata: %{project: project} = metadata + data: %{type: _type, parent_id: _parent_id, child_id: _child_id}, + metadata: %{project: _project} = _metadata } = unit ) do {:ok, unit} @@ -83,7 +83,7 @@ defmodule Arke.Core.Link do _, %{ data: %{type: "parameter", parent_id: parent_id, child_id: child_id}, - metadata: %{project: project} = metadata + metadata: %{project: project} = _metadata } = unit ) do ArkeManager.remove_link( @@ -100,7 +100,7 @@ defmodule Arke.Core.Link do _, %{ data: %{type: "group", parent_id: parent_id, child_id: child_id}, - metadata: %{project: project} = metadata + metadata: %{project: project} = _metadata } = unit ) do GroupManager.remove_link( @@ -116,8 +116,8 @@ defmodule Arke.Core.Link do def on_delete( _, %{ - data: %{type: type, parent_id: parent_id, child_id: child_id}, - metadata: %{project: project} = metadata + data: %{type: _type, parent_id: _parent_id, child_id: _child_id}, + metadata: %{project: _project} = _metadata } = unit ) do {:ok, unit} diff --git a/lib/arke/core/parameter.ex b/lib/arke/core/parameter.ex index 110e001..2688d79 100644 --- a/lib/arke/core/parameter.ex +++ b/lib/arke/core/parameter.ex @@ -65,9 +65,6 @@ end defmodule Arke.Core.Parameter.String do @moduledoc false - alias Arke.Core.Parameter - alias Arke.Boundary.ParameterManager - use Arke.System arke id: "string" do @@ -82,9 +79,6 @@ end defmodule Arke.Core.Parameter.Integer do @moduledoc false - alias Arke.Core.Parameter - alias Arke.Boundary.ParameterManager - use Arke.System arke id: "integer" do @@ -99,9 +93,6 @@ end defmodule Arke.Core.Parameter.Float do @moduledoc false - alias Arke.Core.Parameter - alias Arke.Boundary.ParameterManager - use Arke.System arke id: "float" do @@ -116,9 +107,6 @@ end defmodule Arke.Core.Parameter.Boolean do @moduledoc false - alias Arke.Core.Parameter - alias Arke.Boundary.ParameterManager - use Arke.System arke id: "boolean" do @@ -127,9 +115,6 @@ end defmodule Arke.Core.Parameter.Dict do @moduledoc false - alias Arke.Core.Parameter - alias Arke.Boundary.ParameterManager - use Arke.System arke id: "dict" do @@ -138,9 +123,6 @@ end defmodule Arke.Core.Parameter.List do @moduledoc false - alias Arke.Core.Parameter - alias Arke.Boundary.ParameterManager - use Arke.System arke id: "list" do @@ -149,10 +131,6 @@ end defmodule Arke.Core.Parameter.Date do @moduledoc false - - alias Arke.Core.Parameter - alias Arke.Boundary.ParameterManager - use Arke.System arke id: "date" do @@ -161,10 +139,6 @@ end defmodule Arke.Core.Parameter.Time do @moduledoc false - - alias Arke.Core.Parameter - alias Arke.Boundary.ParameterManager - use Arke.System arke id: "time" do @@ -174,10 +148,6 @@ end defmodule Arke.Core.Parameter.DateTime do @moduledoc false - - alias Arke.Core.Parameter - alias Arke.Boundary.ParameterManager - use Arke.System arke id: "datetime" do @@ -186,9 +156,6 @@ end defmodule Arke.Core.Parameter.Link do @moduledoc false - alias Arke.Core.Parameter - alias Arke.Boundary.ParameterManager - use Arke.System arke id: "link" do @@ -198,9 +165,6 @@ end defmodule Arke.Core.Parameter.Dynamic do @moduledoc false - alias Arke.Core.Parameter - alias Arke.Boundary.ParameterManager - use Arke.System arke id: "dynamic" do @@ -209,9 +173,6 @@ end defmodule Arke.Core.Parameter.Binary do @moduledoc false - - alias Arke.Core.Parameter - alias Arke.Boundary.ParameterManager use Arke.System arke id: "binary" do diff --git a/lib/arke/core/query.ex b/lib/arke/core/query.ex index 01c57a4..1e8239b 100644 --- a/lib/arke/core/query.ex +++ b/lib/arke/core/query.ex @@ -68,7 +68,7 @@ defmodule Arke.Core.Query do - negate => used to figure out whether the condition is to be denied \n """ @spec new( - parameter :: Arke.Core.Parameter.t(), + parameter :: %Arke.Core.Unit{}, operator :: atom(), value :: any, negate :: boolean @@ -140,7 +140,7 @@ defmodule Arke.Core.Query do """ @spec add_link_filter( query :: Arke.Core.Query.t(), - unit :: Arke.Core.Unit.t(), + unit :: %Arke.Core.Unit{}, depth :: integer(), direction :: :child | :parent, connection_type :: String.t() @@ -202,7 +202,7 @@ defmodule Arke.Core.Query do - negate => used to figure out whether the condition is to be denied """ @spec new_filter( - parameter :: Arke.Core.Parameter.t(), + parameter :: %Arke.Core.Unit{}, operator :: Arke.Core.QueryManager.operator(), value :: any, negate :: boolean() @@ -242,7 +242,7 @@ defmodule Arke.Core.Query do """ @spec new_base_filter( - parameter :: Arke.Core.Parameter.t(), + parameter :: %Arke.Core.Unit{}, operator :: Arke.Core.QueryManager.operator(), value :: any, negate :: boolean() @@ -264,7 +264,7 @@ defmodule Arke.Core.Query do """ @spec add_order( query :: Arke.Core.Query.t(), - parameter :: Arke.Core.Parameter.t(), + parameter :: %Arke.Core.Unit{}, direction :: :asc | :desc ):: Arke.Core.Query.t() def add_order(query, parameter, direction) do diff --git a/lib/arke/group.ex b/lib/arke/group.ex index b7dd991..b39547f 100644 --- a/lib/arke/group.ex +++ b/lib/arke/group.ex @@ -13,9 +13,18 @@ # limitations under the License. defmodule Arke.System.Group do + @moduledoc """ + Core module which handle all the management functions for the Unit of a Group. + See `Arke.Example.System.MacroGroup` to get a list of all the available functions. + """ + + @doc """ + This macro is used whenever we want to edit the default behaviour + + use Arke.System.Group + """ defmacro __using__(_) do quote do - # @after_compile __MODULE__ Module.register_attribute(__MODULE__, :group, accumulate: false, persist: true) Module.register_attribute(__MODULE__, :parameters, accumulate: true, persist: false) Module.register_attribute(__MODULE__, :system_group, accumulate: false, persist: true) @@ -24,21 +33,44 @@ defmodule Arke.System.Group do import unquote(__MODULE__), only: [group: 1, group: 2, parameter: 3, parameter: 2] - # @before_compile unquote(__MODULE__) - + # Return the Group struct for the given module + @doc false def group_from_attr(), do: Keyword.get(__MODULE__.__info__(:attributes), :group, []) |> List.first() + + @doc false def is_group?(), do: Keyword.get(__MODULE__.__info__(:attributes), :system_group, []) |> List.first() + @doc false def on_unit_load(arke, data, _persistence_fn), do: {:ok, data} + + @doc false def before_unit_load(_arke, data, _persistence_fn), do: {:ok, data} + + @doc false def on_unit_validate(_arke, unit), do: {:ok, unit} + + @doc false def before_unit_validate(_arke, unit), do: {:ok, unit} + + @doc false def on_unit_create(_arke, unit), do: {:ok, unit} + + @doc false def before_unit_create(_arke, unit), do: {:ok, unit} + + @doc false def on_unit_struct_encode(unit, _), do: {:ok, unit} + + @doc false def on_unit_update(_arke, unit), do: {:ok, unit} + + @doc false def before_unit_update(_arke, unit), do: {:ok, unit} + + @doc false def on_unit_delete(_arke, unit), do: {:ok, unit} + + @doc false def before_unit_delete(_arke, unit), do: {:ok, unit} defoverridable on_unit_load: 3, @@ -55,39 +87,25 @@ defmodule Arke.System.Group do end end - # defmacro __before_compile__(env) do - # end - # - # def compile(translations) do - # - # end ###################################################################################################################### # Group MACRO ######################################################################################################### ###################################################################################################################### @doc """ - Macro to create an arke struct with the given parameters. - Usable only via `code` and not `iex`. - + Macro to manager a group and its related functions ## Example - group do - parameter :custom_parameter - parameter :custom_parameter2 - parameter :custom_parameter3 - parameter :custom_parameter4 + group id: :some_id do end - ## Return - %Arke.Core.Unit{} - + From now on all the overridable functions can be edited and all the public functions will be used as API custom function """ + @spec group(args :: list(), Macro.t()) :: %{} defmacro group(opts \\ [], do: block) do id = Keyword.get(opts, :id) - # metadata = Keyword.get(opts, :metadata, %{}) - # base_parameters = get_base_arke_parameters(type) + quote do id = unquote(id) @@ -109,11 +127,7 @@ defmodule Arke.System.Group do # PARAMETER MACRO #################################################################################################### ###################################################################################################################### - @doc """ - Macro used to define parameter in an arke. - See example above `arke/2` - - """ + @doc false @spec parameter(id :: atom(), type:: atom(), opts :: list()) :: Macro.t() defmacro parameter(id, type, opts \\ []) do # parameter_dict = Arke.System.BaseParameter.parameter_options(opts, id, type) diff --git a/lib/arke/query_manager.ex b/lib/arke/query_manager.ex index 2c1e801..73b0b74 100644 --- a/lib/arke/query_manager.ex +++ b/lib/arke/query_manager.ex @@ -49,7 +49,7 @@ defmodule Arke.QueryManager do @persistence Application.get_env(:arke, :persistence) @record_fields [:id, :data, :metadata, :inserted_at, :updated_at] - @type func_return() :: {:ok, Unit.t()} | Error.t() + @type func_return() :: {:ok, %Unit{}} | Error.t() @type operator() :: :eq | :contains @@ -90,7 +90,7 @@ defmodule Arke.QueryManager do @spec link( Query.t(), - unit :: Unit.t(), + unit :: %Unit{}, opts :: [direction: :child | :parent, depth: integer(), type: String.t()] ) :: Query.t() def link(query, unit, opts \\ []) do @@ -120,7 +120,7 @@ defmodule Arke.QueryManager do - `arke` -> identify the struct of the element we want to create - `args` -> the data of the new element we want to create """ - @spec create(project :: atom(), arke :: Arke.t(), args :: [...]) :: func_return() + @spec create(project :: atom(), arke :: %Arke{}, args :: [...]) :: func_return() def create(project, arke, args) do persistence_fn = @persistence[:arke_postgres][:create] with %Unit{} = unit <- Unit.load(arke, args, :create), @@ -225,7 +225,7 @@ defmodule Arke.QueryManager do - `args` -> list of key: value to update """ - @spec update(Unit.t(), args :: list()) :: func_return() + @spec update(%Unit{}, args :: list()) :: func_return() def update(%{arke_id: arke_id, metadata: %{project: project}, data: data} = current_unit, args) do persistence_fn = @persistence[:arke_postgres][:update] arke = ArkeManager.get(arke_id, project) @@ -252,7 +252,7 @@ defmodule Arke.QueryManager do - `project` -> identify the `Arke.Core.Project` - `unit` -> the unit to delete """ - @spec delete(project :: atom(), Unit.t()) :: {:ok, any()} + @spec delete(project :: atom(), %Unit{}) :: {:ok, any()} def delete(project, %{arke_id: arke_id} = unit) do arke = ArkeManager.get(arke_id, project) persistence_fn = @persistence[:arke_postgres][:delete] @@ -270,13 +270,13 @@ defmodule Arke.QueryManager do Create a query which is used to get a single element which match the given criteria. If more are returned then an exception will be raised """ - @spec get_by(opts :: [{:project,atom} | {atom,any}]) :: Unit.t() | nil + @spec get_by(opts :: [{:project,atom} | {atom,any}]) :: %Unit{} | nil def get_by(opts \\ []), do: basic_query(opts) |> one @doc """ Create a query which is used to get all the element which match the given criteria """ - @spec filter_by(opts :: [{:project,atom} | {atom,any}]) :: [Unit.t()] | [] + @spec filter_by(opts :: [{:project,atom} | {atom,any}]) :: [%Unit{}] | [] def filter_by(opts \\ []), do: basic_query(opts) |> all defp basic_query(opts) when is_map(opts), do: Map.to_list(opts) |> basic_query @@ -360,7 +360,7 @@ defmodule Arke.QueryManager do iex> QueryManager.condition(:string, :eq, "test") """ @spec condition( - parameter :: Unit.t(), + parameter :: %Unit{}, negate :: boolean(), value :: String.t() | boolean() | number() | nil, negate :: boolean() @@ -422,7 +422,7 @@ defmodule Arke.QueryManager do """ @spec filter( query :: Query.t(), - parameter :: Arke.t() | String.t() | atom(), + parameter :: %Arke{} | String.t() | atom(), operator :: operator(), value :: any, negate :: boolean() @@ -462,12 +462,12 @@ defmodule Arke.QueryManager do ## Parameter - `query` => refer to `query/1` - - `parameter => used to order the query + - `parameter` => used to order the query - `direction` => way of sorting the results (ascending or descending) """ @spec order( query :: Query.t(), - parameter :: Arke.t() | String.t() | atom(), + parameter :: %Arke{} | String.t() | atom(), direction :: :asc | :desc ) :: Query.t() def order(query, parameter, direction), @@ -502,7 +502,7 @@ defmodule Arke.QueryManager do - `limit` -> number of element to return """ @spec pagination(query :: Query.t(), offset :: integer(), limit :: integer()) :: - {count :: integer(), elements :: [] | [Unit.t()]} + {count :: integer(), elements :: [] | [%Unit{}]} def pagination(query, offset, limit) do tmp_query = %{query | orders: []} count = count(tmp_query) @@ -515,7 +515,7 @@ defmodule Arke.QueryManager do ## Parameter - query -> refer to `query/1` """ - @spec all(query :: Query.t()) :: [Unit.t()] | [] + @spec all(query :: Query.t()) :: [%Unit{}] | [] def all(query), do: execute_query(query, :all) @doc """ @@ -524,7 +524,7 @@ defmodule Arke.QueryManager do - `query` -> refer to `query/1` """ - @spec one(query :: Query.t()) :: Unit.t() | nil + @spec one(query :: Query.t()) :: %Unit{} | nil def one(query), do: execute_query(query, :one) @doc """ diff --git a/lib/arke/struct_manager.ex b/lib/arke/struct_manager.ex index ed313b0..3c17eca 100644 --- a/lib/arke/struct_manager.ex +++ b/lib/arke/struct_manager.ex @@ -41,7 +41,7 @@ defmodule Arke.StructManager do - `unit` -> unit or list of units that we want to encode - `type` -> desired encode type """ - @spec encode(unit :: [Unit.t(), ...], format :: :json) :: %{atom() => any} | [...] + @spec encode(unit :: [%Unit{}, ...], format :: :json) :: %{atom() => any} | [...] def encode(unit, opts \\ []) def encode(unit, opts) do @@ -239,7 +239,7 @@ defmodule Arke.StructManager do arke_id :: atom(), data :: %{key: any()}, format :: atom() - ) :: Unit.t() + ) :: %Unit{} def decode(project, arke_id, data, :json) when is_atom(arke_id) do ArkeManager.get(arke_id, project) |> Unit.load(data) @@ -305,7 +305,7 @@ defmodule Arke.StructManager do ## Parameters - `arke` -> Arke struct """ - @spec get_struct(arke :: Unit.t()) :: %{parameters: [parameter()], label: String.t()} + @spec get_struct(arke :: %Unit{}) :: %{parameters: [parameter()], label: String.t()} def get_struct(%{arke_id: :arke, data: data} = arke) do struct = %{parameters: get_struct_parameters(arke, %{}), label: data.label} ArkeManager.call_func(arke, :after_get_struct, [arke, struct]) @@ -319,7 +319,7 @@ defmodule Arke.StructManager do - `unit` -> Unit struct - `opts` -> Data we want to include or exclude """ - @spec get_struct(arke :: Unit.t(), unit :: Unit.t(), opts :: [{:include, [atom]} | {:exclude, [atom]}] | [...] | [] ) :: %{ + @spec get_struct(arke :: %Unit{}, unit :: %Unit{}, opts :: [{:include, [atom]} | {:exclude, [atom]}] | [...] | [] ) :: %{ parameters: [parameter()], label: String.t() } @@ -335,7 +335,7 @@ defmodule Arke.StructManager do @doc """ Get a Unit Struct """ - @spec get_struct(arke :: Unit.t(), unit :: Unit.t()) :: %{ + @spec get_struct(arke :: %Unit{}, unit :: %Unit{}) :: %{ parameters: [parameter()], label: String.t() } diff --git a/lib/arke/system.ex b/lib/arke/system.ex index 672e186..5a17d66 100644 --- a/lib/arke/system.ex +++ b/lib/arke/system.ex @@ -14,7 +14,8 @@ defmodule Arke.System do @moduledoc """ - Core module which handle all the management functions for an Arke + Core module which handle all the management functions for an Arke. + See `Arke.Example.System.MacroArke` to get a list of all the available functions. """ @doc """ @@ -24,7 +25,7 @@ defmodule Arke.System do """ defmacro __using__(_) do quote do - # @after_compile __MODULE__ + Module.register_attribute(__MODULE__, :arke, accumulate: false, persist: true) Module.register_attribute(__MODULE__, :groups, accumulate: true, persist: true) Module.register_attribute(__MODULE__, :parameters, accumulate: true, persist: false) @@ -34,6 +35,7 @@ defmodule Arke.System do import unquote(__MODULE__), only: [arke: 1, arke: 2, parameter: 3, parameter: 2, group: 1, group: 2] + # Return the Arke struct for the given module @doc false def arke_from_attr(), do: Keyword.get(__MODULE__.__info__(:attributes), :arke, []) |> List.first() @@ -104,7 +106,7 @@ defmodule Arke.System do header_file = Enum.at(file_as_list, 0) rows = file_as_list |> List.delete_at(0) - header = get_header_for_import(project, arke, header_file) |> parse_haeder_for_import(header_file) + header = get_header_for_import(project, arke, header_file) |> parse_header_for_import(header_file) {correct_units, error_units} = Enum.with_index(rows) |> Enum.reduce({[], []}, fn {row, index}, {correct_units, error_units} -> case Enum.filter(row, & !is_nil(&1)) do @@ -155,7 +157,7 @@ defmodule Arke.System do end end) end - defp parse_haeder_for_import(header, header_file) do + defp parse_header_for_import(header, header_file) do Enum.reduce(Enum.with_index(header_file), [], fn {cell, index}, acc -> case cell do nil -> acc @@ -229,7 +231,7 @@ defmodule Arke.System do arke id: :some_id do end - From now on all the overridable functions can be edited and all the public funcitons will be used as API custom function + From now on all the overridable functions can be edited and all the public functions will be used as API custom function """ @spec arke(args :: list(), Macro.t()) :: %{} defmacro arke(opts \\ [], do: block) do diff --git a/lib/arke/validator.ex b/lib/arke/validator.ex index 67330f1..5d00c55 100644 --- a/lib/arke/validator.ex +++ b/lib/arke/validator.ex @@ -22,7 +22,7 @@ defmodule Arke.Validator do alias Arke.Utils.DatetimeHandler, as: DatetimeHandler alias Arke.Core.{Arke, Unit, Parameter} - @type func_return() :: {:ok, Unit.t()} | Error.t() + @type func_return() :: {:ok, %Unit{}} | Error.t() @doc """ Function to check the given data based on the fields in the reference schema. @@ -50,7 +50,7 @@ defmodule Arke.Validator do %{:error, [message]} """ - @spec validate(unit :: Unit.t(), peristence_fn :: :create | :update, project :: atom()) :: + @spec validate(unit :: %Unit{}, peristence_fn :: :create | :update, project :: atom()) :: func_return() def validate(%{arke_id: arke_id} = unit, persistence_fn, project \\ :arke_system) do with {:ok, unit} <- check_duplicate_unit(unit, project, persistence_fn) do @@ -134,7 +134,7 @@ defmodule Arke.Validator do {value,["parameter label", message ]} in case of error """ @spec validate_parameter( - arke :: Arke.t(), + arke :: %Arke{}, parameter :: Sring.t() | atom() | Parameter.parameter_struct(), value :: any(), project :: atom() diff --git a/lib/examples/system/macro_arke.ex b/lib/examples/system/macro_arke.ex new file mode 100644 index 0000000..b8b85ea --- /dev/null +++ b/lib/examples/system/macro_arke.ex @@ -0,0 +1,178 @@ +defmodule Arke.Example.System.MacroArke do + @moduledoc""" + List of all the available overridable functions in the `Arke.System.__using__/1` macro. \n + All the functions named `before_*` are executed before the overridable function of + `Arke.System.Group.__using__/1` while the ones with `on_*` are executed before. + """ + use Arke.System + + + @doc """ + Overridable function in order to be able to manage the data before the load. + ## Parameters + - `data` => All the data of the Unit we are loading + - `persistence_fn` => Used to identify on which CRUD operations we are loading the unit, so we can pattern match it + """ + @spec before_load(data :: %{atom() => any},persistence_fn :: :create | :update) :: {:ok, %{atom() => any}} | {:error, String.t()} + def before_load(data, _persistence_fn), do: {:ok, data} + + @doc """ + Overridable function in order to be able to manage the data during the unit load + ## Parameters + - `data` => All the data of the Unit we are loading + - `persistence_fn` => Used to identify on which CRUD operations we are loading the unit, so we can pattern match it + """ + @spec on_load(data :: %{atom => any()},persistence_fn :: :create | :update) :: {:ok, %{atom() => any}} | {:error, String.t()} + def on_load(data, _persistence_fn), do: {:ok, data} + + @doc """ + Overridable function in order to be able to manage the data before the validation + ## Parameters + - `arke` => Arke we use as model to validate the parameter + - `unit` => Unit we want to validate against the arke model + """ + @spec before_validate(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{}) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def before_validate(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to manage the data right after the validation + ## Parameters + - `arke` => Arke we used as model to validate the parameter + - `unit` => Unit we have validated against the arke model + """ + @spec on_validate(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{}) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def on_validate(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to manage the data before the creation + ## Parameters + - `arke` => Arke we use as model to create the Unit + - `unit` => Unit we want to create + """ + @spec before_create(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{}) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def before_create(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to right after the creation + ## Parameters + - `arke` => Arke we used as model to create the Unit + - `unit` => Unit we have just created + """ + @spec on_create(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{}) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def on_create(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to manage the data before the encoding + ## Parameters + - `arke` => Arke we uses as model to encode the Unit struct + - `unit` => Unit we want to encode + """ + @spec before_struct_encode(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{}) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def before_struct_encode(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit right after the encoding + ## Parameters + - `arke` => Arke we used as model to encode the Unit struct + - `unit` => Unit we have just encoded + - `data` => Data we want to encode + - opts => List of options + """ + @spec on_struct_encode(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{},data :: %{atom() => any} | %{}, opts :: [] | [...]) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def on_struct_encode(_arke, _unit, data, _opts), do: {:ok, data} + + @doc """ + Overridable function in order to be able to manage the data before the update + ## Parameters + - `arke` => Arke we use as model to update the Unit + - `unit` => Unit we want to update + """ + @spec before_update(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{}) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def before_update(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to manage the data on the update + ## Parameters + - `arke` => Arke we use as model to update the Unit + - old_`unit` => Unit before the update + - `unit` => Unit after the update + """ + @spec on_update(arke :: %Arke.Core.Arke{},old_unit :: %Arke.Core.Unit{},unit :: %Arke.Core.Unit{}) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def on_update(_arke, _old_unit, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to manage the data before the deletion + ## Parameters + - `arke` => Arke we use as model to delete the Unit + - `unit` => Unit we want to delete + """ + @spec before_delete(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{}) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def before_delete(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to manage the data during the deletion + ## Parameters + - `arke` => Arke we used as model to delete the Unit + - `unit` => Unit we have just deleted + """ + @spec on_delete(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{}) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def on_delete(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to manage the data of the Unit after the encoding + ## Parameters + - `arke` => Arke we used as model to delete the Unit + - `unit` => Unit we have just deleted + - `struct` => New struct of the unit + """ + @spec after_get_struct(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{},struct:: %Arke.Core.Unit{}) :: %Arke.Core.Unit{} + def after_get_struct(_arke, _unit, struct), do: struct + + @doc """ + Overridable function in order to be able to manage the data of the Arke after the encoding + ## Parameters + - `arke` => Arke we used as model to delete the Unit + - `struct` => New struct of the Arke + """ + @spec after_get_struct(arke :: %Arke.Core.Arke{},struct:: %Arke.Core.Unit{}) :: %Arke.Core.Arke{} + def after_get_struct(_arke, struct), do: struct + + @doc """ + Overridable function used to import arkes from Excel file + """ + def import(%{runtime_data: %{conn: %{method: "POST"}=_conn}, metadata: %{project: _project}} = _arke), do: {:ok, %{}, 201} + + @doc """ + Overridable function used to import units from excels files + """ + defp import_units(_arke, _project, _member, _file, _mode), do: {:ok, %{}, 201} + + @doc """ + Overridable function used to get all the units that will be used in the import + """ + defp get_all_units_for_import(_project), do: [] + + @doc """ + Overridable function used to create Units struct from the data in an import file + """ + defp load_units(_project, _arke, _header, _row, _, "default"), do: {:ok, []} + + @doc """ + Overridable function used to get all the units already created + """ + defp get_existing_units_for_import(_project, _arke, _header, _units_args), do: [] + + @doc """ + Overridable function used to check if all the units for the import are valid or not + """ + defp check_existing_units_for_import(_project, _arke, _header, _units_args, _existing_units), do: true + + defp get_import_value(_header, _row, _column), do: "" + + @doc """ + Return the values of `Arke.Parameter.BaseParameter` + """ + @spec base_parameters() :: [Arke.Parameter.BaseParameter.t()] | [] + def base_parameters(), do: [] + +end \ No newline at end of file diff --git a/lib/examples/system/macro_group.ex b/lib/examples/system/macro_group.ex new file mode 100644 index 0000000..cd34bfb --- /dev/null +++ b/lib/examples/system/macro_group.ex @@ -0,0 +1,110 @@ +defmodule Arke.Example.System.MacroGroup do + @moduledoc """ + List of all the available overridable functions in the `Arke.System.Group.__using__/1` macro. \n + All the functions named `before_*` are executed after the overridable function of + `Arke.System.__using__/1` while the ones with `on_*` are executed after. + """ + use Arke.System.Group + + @doc """ + Overridable function in order to be able to edit data before the unit load + ## Parameters + - `arke` => Arke of the group we are preparing the load + - `data` => All the data of the Unit we are loading + - `persistence_fn` => Used to identify on which CRUD operations we are loading the unit, so we can pattern match it + """ + @spec before_unit_load(arke :: %Arke.Core.Arke{},data :: %{atom => any()},persistence_fn :: :create | :update) :: {:ok, %{atom() => any}} | {:error, String.t()} + def before_unit_load(_arke, data, _persistence_fn), do: {:ok, data} + + @doc """ + Overridable function in order to be able to edit data during the unit load + ## Parameters + - `arke` => Arke of the group we are permorming the load + - `data` => All the data of the Unit we are loading + - `persistence_fn` => Used to identify on which CRUD operations we are loading the unit, so we can pattern match it + """ + @spec on_unit_load(arke :: %Arke.Core.Arke{},data :: %{atom => any()},persistence_fn :: :create | :update) :: {:ok, %{atom() => any}} | {:error, String.t()} + def on_unit_load(_arke, data, _persistence_fn), do: {:ok, data} + + @doc """ + Overridable function in order to be able to edit data before the validation + ## Parameters + - `arke` => Arke we use as model to validate the parameter + - `unit` => Unit we want to validate against the arke model + """ + @spec before_unit_validate(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{}) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def before_unit_validate(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data during the validation + ## Parameters + - `arke` => Arke we used as model to validate the parameter + - `unit` => Unit we have validated against the arke model + """ + @spec on_unit_validate(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{}) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def on_unit_validate(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data before the creation + ## Parameters + - `arke` => Arke we use as model to create the Unit + - `unit` => Unit we want to create + """ + @spec before_unit_create(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{}) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def before_unit_create(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data during the creation + ## Parameters + - `arke` => Arke we used as model to create the Unit + - `unit` => Unit we have just created + """ + @spec on_unit_create(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{}) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def on_unit_create(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data during the encoding + ## Parameters + - `unit` => Unit we have just encoded + - `arke` => Arke we used as model to encode the Unit struct + """ + @spec on_unit_struct_encode(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{}) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def on_unit_struct_encode(unit, _arke), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data before the update + ## Parameters + - `arke` => Arke we use as model to update the Unit + - `unit` => Unit before the update + """ + @spec before_unit_update(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{}) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def before_unit_update(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data on the update + ## Parameters + - `arke` => Arke we use as model to update the Unit + - `unit` => Unit after the update + """ + @spec on_unit_update(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{}) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def on_unit_update(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data before the deletion + ## Parameters + - `arke` => Arke we use as model to delete the Unit + - `unit` => Unit we want to delete + """ + @spec before_unit_delete(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{}) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def before_unit_delete(_arke, unit), do: {:ok, unit} + + @doc """ + Overridable function in order to be able to edit data during the deletion + ## Parameters + - `arke` => Arke we used as model to delete the Unit + - `unit` => Unit we have just deleted + """ + @spec on_unit_delete(arke :: %Arke.Core.Arke{},unit :: %Arke.Core.Unit{}) :: {:ok, %Arke.Core.Unit{}} | {:error, String.t()} + def on_unit_delete(_arke, unit), do: {:ok, unit} + +end \ No newline at end of file diff --git a/lib/examples/system_macro_function.ex b/lib/examples/system_macro_function.ex deleted file mode 100644 index 4b14b29..0000000 --- a/lib/examples/system_macro_function.ex +++ /dev/null @@ -1,107 +0,0 @@ -defmodule Arke.Example.SystemMacroFunction do - @moduledoc""" - Show all the available overridable functions in the `Arke.System` macro - """ - use Arke.System - @doc """ - Overridable function in order to be able to edit data during the unit load - """ - def on_load(data, _persistence_fn), do: {:ok, data} - - @doc """ - Overridable function in order to be able to edit data before the load - """ - def before_load(data, _persistence_fn), do: {:ok, data} - - @doc """ - Overridable function in order to be able to edit data during the validation - """ - def on_validate(_arke, unit), do: {:ok, unit} - - @doc """ - Overridable function in order to be able to edit data before the validation - """ - def before_validate(_arke, unit), do: {:ok, unit} - - @doc """ - Overridable function in order to be able to edit data during the creation - """ - def on_create(_arke, unit), do: {:ok, unit} - - @doc """ - Overridable function in order to be able to edit data before the creation - """ - def before_create(_arke, unit), do: {:ok, unit} - @doc """ - Overridable function in order to be able to edit data during the encoding - """ - def on_struct_encode(_, _, data, _opts), do: {:ok, data} - @doc """ - Overridable function in order to be able to edit data before the encoding - """ - def before_struct_encode(_, unit), do: {:ok, unit} - @doc """ - Overridable function in order to be able to edit data on the update - """ - def on_update(_arke, _old_unit, unit), do: {:ok, unit} - @doc """ - Overridable function in order to be able to edit data before the update - """ - def before_update(_arke, unit), do: {:ok, unit} - @doc """ - Overridable function in order to be able to edit data during the deletion - """ - def on_delete(_arke, unit), do: {:ok, unit} - @doc """ - Overridable function in order to be able to edit data before the deletion - """ - def before_delete(_arke, unit), do: {:ok, unit} - - @doc """ - Overridable function in order to be able to edit data after the encoding - """ - def after_get_struct(_arke, _unit, struct), do: struct - def after_get_struct(_arke, struct), do: struct - - @doc """ - Overridable function used to import arkes from excel file - """ - def import(%{runtime_data: %{conn: %{method: "POST"}=_conn}, metadata: %{project: _project}} = _arke), do: {:ok, %{}, 201} - - @doc """ - Overridable function used to import units from excels files - """ - defp import_units(_arke, _project, _member, _file, _mode), do: {:ok, %{}, 201} - - @doc """ - Overridable function used to get all the units that will be used in the import - """ - defp get_all_units_for_import(_project), do: [] - - @doc """ - Overridable function used to create Units struct from the data in an import file - """ - defp load_units(_project, _arke, _header, _row, _, "default"), do: {:ok, []} - @doc """ - Overridable function used to get all the units already created - """ - defp get_existing_units_for_import(_project, _arke, _header, _units_args), do: [] - @doc """ - Overridable function used to check if all the units for the import are valid or not - """ - defp check_existing_units_for_import(_project, _arke, _header, _units_args, _existing_units), do: true - defp get_import_value(_header, _row, _column), do: "" - - @doc """ - Return the values of `Arke.Parameter.BaseParameter` - """ - def base_parameters(), do: [] - @doc """ - Return the Arke struct for the given module - """ - def arke_from_attr(), do: nil - @doc """ - Return all the groups where the Arke, defined in the module, belongs to - """ - def groups_from_attr(), do: [] -end \ No newline at end of file diff --git a/mix.exs b/mix.exs index dd16b42..0cf1b23 100644 --- a/mix.exs +++ b/mix.exs @@ -36,7 +36,6 @@ defmodule Arke.MixProject do logo: @logo_url, extras: ["README.md", "LICENSE"], groups_for_modules: groups_for_modules(), - extras: extras() ] ] end @@ -113,7 +112,8 @@ defmodule Arke.MixProject do Parameter: [~r"Arke.Core.Parameter"], Query: [~r"Arke.Core.Query"], Core: [~r"Arke.Core."], - Utils: [~r"Arke.Utils."] + Utils: [~r"Arke.Utils."], + Example: [~r"Arke.Example."], ] end defp extras do From 302c78a890effe287c7c7264a9428169fd8c5fb5 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 17 May 2024 17:37:54 +0200 Subject: [PATCH 18/20] docs: base arke,group and parameter --- lib/arke/core/arke.ex | 4 ++-- lib/arke/group.ex | 3 +++ lib/arke/system.ex | 6 ++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/arke/core/arke.ex b/lib/arke/core/arke.ex index 63656fe..dfb50cc 100644 --- a/lib/arke/core/arke.ex +++ b/lib/arke/core/arke.ex @@ -13,8 +13,8 @@ # limitations under the License. defmodule Arke.Core.Arke do - @moduledoc """ - Defines the skeleton of an Arke by defining its characteristics and the various parameters of which it is composed + @moduledoc false && """ + This module is used as entrypoint for every Arke created which does not have a module associated """ defstruct [:id, :label, :active, :type, :parameters] diff --git a/lib/arke/group.ex b/lib/arke/group.ex index b39547f..ef3217f 100644 --- a/lib/arke/group.ex +++ b/lib/arke/group.ex @@ -143,5 +143,8 @@ defmodule Arke.System.Group do end defmodule Arke.System.BaseGroup do + @moduledoc false && """ + This module is used as entrypoint for every Group created which does not have a module associated + """ use Arke.System.Group end diff --git a/lib/arke/system.ex b/lib/arke/system.ex index 5a17d66..b076c4f 100644 --- a/lib/arke/system.ex +++ b/lib/arke/system.ex @@ -338,16 +338,18 @@ defmodule Arke.System do end defmodule Arke.System.Arke do + @moduledoc false use Arke.System end defmodule Arke.System.BaseArke do + @moduledoc false defstruct [:id, :label, :active, :type, :parameters, :metadata] end defmodule Arke.System.BaseParameter do - @moduledoc """ - Default struct for every Parameter type + @moduledoc false && """ + This module is used as entrypoint for every Parameter created which does not have a module associated """ defstruct [:id, :label, :active, :metadata, :type, :parameters] From bd049486c60f77018c9fb935dfecf77167f70962 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 17 May 2024 17:51:16 +0200 Subject: [PATCH 19/20] docs: validator --- lib/arke/validator.ex | 46 ++++++++++--------------------------------- 1 file changed, 10 insertions(+), 36 deletions(-) diff --git a/lib/arke/validator.ex b/lib/arke/validator.ex index 5d00c55..6994bad 100644 --- a/lib/arke/validator.ex +++ b/lib/arke/validator.ex @@ -14,7 +14,7 @@ defmodule Arke.Validator do @moduledoc """ - This module provide validation before assign a certain value to an `{arke_struct}` + This module provide the data validation of a unit before its creation """ alias Arke.Boundary.{ArkeManager, ParameterManager} alias Arke.QueryManager, as: QueryManager @@ -22,36 +22,17 @@ defmodule Arke.Validator do alias Arke.Utils.DatetimeHandler, as: DatetimeHandler alias Arke.Core.{Arke, Unit, Parameter} - @type func_return() :: {:ok, %Unit{}} | Error.t() @doc """ - Function to check the given data based on the fields in the reference schema. - + Check if the given unit has valid data. + In order to do so, it uses the arke_id to get the arke which has been used to load the parameters. + Then check whether the value of each unit parameter meets the type and requirements of those associated with the arke ## Parameters - `unit` -> unit to validate - `persistence_fn` -> operation identifiers - `project` -> `Arke.Core.Project` - - ## Example - iex> arke = ArkeManager.get(:arke, :arke_system) - ...> unit = Arke.Core.Unit.load(arke, %{ - id: :test, - label: "Test", - type: "arke", - active: true, - parameters: [], - metadata: %{} - }) - - ...> Arke.Validator.validate(unit, :update, :test_schema) - - ## Return - %{:ok,_} - %{:error, [message]} - """ - @spec validate(unit :: %Unit{}, peristence_fn :: :create | :update, project :: atom()) :: - func_return() + @spec validate(unit :: %Unit{}, peristence_fn :: :create | :update, project :: atom()) :: {:ok, %Unit{}} | {:error, [Error.t()]} def validate(%{arke_id: arke_id} = unit, persistence_fn, project \\ :arke_system) do with {:ok, unit} <- check_duplicate_unit(unit, project, persistence_fn) do {%{data: data} = unit, errors} = before_validate(unit, project) @@ -117,28 +98,21 @@ defmodule Arke.Validator do defp get_result({unit, _errors} = _res), do: {:ok, unit} @doc """ - Check if the value can be assigned to a given parameter in a specific schema struct. + Check if the value can be assigned to a given parameter for the given Arke. + It always returns a tuple with the given value and a list of errors, if any, otherwise it is empty. ## Parameters - - `schema_struct` -> the element where to find and check the field - - `field` -> the id of the paramater + - `arke` -> used as a model to get all the requirements to validate the value of parameter + - `parameter` -> the id of the parameter - `value` -> the value we want to assign to the above field - `project` -> identify the `Arke.Core.Project` - - ## Example - iex> arke = ArkeManager.get(:arke, :arke_system) - ...> Arke.Boundary.ArkeValidator.validate_field(arke, :label, "test") - - ## Returns - {value,[]} if success - {value,["parameter label", message ]} in case of error """ @spec validate_parameter( arke :: %Arke{}, parameter :: Sring.t() | atom() | Parameter.parameter_struct(), value :: any(), project :: atom() - ) :: func_return() + ) :: {any(),[Error.t()]} | {any(),[]} def validate_parameter(arke, parameter, value, project \\ :arke_system) def validate_parameter(arke, parameter, value, project) when is_atom(parameter) do From 7136d713ca18d39ebe48bd1a4783207fbfa446d6 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 17 May 2024 18:37:20 +0200 Subject: [PATCH 20/20] docs: error generator --- lib/arke/utils/error_generator.ex | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/arke/utils/error_generator.ex b/lib/arke/utils/error_generator.ex index d3ebdbf..f68fd60 100644 --- a/lib/arke/utils/error_generator.ex +++ b/lib/arke/utils/error_generator.ex @@ -14,16 +14,7 @@ defmodule Arke.Utils.ErrorGenerator do @moduledoc """ - Documentation for `Arke.Utils.ErrorGenerator` - """ - - @doc """ - Create standardized errors - - ## Parameters - - context => string => the context where the error has been generated - - errors => list | string => the error itself - + Used to standardize all the arke package errors ## Example iex> Arke.Utils.ErrorGenerator.create(:auth, "login error") @@ -31,9 +22,19 @@ defmodule Arke.Utils.ErrorGenerator do {:error , [%{context: "context_value", message: "message_value"}, ...]} """ - @type t() :: {:error, [%{context: String.t(), message: String.t()}]} + @doc """ + Create standardized errors. + ## Note + - If a list of error is provided they will be created with the same context + - If a string is provided the above example will be returned + + ## Parameters + - `context` => the context where the error has been generated + - `errors` => the error itself + + """ - @spec create(context :: String.t(), errors :: list() | String.t()) :: + @spec create(context :: String.t() | atom(), errors :: [String.t()] | String.t()) :: {:error, [%{context: String.t(), message: String.t()}]} def create(context, errors) when is_list(errors) do {:error, create_map(context, errors)}