defmodule DaProductApp.ParameterManagement.ParameterDefinition do use Ecto.Schema import Ecto.Changeset import Ecto.Query @primary_key {:id, :id, autogenerate: true} @foreign_key_type :id schema "parameter_definitions" do field :key, :string field :name, :string field :description, :string field :data_type, :string field :default_value, :string field :is_required, :boolean, default: false field :is_system, :boolean, default: false field :validation_rules, :map field :display_order, :integer, default: 0 field :is_active, :boolean, default: true belongs_to :category, DaProductApp.ParameterManagement.ParameterCategory has_many :template_values, DaProductApp.ParameterManagement.ParameterTemplateValue has_many :device_overrides, DaProductApp.ParameterManagement.DeviceParameterOverride timestamps() end @valid_data_types ~w(string integer boolean select multi_select) def changeset(definition, attrs) do definition |> cast(attrs, [ :key, :name, :description, :category_id, :data_type, :default_value, :is_required, :is_system, :validation_rules, :display_order, :is_active ]) |> validate_required([:key, :name, :category_id, :data_type]) |> validate_length(:key, max: 100) |> validate_length(:name, max: 255) |> validate_inclusion(:data_type, @valid_data_types) |> unique_constraint(:key) |> validate_number(:display_order, greater_than_or_equal_to: 0) |> validate_validation_rules() end def active_query(query \\ __MODULE__) do from d in query, where: d.is_active == true end def by_category(query \\ __MODULE__, category_id) do from d in query, where: d.category_id == ^category_id, order_by: [asc: d.display_order, asc: d.name] end def system_parameters(query \\ __MODULE__) do from d in query, where: d.is_system == true end def user_parameters(query \\ __MODULE__) do from d in query, where: d.is_system == false end defp validate_validation_rules(changeset) do case get_field(changeset, :validation_rules) do nil -> changeset rules when is_map(rules) -> # Validate the structure of validation rules based on data type data_type = get_field(changeset, :data_type) validate_rules_for_type(changeset, data_type, rules) _ -> add_error(changeset, :validation_rules, "must be a valid map") end end defp validate_rules_for_type(changeset, "integer", rules) do # Validate integer-specific rules like min, max case validate_integer_rules(rules) do :ok -> changeset {:error, message} -> add_error(changeset, :validation_rules, message) end end defp validate_rules_for_type(changeset, "string", rules) do # Validate string-specific rules like min_length, max_length, pattern case validate_string_rules(rules) do :ok -> changeset {:error, message} -> add_error(changeset, :validation_rules, message) end end defp validate_rules_for_type(changeset, data_type, rules) when data_type in ["select", "multi_select"] do # Validate select-specific rules like options case validate_select_rules(rules) do :ok -> changeset {:error, message} -> add_error(changeset, :validation_rules, message) end end defp validate_rules_for_type(changeset, _data_type, _rules), do: changeset defp validate_integer_rules(rules) do cond do Map.has_key?(rules, "min") and not is_integer(rules["min"]) -> {:error, "min must be an integer"} Map.has_key?(rules, "max") and not is_integer(rules["max"]) -> {:error, "max must be an integer"} Map.has_key?(rules, "min") and Map.has_key?(rules, "max") and rules["min"] > rules["max"] -> {:error, "min cannot be greater than max"} true -> :ok end end defp validate_string_rules(rules) do cond do Map.has_key?(rules, "min_length") and not is_integer(rules["min_length"]) -> {:error, "min_length must be an integer"} Map.has_key?(rules, "max_length") and not is_integer(rules["max_length"]) -> {:error, "max_length must be an integer"} Map.has_key?(rules, "min_length") and Map.has_key?(rules, "max_length") and rules["min_length"] > rules["max_length"] -> {:error, "min_length cannot be greater than max_length"} Map.has_key?(rules, "pattern") and not is_binary(rules["pattern"]) -> {:error, "pattern must be a string"} true -> :ok end end defp validate_select_rules(rules) do cond do not Map.has_key?(rules, "options") -> {:error, "select parameters must have options"} not is_list(rules["options"]) -> {:error, "options must be a list"} Enum.empty?(rules["options"]) -> {:error, "options cannot be empty"} true -> :ok end end def validate_value(parameter, value) do case parameter.data_type do "string" -> validate_string_value(parameter, value) "integer" -> validate_integer_value(parameter, value) "boolean" -> validate_boolean_value(parameter, value) "select" -> validate_select_value(parameter, value) "multi_select" -> validate_multi_select_value(parameter, value) _ -> {:error, "Unknown data type"} end end defp validate_string_value(parameter, value) when is_binary(value) do rules = parameter.validation_rules || %{} cond do parameter.is_required and (is_nil(value) or value == "") -> {:error, "#{parameter.name} is required"} rules["min_length"] && String.length(value) < rules["min_length"] -> {:error, "#{parameter.name} must be at least #{rules["min_length"]} characters"} rules["max_length"] && String.length(value) > rules["max_length"] -> {:error, "#{parameter.name} cannot exceed #{rules["max_length"]} characters"} rules["pattern"] && not Regex.match?(~r/#{rules["pattern"]}/, value) -> {:error, "#{parameter.name} format is invalid"} true -> :ok end end defp validate_string_value(parameter, _), do: {:error, "#{parameter.name} must be a string"} defp validate_integer_value(parameter, value) when is_binary(value) do case Integer.parse(value) do {int_value, ""} -> validate_integer_value(parameter, int_value) _ -> {:error, "#{parameter.name} must be a valid integer"} end end defp validate_integer_value(parameter, value) when is_integer(value) do rules = parameter.validation_rules || %{} cond do parameter.is_required and is_nil(value) -> {:error, "#{parameter.name} is required"} rules["min"] && value < rules["min"] -> {:error, "#{parameter.name} must be at least #{rules["min"]}"} rules["max"] && value > rules["max"] -> {:error, "#{parameter.name} cannot exceed #{rules["max"]}"} true -> :ok end end defp validate_integer_value(parameter, _), do: {:error, "#{parameter.name} must be an integer"} defp validate_boolean_value(parameter, value) when value in [true, false, "true", "false"] do if parameter.is_required and is_nil(value) do {:error, "#{parameter.name} is required"} else :ok end end defp validate_boolean_value(parameter, _), do: {:error, "#{parameter.name} must be true or false"} defp validate_select_value(parameter, value) when is_binary(value) do rules = parameter.validation_rules || %{} options = rules["options"] || [] cond do parameter.is_required and (is_nil(value) or value == "") -> {:error, "#{parameter.name} is required"} not Enum.member?(options, value) -> {:error, "#{parameter.name} must be one of: #{Enum.join(options, ", ")}"} true -> :ok end end defp validate_select_value(parameter, _), do: {:error, "#{parameter.name} must be a valid option"} defp validate_multi_select_value(parameter, value) when is_list(value) do rules = parameter.validation_rules || %{} options = rules["options"] || [] cond do parameter.is_required and Enum.empty?(value) -> {:error, "#{parameter.name} is required"} not Enum.all?(value, fn v -> Enum.member?(options, v) end) -> {:error, "All #{parameter.name} values must be from: #{Enum.join(options, ", ")}"} true -> :ok end end defp validate_multi_select_value(parameter, _), do: {:error, "#{parameter.name} must be a list of valid options"} end