validate.ex (4628B)
1 # Zenflows is designed to implement the Valueflows vocabulary, 2 # written and maintained by srfsh <info@dyne.org>. 3 # Copyright (C) 2021-2022 Dyne.org foundation <foundation@dyne.org>. 4 # 5 # This program is free software: you can redistribute it and/or modify 6 # it under the terms of the GNU Affero General Public License as published by 7 # the Free Software Foundation, either version 3 of the License, or 8 # (at your option) any later version. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU Affero General Public License for more details. 14 # 15 # You should have received a copy of the GNU Affero General Public License 16 # along with this program. If not, see <https://www.gnu.org/licenses/>. 17 18 defmodule Zenflows.VF.Validate do 19 @moduledoc """ 20 Common Valueflows validators for Ecto.Changesets. All the limitations 21 here are rough and can be changed in the future. 22 """ 23 24 alias Ecto.Changeset, as: Chset 25 26 require Logger 27 28 @doc "Checks if the given string field is [16, 2048] bytes long." 29 @spec key(Chset.t(), atom()) :: Chset.t() 30 def key(cset, field) do 31 Chset.validate_change(cset, field, :valflow, fn 32 _, str when byte_size(str) < 16 -> 33 [{field, "should be at least 16 bytes long"}] 34 _, str when byte_size(str) > 2048 -> 35 [{field, "should be at most 2048 bytes long"}] 36 _, _ -> 37 [] 38 end) 39 end 40 41 @doc "Checks if the given string field is [1, 256] bytes long." 42 @spec name(Chset.t(), atom()) :: Chset.t() 43 def name(cset, field) do 44 Chset.validate_change(cset, field, :valflow, fn 45 _, str when byte_size(str) < 1 -> 46 [{field, "should be at least 1 byte long"}] 47 _, str when byte_size(str) > 256 -> 48 [{field, "should be at most 256 bytes long"}] 49 _, _ -> 50 [] 51 end) 52 end 53 54 @doc "Checks if the given string field is [1, 2048] bytes long." 55 @spec note(Chset.t(), atom()) :: Chset.t() 56 def note(cset, field) do 57 Chset.validate_change(cset, field, :valflow, fn 58 _, str when byte_size(str) < 1 -> 59 [{field, "should be at least 1 bytes long"}] 60 _, str when byte_size(str) > 2048 -> 61 [{field, "should be at most 2048 bytes long"}] 62 _, _ -> 63 [] 64 end) 65 end 66 67 @doc "Checks if the given string is [1, 512] bytes long." 68 @spec uri(Chset.t(), atom()) :: Chset.t() 69 def uri(cset, field) do 70 Chset.validate_change(cset, field, :valflow, fn 71 _, str when byte_size(str) < 1 -> 72 [{field, "should be at least 1 bytes long"}] 73 _, str when byte_size(str) > 512 -> 74 [{field, "should be at most 512 bytes long"}] 75 _, _ -> 76 [] 77 end) 78 end 79 80 @mebibyte 1024 * 1024 81 82 @doc """ 83 Check if the given base64-encoded binary data is at least 1B, at most 84 25MiB in size. And, display a warning if it is longer than 4MiB. 85 """ 86 @spec img(Chset.t(), atom()) :: Chset.t() 87 def img(cset, field) do 88 Chset.validate_change(cset, field, :valflow, fn 89 _, str when byte_size(str) < 1 -> 90 [{field, "should be at least 1B long"}] 91 _, str when byte_size(str) > 25 * @mebibyte -> 92 [{field, "should be at most 25MiB long"}] 93 _, str when byte_size(str) > 4 * @mebibyte -> 94 Logger.warning("file exceeds 4MiB") 95 [] 96 _, _ -> 97 [] 98 end) 99 end 100 101 @doc """ 102 Checks if the given classifications (list of strings) for: 103 104 - Each item in the list is [1, 512] bytes long; 105 - The list can contain only [1, 128] items. 106 """ 107 @spec class(Chset.t(), atom()) :: Chset.t() 108 def class(cset, field) do 109 Chset.validate_change(cset, field, :valflow, fn 110 _, [] -> 111 [{field, "must contain at least 1 item"}] 112 _, list -> 113 case do_class(list) do 114 {:exceeds, _ind} -> [{field, "must contain at most 128 items"}] 115 {:short, ind} -> [{field, "the item at #{ind + 1} cannot be shorter than 1 bytes"}] 116 {:long, ind} -> [{field, "the item at #{ind + 1} cannot be longer than 512 bytes"}] 117 {:valid, _ind} -> [] 118 end 119 end) 120 end 121 122 @spec do_class([String.t()]) :: {atom(), integer()} 123 defp do_class(list) do 124 do_class(list, 0, 128) 125 end 126 127 # The rationale of this function is to loop over the list while decreasing 128 # `remaining' and increasing `index' until either one of these happen (in 129 # that order): 130 # * remaining hits 0 131 # * one of the items in the list is shorter than 3 bytes long 132 # * one of the items in the list is longer than 512 bytes long 133 @spec do_class([String.t()], integer(), integer()) :: {atom(), integer()} 134 defp do_class([head | tail], index, remaining) do 135 cond do 136 remaining == 0 -> {:exceeds, index} 137 byte_size(head) < 1 -> {:short, index} 138 byte_size(head) > 512 -> {:long, index} 139 true -> do_class(tail, index + 1, remaining - 1) 140 end 141 end 142 143 defp do_class([], index, _) do 144 {:valid, index - 1} 145 end 146 end