type.ex (43258B)
1 defmodule Ecto.Type do 2 @moduledoc """ 3 Defines functions and the `Ecto.Type` behaviour for implementing 4 basic custom types. 5 6 Ecto provides two types of custom types: basic types and 7 parameterized types. Basic types are simple, requiring only four 8 callbacks to be implemented, and are enough for most occasions. 9 Parameterized types can be customized on the field definition and 10 provide a wide variety of callbacks. 11 12 The definition of basic custom types and all of their callbacks are 13 available in this module. You can learn more about parameterized 14 types in `Ecto.ParameterizedType`. If in doubt, prefer to use 15 basic custom types and rely on parameterized types if you need 16 the extra functionality. 17 18 ## Example 19 20 Imagine you want to store a URI struct as part of a schema in a 21 url-shortening service. There isn't an Ecto field type to support 22 that value at runtime therefore a custom one is needed. 23 24 You also want to query not only by the full url, but for example 25 by specific ports used. This is possible by putting the URI data 26 into a map field instead of just storing the plain 27 string representation. 28 29 from s in ShortUrl, 30 where: fragment("?->>? ILIKE ?", s.original_url, "port", "443") 31 32 So the custom type does need to handle the conversion from 33 external data to runtime data (`c:cast/1`) as well as 34 transforming that runtime data into the `:map` Ecto native type and 35 back (`c:dump/1` and `c:load/1`). 36 37 defmodule EctoURI do 38 use Ecto.Type 39 def type, do: :map 40 41 # Provide custom casting rules. 42 # Cast strings into the URI struct to be used at runtime 43 def cast(uri) when is_binary(uri) do 44 {:ok, URI.parse(uri)} 45 end 46 47 # Accept casting of URI structs as well 48 def cast(%URI{} = uri), do: {:ok, uri} 49 50 # Everything else is a failure though 51 def cast(_), do: :error 52 53 # When loading data from the database, as long as it's a map, 54 # we just put the data back into a URI struct to be stored in 55 # the loaded schema struct. 56 def load(data) when is_map(data) do 57 data = 58 for {key, val} <- data do 59 {String.to_existing_atom(key), val} 60 end 61 {:ok, struct!(URI, data)} 62 end 63 64 # When dumping data to the database, we *expect* a URI struct 65 # but any value could be inserted into the schema struct at runtime, 66 # so we need to guard against them. 67 def dump(%URI{} = uri), do: {:ok, Map.from_struct(uri)} 68 def dump(_), do: :error 69 end 70 71 Now we can use our new field type above in our schemas: 72 73 defmodule ShortUrl do 74 use Ecto.Schema 75 76 schema "posts" do 77 field :original_url, EctoURI 78 end 79 end 80 81 Note: `nil` values are always bypassed and cannot be handled by 82 custom types. 83 84 ## Custom types and primary keys 85 86 Remember that, if you change the type of your primary keys, 87 you will also need to change the type of all associations that 88 point to said primary key. 89 90 Imagine you want to encode the ID so they cannot enumerate the 91 content in your application. An Ecto type could handle the conversion 92 between the encoded version of the id and its representation in the 93 database. For the sake of simplicity, we'll use base64 encoding in 94 this example: 95 96 defmodule EncodedId do 97 use Ecto.Type 98 99 def type, do: :id 100 101 def cast(id) when is_integer(id) do 102 {:ok, encode_id(id)} 103 end 104 def cast(_), do: :error 105 106 def dump(id) when is_binary(id) do 107 Base.decode64(id) 108 end 109 110 def load(id) when is_integer(id) do 111 {:ok, encode_id(id)} 112 end 113 114 defp encode_id(id) do 115 id 116 |> Integer.to_string() 117 |> Base.encode64 118 end 119 end 120 121 To use it as the type for the id in our schema, we can use the 122 `@primary_key` module attribute: 123 124 defmodule BlogPost do 125 use Ecto.Schema 126 127 @primary_key {:id, EncodedId, autogenerate: true} 128 schema "posts" do 129 belongs_to :author, Author, type: EncodedId 130 field :content, :string 131 end 132 end 133 134 defmodule Author do 135 use Ecto.Schema 136 137 @primary_key {:id, EncodedId, autogenerate: true} 138 schema "authors" do 139 field :name, :string 140 has_many :posts, BlogPost 141 end 142 end 143 144 The `@primary_key` attribute will tell ecto which type to 145 use for the id. 146 147 Note the `type: EncodedId` option given to `belongs_to` in 148 the `BlogPost` schema. By default, Ecto will treat 149 associations as if their keys were `:integer`s. Our primary 150 keys are a custom type, so when Ecto tries to cast those 151 ids, it will fail. 152 153 Alternatively, you can set `@foreign_key_type EncodedId` 154 after `@primary_key` to automatically configure the type 155 of all `belongs_to` fields. 156 """ 157 158 import Kernel, except: [match?: 2] 159 160 @doc false 161 defmacro __using__(_opts) do 162 quote location: :keep do 163 @behaviour Ecto.Type 164 def embed_as(_), do: :self 165 def equal?(term1, term2), do: term1 == term2 166 defoverridable [embed_as: 1, equal?: 2] 167 end 168 end 169 170 @typedoc "An Ecto type, primitive or custom." 171 @type t :: primitive | custom 172 173 @typedoc "Primitive Ecto types (handled by Ecto)." 174 @type primitive :: base | composite 175 176 @typedoc "Custom types are represented by user-defined modules." 177 @type custom :: module | {:parameterized, module, term} 178 179 @type base :: :integer | :float | :boolean | :string | :map | 180 :binary | :decimal | :id | :binary_id | 181 :utc_datetime | :naive_datetime | :date | :time | :any | 182 :utc_datetime_usec | :naive_datetime_usec | :time_usec 183 184 @type composite :: {:array, t} | {:map, t} | private_composite 185 186 @typep private_composite :: {:maybe, t} | {:in, t} | {:param, :any_datetime} 187 188 @base ~w( 189 integer float decimal boolean string map binary id binary_id any 190 utc_datetime naive_datetime date time 191 utc_datetime_usec naive_datetime_usec time_usec 192 )a 193 @composite ~w(array map maybe in param)a 194 195 @doc """ 196 Returns the underlying schema type for the custom type. 197 198 For example, if you want to provide your own date 199 structures, the type function should return `:date`. 200 201 Note this function is not required to return Ecto primitive 202 types, the type is only required to be known by the adapter. 203 """ 204 @callback type :: t 205 206 @doc """ 207 Casts the given input to the custom type. 208 209 This callback is called on external input and can return any type, 210 as long as the `dump/1` function is able to convert the returned 211 value into an Ecto native type. There are two situations where 212 this callback is called: 213 214 1. When casting values by `Ecto.Changeset` 215 2. When passing arguments to `Ecto.Query` 216 217 You can return `:error` if the given term cannot be cast. 218 A default error message of "is invalid" will be added to the 219 changeset. 220 221 You may also return `{:error, keyword()}` to customize the 222 changeset error message and its metadata. Passing a `:message` 223 key, will override the default message. It is not possible to 224 override the `:type` key. 225 226 For `{:array, CustomType}` or `{:map, CustomType}` the returned 227 keyword list will be erased and the default error will be shown. 228 """ 229 @callback cast(term) :: {:ok, term} | :error | {:error, keyword()} 230 231 @doc """ 232 Loads the given term into a custom type. 233 234 This callback is called when loading data from the database and 235 receives an Ecto native type. It can return any type, as long as 236 the `dump/1` function is able to convert the returned value back 237 into an Ecto native type. 238 """ 239 @callback load(term) :: {:ok, term} | :error 240 241 @doc """ 242 Dumps the given term into an Ecto native type. 243 244 This callback is called with any term that was stored in the struct 245 and it needs to validate them and convert it to an Ecto native type. 246 """ 247 @callback dump(term) :: {:ok, term} | :error 248 249 @doc """ 250 Checks if two terms are semantically equal. 251 """ 252 @callback equal?(term, term) :: boolean 253 254 @doc """ 255 Dictates how the type should be treated inside embeds. 256 257 By default, the type is sent as itself, without calling 258 dumping to keep the higher level representation. But 259 it can be set to `:dump` so that it is dumped before 260 being encoded. 261 """ 262 @callback embed_as(format :: atom) :: :self | :dump 263 264 @doc """ 265 Generates a loaded version of the data. 266 267 This is callback is invoked when a custom type is given 268 to `field` with the `:autogenerate` flag. 269 """ 270 @callback autogenerate() :: term() 271 272 @optional_callbacks autogenerate: 0 273 274 ## Functions 275 276 @doc """ 277 Checks if we have a primitive type. 278 279 iex> primitive?(:string) 280 true 281 iex> primitive?(Another) 282 false 283 284 iex> primitive?({:array, :string}) 285 true 286 iex> primitive?({:array, Another}) 287 true 288 289 """ 290 @spec primitive?(t) :: boolean 291 def primitive?({:parameterized, _, _}), do: true 292 def primitive?({composite, _}) when composite in @composite, do: true 293 def primitive?(base) when base in @base, do: true 294 def primitive?(_), do: false 295 296 @doc """ 297 Checks if the given atom can be used as composite type. 298 299 iex> composite?(:array) 300 true 301 iex> composite?(:string) 302 false 303 304 """ 305 @spec composite?(atom) :: boolean 306 def composite?(atom), do: atom in @composite 307 308 @doc """ 309 Checks if the given atom can be used as base type. 310 311 iex> base?(:string) 312 true 313 iex> base?(:array) 314 false 315 iex> base?(Custom) 316 false 317 318 """ 319 @spec base?(atom) :: boolean 320 def base?(atom), do: atom in @base 321 322 @doc """ 323 Gets how the type is treated inside embeds for the given format. 324 325 See `c:embed_as/1`. 326 """ 327 def embed_as({:parameterized, module, params}, format), do: module.embed_as(format, params) 328 def embed_as({composite, type}, format) when composite in @composite, do: embed_as(type, format) 329 def embed_as(base, _format) when base in @base, do: :self 330 def embed_as(mod, format), do: mod.embed_as(format) 331 332 @doc """ 333 Dumps the `value` for `type` considering it will be embedded in `format`. 334 335 ## Examples 336 337 iex> Ecto.Type.embedded_dump(:decimal, Decimal.new("1"), :json) 338 {:ok, Decimal.new("1")} 339 340 """ 341 def embedded_dump(type, value, format) do 342 case embed_as(type, format) do 343 :self -> {:ok, value} 344 :dump -> dump(type, value, &embedded_dump(&1, &2, format)) 345 end 346 end 347 348 @doc """ 349 Loads the `value` for `type` considering it was embedded in `format`. 350 351 ## Examples 352 353 iex> Ecto.Type.embedded_load(:decimal, "1", :json) 354 {:ok, Decimal.new("1")} 355 356 """ 357 def embedded_load(type, value, format) do 358 case embed_as(type, format) do 359 :self -> 360 case cast(type, value) do 361 {:ok, _} = ok -> ok 362 _ -> :error 363 end 364 365 :dump -> 366 load(type, value, &embedded_load(&1, &2, format)) 367 end 368 end 369 370 @doc """ 371 Retrieves the underlying schema type for the given, possibly custom, type. 372 373 iex> type(:string) 374 :string 375 iex> type(Ecto.UUID) 376 :uuid 377 378 iex> type({:array, :string}) 379 {:array, :string} 380 iex> type({:array, Ecto.UUID}) 381 {:array, :uuid} 382 383 iex> type({:map, Ecto.UUID}) 384 {:map, :uuid} 385 386 """ 387 @spec type(t) :: t 388 def type(type) 389 def type({:parameterized, type, params}), do: type.type(params) 390 def type({:array, type}), do: {:array, type(type)} 391 def type({:map, type}), do: {:map, type(type)} 392 def type(type) when type in @base, do: type 393 def type(type) when is_atom(type), do: type.type() 394 def type(type), do: type 395 396 @doc """ 397 Checks if a given type matches with a primitive type 398 that can be found in queries. 399 400 iex> match?(:string, :any) 401 true 402 iex> match?(:any, :string) 403 true 404 iex> match?(:string, :string) 405 true 406 407 iex> match?({:array, :string}, {:array, :any}) 408 true 409 410 iex> match?(Ecto.UUID, :uuid) 411 true 412 iex> match?(Ecto.UUID, :string) 413 false 414 415 """ 416 @spec match?(t, primitive) :: boolean 417 def match?(schema_type, query_type) do 418 if primitive?(schema_type) do 419 do_match?(schema_type, query_type) 420 else 421 do_match?(schema_type.type, query_type) 422 end 423 end 424 425 defp do_match?(_left, :any), do: true 426 defp do_match?(:any, _right), do: true 427 defp do_match?({outer, left}, {outer, right}), do: match?(left, right) 428 defp do_match?(:decimal, type) when type in [:float, :integer], do: true 429 defp do_match?(:binary_id, :binary), do: true 430 defp do_match?(:id, :integer), do: true 431 defp do_match?(type, type), do: true 432 defp do_match?(:naive_datetime, {:param, :any_datetime}), do: true 433 defp do_match?(:naive_datetime_usec, {:param, :any_datetime}), do: true 434 defp do_match?(:utc_datetime, {:param, :any_datetime}), do: true 435 defp do_match?(:utc_datetime_usec, {:param, :any_datetime}), do: true 436 defp do_match?(_, _), do: false 437 438 @doc """ 439 Dumps a value to the given type. 440 441 Opposite to casting, dumping requires the returned value 442 to be a valid Ecto type, as it will be sent to the 443 underlying data store. 444 445 iex> dump(:string, nil) 446 {:ok, nil} 447 iex> dump(:string, "foo") 448 {:ok, "foo"} 449 450 iex> dump(:integer, 1) 451 {:ok, 1} 452 iex> dump(:integer, "10") 453 :error 454 455 iex> dump(:binary, "foo") 456 {:ok, "foo"} 457 iex> dump(:binary, 1) 458 :error 459 460 iex> dump({:array, :integer}, [1, 2, 3]) 461 {:ok, [1, 2, 3]} 462 iex> dump({:array, :integer}, [1, "2", 3]) 463 :error 464 iex> dump({:array, :binary}, ["1", "2", "3"]) 465 {:ok, ["1", "2", "3"]} 466 467 """ 468 @spec dump(t, term) :: {:ok, term} | :error 469 @spec dump(t, term, (t, term -> {:ok, term} | :error)) :: {:ok, term} | :error 470 def dump(type, value, dumper \\ &dump/2) 471 472 def dump({:parameterized, module, params}, value, dumper) do 473 module.dump(value, dumper, params) 474 end 475 476 def dump(_type, nil, _dumper) do 477 {:ok, nil} 478 end 479 480 def dump({:maybe, type}, value, dumper) do 481 case dump(type, value, dumper) do 482 {:ok, _} = ok -> ok 483 :error -> {:ok, value} 484 end 485 end 486 487 def dump({:in, type}, value, dumper) do 488 case dump({:array, type}, value, dumper) do 489 {:ok, value} -> {:ok, {:in, value}} 490 :error -> :error 491 end 492 end 493 494 def dump({:array, {_, _, _} = type}, value, dumper), do: array(value, type, dumper, false, []) 495 def dump({:array, type}, value, dumper), do: array(value, type, dumper, true, []) 496 def dump({:map, type}, value, dumper), do: map(value, type, dumper, false, %{}) 497 498 def dump(:any, value, _dumper), do: {:ok, value} 499 def dump(:integer, value, _dumper), do: same_integer(value) 500 def dump(:float, value, _dumper), do: dump_float(value) 501 def dump(:boolean, value, _dumper), do: same_boolean(value) 502 def dump(:map, value, _dumper), do: same_map(value) 503 def dump(:string, value, _dumper), do: same_binary(value) 504 def dump(:binary, value, _dumper), do: same_binary(value) 505 def dump(:id, value, _dumper), do: same_integer(value) 506 def dump(:binary_id, value, _dumper), do: same_binary(value) 507 def dump(:decimal, value, _dumper), do: same_decimal(value) 508 def dump(:date, value, _dumper), do: same_date(value) 509 def dump(:time, value, _dumper), do: dump_time(value) 510 def dump(:time_usec, value, _dumper), do: dump_time_usec(value) 511 def dump(:naive_datetime, value, _dumper), do: dump_naive_datetime(value) 512 def dump(:naive_datetime_usec, value, _dumper), do: dump_naive_datetime_usec(value) 513 def dump(:utc_datetime, value, _dumper), do: dump_utc_datetime(value) 514 def dump(:utc_datetime_usec, value, _dumper), do: dump_utc_datetime_usec(value) 515 def dump({:param, :any_datetime}, value, _dumper), do: dump_any_datetime(value) 516 def dump(mod, value, _dumper) when is_atom(mod), do: mod.dump(value) 517 518 defp dump_float(term) when is_float(term), do: {:ok, term} 519 defp dump_float(_), do: :error 520 521 defp dump_time(%Time{} = term), do: {:ok, check_no_usec!(term, :time)} 522 defp dump_time(_), do: :error 523 524 defp dump_time_usec(%Time{} = term), do: {:ok, check_usec!(term, :time_usec)} 525 defp dump_time_usec(_), do: :error 526 527 defp dump_any_datetime(%NaiveDateTime{} = term), do: {:ok, term} 528 defp dump_any_datetime(%DateTime{} = term), do: {:ok, term} 529 defp dump_any_datetime(_), do: :error 530 531 defp dump_naive_datetime(%NaiveDateTime{} = term), do: 532 {:ok, check_no_usec!(term, :naive_datetime)} 533 534 defp dump_naive_datetime(_), do: :error 535 536 defp dump_naive_datetime_usec(%NaiveDateTime{} = term), 537 do: {:ok, check_usec!(term, :naive_datetime_usec)} 538 539 defp dump_naive_datetime_usec(_), do: :error 540 541 defp dump_utc_datetime(%DateTime{} = datetime) do 542 kind = :utc_datetime 543 {:ok, datetime |> check_utc_timezone!(kind) |> check_no_usec!(kind)} 544 end 545 546 defp dump_utc_datetime(_), do: :error 547 548 defp dump_utc_datetime_usec(%DateTime{} = datetime) do 549 kind = :utc_datetime_usec 550 {:ok, datetime |> check_utc_timezone!(kind) |> check_usec!(kind)} 551 end 552 553 defp dump_utc_datetime_usec(_), do: :error 554 555 @doc """ 556 Loads a value with the given type. 557 558 iex> load(:string, nil) 559 {:ok, nil} 560 iex> load(:string, "foo") 561 {:ok, "foo"} 562 563 iex> load(:integer, 1) 564 {:ok, 1} 565 iex> load(:integer, "10") 566 :error 567 568 """ 569 @spec load(t, term) :: {:ok, term} | :error 570 @spec load(t, term, (t, term -> {:ok, term} | :error)) :: {:ok, term} | :error 571 def load(type, value, loader \\ &load/2) 572 573 def load({:parameterized, module, params}, value, loader) do 574 module.load(value, loader, params) 575 end 576 577 def load(_type, nil, _loader) do 578 {:ok, nil} 579 end 580 581 def load({:maybe, type}, value, loader) do 582 case load(type, value, loader) do 583 {:ok, _} = ok -> ok 584 :error -> {:ok, value} 585 end 586 end 587 588 def load({:array, {_, _, _} = type}, value, loader), do: array(value, type, loader, false, []) 589 def load({:array, type}, value, loader), do: array(value, type, loader, true, []) 590 def load({:map, type}, value, loader), do: map(value, type, loader, false, %{}) 591 592 def load(:any, value, _loader), do: {:ok, value} 593 def load(:integer, value, _loader), do: same_integer(value) 594 def load(:float, value, _loader), do: load_float(value) 595 def load(:boolean, value, _loader), do: same_boolean(value) 596 def load(:map, value, _loader), do: same_map(value) 597 def load(:string, value, _loader), do: same_binary(value) 598 def load(:binary, value, _loader), do: same_binary(value) 599 def load(:id, value, _loader), do: same_integer(value) 600 def load(:binary_id, value, _loader), do: same_binary(value) 601 def load(:decimal, value, _loader), do: same_decimal(value) 602 def load(:date, value, _loader), do: same_date(value) 603 def load(:time, value, _loader), do: load_time(value) 604 def load(:time_usec, value, _loader), do: load_time_usec(value) 605 def load(:naive_datetime, value, _loader), do: load_naive_datetime(value) 606 def load(:naive_datetime_usec, value, _loader), do: load_naive_datetime_usec(value) 607 def load(:utc_datetime, value, _loader), do: load_utc_datetime(value) 608 def load(:utc_datetime_usec, value, _loader), do: load_utc_datetime_usec(value) 609 def load(mod, value, _loader), do: mod.load(value) 610 611 defp load_float(term) when is_float(term), do: {:ok, term} 612 defp load_float(term) when is_integer(term), do: {:ok, :erlang.float(term)} 613 defp load_float(_), do: :error 614 615 defp load_time(%Time{} = time), do: {:ok, truncate_usec(time)} 616 defp load_time(_), do: :error 617 618 defp load_time_usec(%Time{} = time), do: {:ok, pad_usec(time)} 619 defp load_time_usec(_), do: :error 620 621 # This is a downcast, which is always fine, and in case 622 # we try to send a naive datetime where a datetime is expected, 623 # the adapter will either explicitly error (Postgres) or it will 624 # accept the data (MySQL), which is fine as we always assume UTC 625 defp load_naive_datetime(%DateTime{} = datetime), 626 do: {:ok, datetime |> check_utc_timezone!(:naive_datetime) |> DateTime.to_naive() |> truncate_usec()} 627 628 defp load_naive_datetime(%NaiveDateTime{} = naive_datetime), 629 do: {:ok, truncate_usec(naive_datetime)} 630 631 defp load_naive_datetime(_), do: :error 632 633 defp load_naive_datetime_usec(%DateTime{} = datetime), 634 do: {:ok, datetime |> check_utc_timezone!(:naive_datetime_usec) |> DateTime.to_naive() |> pad_usec()} 635 636 defp load_naive_datetime_usec(%NaiveDateTime{} = naive_datetime), 637 do: {:ok, pad_usec(naive_datetime)} 638 639 defp load_naive_datetime_usec(_), do: :error 640 641 # This is an upcast but because we assume the database 642 # is always in UTC, we can perform it. 643 defp load_utc_datetime(%NaiveDateTime{} = naive_datetime), 644 do: {:ok, naive_datetime |> truncate_usec() |> DateTime.from_naive!("Etc/UTC")} 645 646 defp load_utc_datetime(%DateTime{} = datetime), 647 do: {:ok, datetime |> check_utc_timezone!(:utc_datetime) |> truncate_usec()} 648 649 defp load_utc_datetime(_), 650 do: :error 651 652 defp load_utc_datetime_usec(%NaiveDateTime{} = naive_datetime), 653 do: {:ok, naive_datetime |> pad_usec() |> DateTime.from_naive!("Etc/UTC")} 654 655 defp load_utc_datetime_usec(%DateTime{} = datetime), 656 do: {:ok, datetime |> check_utc_timezone!(:utc_datetime_usec) |> pad_usec()} 657 658 defp load_utc_datetime_usec(_), 659 do: :error 660 661 @doc """ 662 Casts a value to the given type. 663 664 `cast/2` is used by the finder queries and changesets to cast outside values to 665 specific types. 666 667 Note that nil can be cast to all primitive types as data stores allow nil to be 668 set on any column. 669 670 NaN and infinite decimals are not supported, use custom types instead. 671 672 iex> cast(:any, "whatever") 673 {:ok, "whatever"} 674 675 iex> cast(:any, nil) 676 {:ok, nil} 677 iex> cast(:string, nil) 678 {:ok, nil} 679 680 iex> cast(:integer, 1) 681 {:ok, 1} 682 iex> cast(:integer, "1") 683 {:ok, 1} 684 iex> cast(:integer, "1.0") 685 :error 686 687 iex> cast(:id, 1) 688 {:ok, 1} 689 iex> cast(:id, "1") 690 {:ok, 1} 691 iex> cast(:id, "1.0") 692 :error 693 694 iex> cast(:float, 1.0) 695 {:ok, 1.0} 696 iex> cast(:float, 1) 697 {:ok, 1.0} 698 iex> cast(:float, "1") 699 {:ok, 1.0} 700 iex> cast(:float, "1.0") 701 {:ok, 1.0} 702 iex> cast(:float, "1-foo") 703 :error 704 705 iex> cast(:boolean, true) 706 {:ok, true} 707 iex> cast(:boolean, false) 708 {:ok, false} 709 iex> cast(:boolean, "1") 710 {:ok, true} 711 iex> cast(:boolean, "0") 712 {:ok, false} 713 iex> cast(:boolean, "whatever") 714 :error 715 716 iex> cast(:string, "beef") 717 {:ok, "beef"} 718 iex> cast(:binary, "beef") 719 {:ok, "beef"} 720 721 iex> cast(:decimal, Decimal.new("1.0")) 722 {:ok, Decimal.new("1.0")} 723 iex> cast(:decimal, "1.0bad") 724 :error 725 726 iex> cast({:array, :integer}, [1, 2, 3]) 727 {:ok, [1, 2, 3]} 728 iex> cast({:array, :integer}, ["1", "2", "3"]) 729 {:ok, [1, 2, 3]} 730 iex> cast({:array, :string}, [1, 2, 3]) 731 :error 732 iex> cast(:string, [1, 2, 3]) 733 :error 734 735 """ 736 @spec cast(t, term) :: {:ok, term} | {:error, keyword()} | :error 737 def cast({:parameterized, type, params}, value), do: type.cast(value, params) 738 def cast({:in, _type}, nil), do: :error 739 def cast(_type, nil), do: {:ok, nil} 740 741 def cast({:maybe, type}, value) do 742 case cast(type, value) do 743 {:ok, _} = ok -> ok 744 _ -> {:ok, value} 745 end 746 end 747 748 def cast(type, value) do 749 cast_fun(type).(value) 750 end 751 752 defp cast_fun(:integer), do: &cast_integer/1 753 defp cast_fun(:float), do: &cast_float/1 754 defp cast_fun(:boolean), do: &cast_boolean/1 755 defp cast_fun(:map), do: &cast_map/1 756 defp cast_fun(:string), do: &cast_binary/1 757 defp cast_fun(:binary), do: &cast_binary/1 758 defp cast_fun(:id), do: &cast_integer/1 759 defp cast_fun(:binary_id), do: &cast_binary/1 760 defp cast_fun(:any), do: &{:ok, &1} 761 defp cast_fun(:decimal), do: &cast_decimal/1 762 defp cast_fun(:date), do: &cast_date/1 763 defp cast_fun(:time), do: &maybe_truncate_usec(cast_time(&1)) 764 defp cast_fun(:time_usec), do: &maybe_pad_usec(cast_time(&1)) 765 defp cast_fun(:naive_datetime), do: &maybe_truncate_usec(cast_naive_datetime(&1)) 766 defp cast_fun(:naive_datetime_usec), do: &maybe_pad_usec(cast_naive_datetime(&1)) 767 defp cast_fun(:utc_datetime), do: &maybe_truncate_usec(cast_utc_datetime(&1)) 768 defp cast_fun(:utc_datetime_usec), do: &maybe_pad_usec(cast_utc_datetime(&1)) 769 defp cast_fun({:param, :any_datetime}), do: &cast_any_datetime(&1) 770 defp cast_fun({:parameterized, mod, params}), do: &mod.cast(&1, params) 771 defp cast_fun({:in, type}), do: cast_fun({:array, type}) 772 773 defp cast_fun({:array, {:parameterized, _, _} = type}) do 774 fun = cast_fun(type) 775 &array(&1, fun, false, []) 776 end 777 778 defp cast_fun({:array, type}) do 779 fun = cast_fun(type) 780 &array(&1, fun, true, []) 781 end 782 783 defp cast_fun({:map, {:parameterized, _, _} = type}) do 784 fun = cast_fun(type) 785 &map(&1, fun, false, %{}) 786 end 787 788 defp cast_fun({:map, type}) do 789 fun = cast_fun(type) 790 &map(&1, fun, true, %{}) 791 end 792 793 defp cast_fun(mod) when is_atom(mod) do 794 fn 795 nil -> {:ok, nil} 796 value -> mod.cast(value) 797 end 798 end 799 800 defp cast_integer(term) when is_binary(term) do 801 case Integer.parse(term) do 802 {integer, ""} -> {:ok, integer} 803 _ -> :error 804 end 805 end 806 807 defp cast_integer(term) when is_integer(term), do: {:ok, term} 808 defp cast_integer(_), do: :error 809 810 defp cast_float(term) when is_binary(term) do 811 case Float.parse(term) do 812 {float, ""} -> {:ok, float} 813 _ -> :error 814 end 815 end 816 817 defp cast_float(term) when is_float(term), do: {:ok, term} 818 defp cast_float(term) when is_integer(term), do: {:ok, :erlang.float(term)} 819 defp cast_float(_), do: :error 820 821 defp cast_decimal(term) when is_binary(term) do 822 case Decimal.parse(term) do 823 {:ok, decimal} -> check_decimal(decimal, false) 824 # The following two clauses exist to support earlier versions of Decimal. 825 {decimal, ""} -> check_decimal(decimal, false) 826 {_, remainder} when is_binary(remainder) and byte_size(remainder) > 0 -> :error 827 :error -> :error 828 end 829 end 830 defp cast_decimal(term), do: same_decimal(term) 831 832 defp cast_boolean(term) when term in ~w(true 1), do: {:ok, true} 833 defp cast_boolean(term) when term in ~w(false 0), do: {:ok, false} 834 defp cast_boolean(term) when is_boolean(term), do: {:ok, term} 835 defp cast_boolean(_), do: :error 836 837 defp cast_binary(term) when is_binary(term), do: {:ok, term} 838 defp cast_binary(_), do: :error 839 840 defp cast_map(term) when is_map(term), do: {:ok, term} 841 defp cast_map(_), do: :error 842 843 ## Shared helpers 844 845 @compile {:inline, same_integer: 1, same_boolean: 1, same_map: 1, same_decimal: 1, same_date: 1} 846 defp same_integer(term) when is_integer(term), do: {:ok, term} 847 defp same_integer(_), do: :error 848 849 defp same_boolean(term) when is_boolean(term), do: {:ok, term} 850 defp same_boolean(_), do: :error 851 852 defp same_binary(term) when is_binary(term), do: {:ok, term} 853 defp same_binary(_), do: :error 854 855 defp same_map(term) when is_map(term), do: {:ok, term} 856 defp same_map(_), do: :error 857 858 defp same_decimal(term) when is_integer(term), do: {:ok, Decimal.new(term)} 859 defp same_decimal(term) when is_float(term), do: {:ok, Decimal.from_float(term)} 860 defp same_decimal(%Decimal{} = term), do: check_decimal(term, true) 861 defp same_decimal(_), do: :error 862 863 defp same_date(%Date{} = term), do: {:ok, term} 864 defp same_date(_), do: :error 865 866 @doc false 867 @spec filter_empty_values(t, any, [any]) :: {:ok, any} | :empty 868 def filter_empty_values({:array, type}, value, empty_values) when is_list(value) do 869 value = 870 for elem <- value, 871 {:ok, elem} <- [filter_empty_values(type, elem, empty_values)], 872 do: elem 873 874 if value in empty_values do 875 :empty 876 else 877 {:ok, value} 878 end 879 end 880 881 def filter_empty_values(_type, value, empty_values) do 882 if value in empty_values do 883 :empty 884 else 885 {:ok, value} 886 end 887 end 888 889 ## Adapter related 890 891 @doc false 892 def adapter_autogenerate(adapter, type) do 893 type 894 |> type() 895 |> adapter.autogenerate() 896 end 897 898 @doc false 899 def adapter_load(adapter, {:parameterized, module, params} = type, value) do 900 process_loaders(adapter.loaders(module.type(params), type), {:ok, value}, adapter) 901 end 902 def adapter_load(_adapter, _type, nil) do 903 {:ok, nil} 904 end 905 def adapter_load(adapter, type, value) do 906 if of_base_type?(type, value) do 907 {:ok, value} 908 else 909 process_loaders(adapter.loaders(type(type), type), {:ok, value}, adapter) 910 end 911 end 912 913 defp process_loaders(_, :error, _adapter), 914 do: :error 915 defp process_loaders([fun|t], {:ok, value}, adapter) when is_function(fun), 916 do: process_loaders(t, fun.(value), adapter) 917 defp process_loaders([type|t], {:ok, value}, adapter), 918 do: process_loaders(t, load(type, value, &adapter_load(adapter, &1, &2)), adapter) 919 defp process_loaders([], {:ok, _} = acc, _adapter), 920 do: acc 921 922 @doc false 923 def adapter_dump(adapter, {:parameterized, module, params} = type, value) do 924 process_dumpers(adapter.dumpers(module.type(params), type), {:ok, value}, adapter) 925 end 926 def adapter_dump(_adapter, type, nil) do 927 dump(type, nil) 928 end 929 def adapter_dump(adapter, type, value) do 930 process_dumpers(adapter.dumpers(type(type), type), {:ok, value}, adapter) 931 end 932 933 defp process_dumpers(_, :error, _adapter), 934 do: :error 935 defp process_dumpers([fun|t], {:ok, value}, adapter) when is_function(fun), 936 do: process_dumpers(t, fun.(value), adapter) 937 defp process_dumpers([type|t], {:ok, value}, adapter), 938 do: process_dumpers(t, dump(type, value, &adapter_dump(adapter, &1, &2)), adapter) 939 defp process_dumpers([], {:ok, _} = acc, _adapter), 940 do: acc 941 942 ## Date 943 944 defp cast_date(binary) when is_binary(binary) do 945 case Date.from_iso8601(binary) do 946 {:ok, _} = ok -> 947 ok 948 {:error, _} -> 949 case NaiveDateTime.from_iso8601(binary) do 950 {:ok, naive_datetime} -> {:ok, NaiveDateTime.to_date(naive_datetime)} 951 {:error, _} -> :error 952 end 953 end 954 end 955 defp cast_date(%{"year" => empty, "month" => empty, "day" => empty}) when empty in ["", nil], 956 do: {:ok, nil} 957 defp cast_date(%{year: empty, month: empty, day: empty}) when empty in ["", nil], 958 do: {:ok, nil} 959 defp cast_date(%{"year" => year, "month" => month, "day" => day}), 960 do: cast_date(to_i(year), to_i(month), to_i(day)) 961 defp cast_date(%{year: year, month: month, day: day}), 962 do: cast_date(to_i(year), to_i(month), to_i(day)) 963 defp cast_date(_), 964 do: :error 965 966 defp cast_date(year, month, day) when is_integer(year) and is_integer(month) and is_integer(day) do 967 case Date.new(year, month, day) do 968 {:ok, _} = ok -> ok 969 {:error, _} -> :error 970 end 971 end 972 defp cast_date(_, _, _), 973 do: :error 974 975 ## Time 976 977 defp cast_time(<<hour::2-bytes, ?:, minute::2-bytes>>), 978 do: cast_time(to_i(hour), to_i(minute), 0, nil) 979 defp cast_time(binary) when is_binary(binary) do 980 case Time.from_iso8601(binary) do 981 {:ok, _} = ok -> ok 982 {:error, _} -> :error 983 end 984 end 985 defp cast_time(%{"hour" => empty, "minute" => empty}) when empty in ["", nil], 986 do: {:ok, nil} 987 defp cast_time(%{hour: empty, minute: empty}) when empty in ["", nil], 988 do: {:ok, nil} 989 defp cast_time(%{"hour" => hour, "minute" => minute} = map), 990 do: cast_time(to_i(hour), to_i(minute), to_i(Map.get(map, "second")), to_i(Map.get(map, "microsecond"))) 991 defp cast_time(%{hour: hour, minute: minute, second: second, microsecond: {microsecond, precision}}), 992 do: cast_time(to_i(hour), to_i(minute), to_i(second), {to_i(microsecond), to_i(precision)}) 993 defp cast_time(%{hour: hour, minute: minute} = map), 994 do: cast_time(to_i(hour), to_i(minute), to_i(Map.get(map, :second)), to_i(Map.get(map, :microsecond))) 995 defp cast_time(_), 996 do: :error 997 998 defp cast_time(hour, minute, sec, usec) when is_integer(usec) do 999 cast_time(hour, minute, sec, {usec, 6}) 1000 end 1001 defp cast_time(hour, minute, sec, nil) do 1002 cast_time(hour, minute, sec, {0, 0}) 1003 end 1004 defp cast_time(hour, minute, sec, {usec, precision}) 1005 when is_integer(hour) and is_integer(minute) and 1006 (is_integer(sec) or is_nil(sec)) and is_integer(usec) and is_integer(precision) do 1007 case Time.new(hour, minute, sec || 0, {usec, precision}) do 1008 {:ok, _} = ok -> ok 1009 {:error, _} -> :error 1010 end 1011 end 1012 defp cast_time(_, _, _, _) do 1013 :error 1014 end 1015 1016 defp cast_any_datetime(%DateTime{} = datetime), do: cast_utc_datetime(datetime) 1017 defp cast_any_datetime(other), do: cast_naive_datetime(other) 1018 1019 ## Naive datetime 1020 1021 defp cast_naive_datetime("-" <> rest) do 1022 with {:ok, naive_datetime} <- cast_naive_datetime(rest) do 1023 {:ok, %{naive_datetime | year: naive_datetime.year * -1}} 1024 end 1025 end 1026 1027 defp cast_naive_datetime(<<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes, sep, hour::2-bytes, ?:, minute::2-bytes>>) 1028 when sep in [?\s, ?T] do 1029 case NaiveDateTime.new(to_i(year), to_i(month), to_i(day), to_i(hour), to_i(minute), 0) do 1030 {:ok, _} = ok -> ok 1031 _ -> :error 1032 end 1033 end 1034 1035 defp cast_naive_datetime(binary) when is_binary(binary) do 1036 case NaiveDateTime.from_iso8601(binary) do 1037 {:ok, _} = ok -> ok 1038 {:error, _} -> :error 1039 end 1040 end 1041 1042 defp cast_naive_datetime(%{"year" => empty, "month" => empty, "day" => empty, 1043 "hour" => empty, "minute" => empty}) when empty in ["", nil], 1044 do: {:ok, nil} 1045 1046 defp cast_naive_datetime(%{year: empty, month: empty, day: empty, 1047 hour: empty, minute: empty}) when empty in ["", nil], 1048 do: {:ok, nil} 1049 1050 defp cast_naive_datetime(%{} = map) do 1051 with {:ok, %Date{} = date} <- cast_date(map), 1052 {:ok, %Time{} = time} <- cast_time(map) do 1053 NaiveDateTime.new(date, time) 1054 else 1055 _ -> :error 1056 end 1057 end 1058 1059 defp cast_naive_datetime(_) do 1060 :error 1061 end 1062 1063 ## UTC datetime 1064 1065 defp cast_utc_datetime("-" <> rest) do 1066 with {:ok, utc_datetime} <- cast_utc_datetime(rest) do 1067 {:ok, %{utc_datetime | year: utc_datetime.year * -1}} 1068 end 1069 end 1070 1071 defp cast_utc_datetime(<<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes, sep, hour::2-bytes, ?:, minute::2-bytes>>) 1072 when sep in [?\s, ?T] do 1073 case NaiveDateTime.new(to_i(year), to_i(month), to_i(day), to_i(hour), to_i(minute), 0) do 1074 {:ok, naive_datetime} -> {:ok, DateTime.from_naive!(naive_datetime, "Etc/UTC")} 1075 _ -> :error 1076 end 1077 end 1078 1079 defp cast_utc_datetime(binary) when is_binary(binary) do 1080 case DateTime.from_iso8601(binary) do 1081 {:ok, datetime, _offset} -> {:ok, datetime} 1082 {:error, :missing_offset} -> 1083 case NaiveDateTime.from_iso8601(binary) do 1084 {:ok, naive_datetime} -> {:ok, DateTime.from_naive!(naive_datetime, "Etc/UTC")} 1085 {:error, _} -> :error 1086 end 1087 {:error, _} -> :error 1088 end 1089 end 1090 defp cast_utc_datetime(%DateTime{time_zone: "Etc/UTC"} = datetime), do: {:ok, datetime} 1091 defp cast_utc_datetime(%DateTime{} = datetime) do 1092 case (datetime |> DateTime.to_unix(:microsecond) |> DateTime.from_unix(:microsecond)) do 1093 {:ok, _} = ok -> ok 1094 {:error, _} -> :error 1095 end 1096 end 1097 defp cast_utc_datetime(value) do 1098 case cast_naive_datetime(value) do 1099 {:ok, %NaiveDateTime{} = naive_datetime} -> 1100 {:ok, DateTime.from_naive!(naive_datetime, "Etc/UTC")} 1101 {:ok, _} = ok -> 1102 ok 1103 :error -> 1104 :error 1105 end 1106 end 1107 1108 @doc """ 1109 Checks if two terms are equal. 1110 1111 Depending on the given `type` performs a structural or semantical comparison. 1112 1113 ## Examples 1114 1115 iex> equal?(:integer, 1, 1) 1116 true 1117 iex> equal?(:decimal, Decimal.new("1"), Decimal.new("1.00")) 1118 true 1119 1120 """ 1121 @spec equal?(t, term, term) :: boolean 1122 def equal?(_, nil, nil), do: true 1123 1124 def equal?(type, term1, term2) do 1125 if fun = equal_fun(type) do 1126 fun.(term1, term2) 1127 else 1128 term1 == term2 1129 end 1130 end 1131 1132 @doc """ 1133 Checks if `collection` includes a `term`. 1134 1135 Depending on the given `type` performs a structural or semantical comparison. 1136 1137 ## Examples 1138 1139 iex> include?(:integer, 1, 1..3) 1140 true 1141 iex> include?(:decimal, Decimal.new("1"), [Decimal.new("1.00"), Decimal.new("2.00")]) 1142 true 1143 1144 """ 1145 @spec include?(t, term, Enum.t()) :: boolean 1146 def include?(type, term, collection) do 1147 if fun = equal_fun(type) do 1148 Enum.any?(collection, &fun.(term, &1)) 1149 else 1150 term in collection 1151 end 1152 end 1153 1154 defp equal_fun(:decimal), do: &equal_decimal?/2 1155 defp equal_fun(t) when t in [:time, :time_usec], do: &equal_time?/2 1156 defp equal_fun(t) when t in [:utc_datetime, :utc_datetime_usec], do: &equal_utc_datetime?/2 1157 defp equal_fun(t) when t in [:naive_datetime, :naive_datetime_usec], do: &equal_naive_datetime?/2 1158 defp equal_fun(t) when t in @base, do: nil 1159 1160 defp equal_fun({:array, type}) do 1161 if fun = equal_fun(type) do 1162 &equal_list?(fun, &1, &2) 1163 end 1164 end 1165 1166 defp equal_fun({:map, type}) do 1167 if fun = equal_fun(type) do 1168 &equal_map?(fun, &1, &2) 1169 end 1170 end 1171 1172 defp equal_fun({:parameterized, mod, params}) do 1173 &mod.equal?(&1, &2, params) 1174 end 1175 1176 defp equal_fun(mod) when is_atom(mod), do: &mod.equal?/2 1177 1178 defp equal_decimal?(%Decimal{} = a, %Decimal{} = b), do: Decimal.equal?(a, b) 1179 defp equal_decimal?(_, _), do: false 1180 1181 defp equal_time?(%Time{} = a, %Time{} = b), do: Time.compare(a, b) == :eq 1182 defp equal_time?(_, _), do: false 1183 1184 defp equal_utc_datetime?(%DateTime{} = a, %DateTime{} = b), do: DateTime.compare(a, b) == :eq 1185 defp equal_utc_datetime?(_, _), do: false 1186 1187 defp equal_naive_datetime?(%NaiveDateTime{} = a, %NaiveDateTime{} = b), 1188 do: NaiveDateTime.compare(a, b) == :eq 1189 defp equal_naive_datetime?(_, _), 1190 do: false 1191 1192 defp equal_list?(fun, [nil | xs], [nil | ys]), do: equal_list?(fun, xs, ys) 1193 defp equal_list?(fun, [x | xs], [y | ys]), do: fun.(x, y) and equal_list?(fun, xs, ys) 1194 defp equal_list?(_fun, [], []), do: true 1195 defp equal_list?(_fun, _, _), do: false 1196 1197 defp equal_map?(_fun, map1, map2) when map_size(map1) != map_size(map2) do 1198 false 1199 end 1200 1201 defp equal_map?(fun, %{} = map1, %{} = map2) do 1202 equal_map?(fun, Map.to_list(map1), map2) 1203 end 1204 1205 defp equal_map?(fun, [{key, nil} | tail], other_map) do 1206 case other_map do 1207 %{^key => nil} -> equal_map?(fun, tail, other_map) 1208 _ -> false 1209 end 1210 end 1211 1212 defp equal_map?(fun, [{key, val} | tail], other_map) do 1213 case other_map do 1214 %{^key => other_val} -> fun.(val, other_val) and equal_map?(fun, tail, other_map) 1215 _ -> false 1216 end 1217 end 1218 1219 defp equal_map?(_fun, [], _) do 1220 true 1221 end 1222 1223 defp equal_map?(_fun, _, _) do 1224 false 1225 end 1226 1227 ## Helpers 1228 1229 # Checks if a value is of the given primitive type. 1230 defp of_base_type?(:any, _), do: true 1231 defp of_base_type?(:id, term), do: is_integer(term) 1232 defp of_base_type?(:float, term), do: is_float(term) 1233 defp of_base_type?(:integer, term), do: is_integer(term) 1234 defp of_base_type?(:boolean, term), do: is_boolean(term) 1235 defp of_base_type?(:binary, term), do: is_binary(term) 1236 defp of_base_type?(:string, term), do: is_binary(term) 1237 defp of_base_type?(:map, term), do: is_map(term) and not Map.has_key?(term, :__struct__) 1238 defp of_base_type?(:decimal, value), do: Kernel.match?(%Decimal{}, value) 1239 defp of_base_type?(:date, value), do: Kernel.match?(%Date{}, value) 1240 defp of_base_type?(_, _), do: false 1241 1242 defp array([nil | t], fun, true, acc) do 1243 array(t, fun, true, [nil | acc]) 1244 end 1245 1246 defp array([h | t], fun, skip_nil?, acc) do 1247 case fun.(h) do 1248 {:ok, h} -> array(t, fun, skip_nil?, [h | acc]) 1249 :error -> :error 1250 {:error, _custom_errors} -> :error 1251 end 1252 end 1253 1254 defp array([], _fun, _skip_nil?,acc) do 1255 {:ok, Enum.reverse(acc)} 1256 end 1257 1258 defp array(_, _, _, _) do 1259 :error 1260 end 1261 1262 defp map(map, fun, skip_nil?, acc) when is_map(map) do 1263 map_each(Map.to_list(map), fun, skip_nil?, acc) 1264 end 1265 1266 defp map(_, _, _, _) do 1267 :error 1268 end 1269 1270 defp map_each([{key, nil} | t], fun, true, acc) do 1271 map_each(t, fun, true, Map.put(acc, key, nil)) 1272 end 1273 1274 defp map_each([{key, value} | t], fun, skip_nil?, acc) do 1275 case fun.(value) do 1276 {:ok, value} -> map_each(t, fun, skip_nil?, Map.put(acc, key, value)) 1277 :error -> :error 1278 {:error, _custom_errors} -> :error 1279 end 1280 end 1281 1282 defp map_each([], _fun, _skip_nil?, acc) do 1283 {:ok, acc} 1284 end 1285 1286 defp array([nil | t], type, fun, true, acc) do 1287 array(t, type, fun, true, [nil | acc]) 1288 end 1289 1290 defp array([h | t], type, fun, skip_nil?, acc) do 1291 case fun.(type, h) do 1292 {:ok, h} -> array(t, type, fun, skip_nil?, [h | acc]) 1293 :error -> :error 1294 end 1295 end 1296 1297 defp array([], _type, _fun, _skip_nil?, acc) do 1298 {:ok, Enum.reverse(acc)} 1299 end 1300 1301 defp array(_, _, _, _, _) do 1302 :error 1303 end 1304 1305 defp map(map, type, fun, skip_nil?, acc) when is_map(map) do 1306 map_each(Map.to_list(map), type, fun, skip_nil?, acc) 1307 end 1308 1309 defp map(_, _, _, _, _) do 1310 :error 1311 end 1312 1313 defp map_each([{key, value} | t], type, fun, skip_nil?, acc) do 1314 case fun.(type, value) do 1315 {:ok, value} -> map_each(t, type, fun, skip_nil?, Map.put(acc, key, value)) 1316 :error -> :error 1317 end 1318 end 1319 1320 defp map_each([], _type, _fun, _skip_nil?, acc) do 1321 {:ok, acc} 1322 end 1323 1324 defp to_i(nil), do: nil 1325 defp to_i(int) when is_integer(int), do: int 1326 defp to_i(bin) when is_binary(bin) do 1327 case Integer.parse(bin) do 1328 {int, ""} -> int 1329 _ -> nil 1330 end 1331 end 1332 1333 defp maybe_truncate_usec({:ok, struct}), do: {:ok, truncate_usec(struct)} 1334 defp maybe_truncate_usec(:error), do: :error 1335 1336 defp maybe_pad_usec({:ok, struct}), do: {:ok, pad_usec(struct)} 1337 defp maybe_pad_usec(:error), do: :error 1338 1339 defp truncate_usec(nil), do: nil 1340 defp truncate_usec(%{microsecond: {0, 0}} = struct), do: struct 1341 defp truncate_usec(struct), do: %{struct | microsecond: {0, 0}} 1342 1343 defp pad_usec(nil), do: nil 1344 defp pad_usec(%{microsecond: {_, 6}} = struct), do: struct 1345 1346 defp pad_usec(%{microsecond: {microsecond, _}} = struct), 1347 do: %{struct | microsecond: {microsecond, 6}} 1348 1349 defp check_utc_timezone!(%{time_zone: "Etc/UTC"} = datetime, _kind), do: datetime 1350 1351 defp check_utc_timezone!(datetime, kind) do 1352 raise ArgumentError, 1353 "#{inspect kind} expects the time zone to be \"Etc/UTC\", got `#{inspect(datetime)}`" 1354 end 1355 1356 defp check_usec!(%{microsecond: {_, 6}} = datetime, _kind), do: datetime 1357 1358 defp check_usec!(datetime, kind) do 1359 raise ArgumentError, 1360 "#{inspect(kind)} expects microsecond precision, got: #{inspect(datetime)}" 1361 end 1362 1363 defp check_no_usec!(%{microsecond: {0, 0}} = datetime, _kind), do: datetime 1364 1365 defp check_no_usec!(%struct{} = datetime, kind) do 1366 raise ArgumentError, """ 1367 #{inspect(kind)} expects microseconds to be empty, got: #{inspect(datetime)} 1368 1369 Use `#{inspect(struct)}.truncate(#{kind}, :second)` (available in Elixir v1.6+) to remove microseconds. 1370 """ 1371 end 1372 1373 defp check_decimal(%Decimal{coef: coef} = decimal, _) when is_integer(coef), do: {:ok, decimal} 1374 defp check_decimal(_decimal, false), do: :error 1375 defp check_decimal(decimal, true) do 1376 raise ArgumentError, """ 1377 #{inspect(decimal)} is not allowed for type :decimal 1378 1379 `+Infinity`, `-Infinity`, and `NaN` values are not supported, even though the `Decimal` library handles them. \ 1380 To support them, you can create a custom type. 1381 """ 1382 end 1383 end