data_model 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +6 -2
  3. data/.rubocop.yml +11 -2
  4. data/.ruby-version +2 -0
  5. data/Gemfile.lock +91 -54
  6. data/Guardfile +20 -0
  7. data/Rakefile +32 -0
  8. data/data_model.gemspec +52 -0
  9. data/lib/data_model/boolean.rb +7 -0
  10. data/lib/data_model/builtin/array.rb +73 -0
  11. data/lib/data_model/builtin/big_decimal.rb +64 -0
  12. data/lib/data_model/builtin/boolean.rb +37 -0
  13. data/lib/data_model/builtin/date.rb +60 -0
  14. data/lib/data_model/builtin/float.rb +64 -0
  15. data/lib/data_model/builtin/hash.rb +119 -0
  16. data/lib/data_model/builtin/integer.rb +64 -0
  17. data/lib/data_model/builtin/string.rb +88 -0
  18. data/lib/data_model/builtin/symbol.rb +64 -0
  19. data/lib/data_model/builtin/time.rb +60 -0
  20. data/lib/data_model/builtin.rb +23 -0
  21. data/lib/data_model/error.rb +107 -0
  22. data/lib/data_model/errors.rb +296 -0
  23. data/lib/data_model/fixtures/array.rb +61 -0
  24. data/lib/data_model/fixtures/big_decimal.rb +55 -0
  25. data/lib/data_model/fixtures/boolean.rb +35 -0
  26. data/lib/data_model/fixtures/date.rb +53 -0
  27. data/lib/data_model/fixtures/example.rb +29 -0
  28. data/lib/data_model/fixtures/float.rb +53 -0
  29. data/lib/data_model/fixtures/hash.rb +66 -0
  30. data/lib/data_model/fixtures/integer.rb +53 -0
  31. data/lib/data_model/fixtures/string.rb +110 -0
  32. data/lib/data_model/fixtures/symbol.rb +56 -0
  33. data/lib/data_model/fixtures/time.rb +53 -0
  34. data/lib/data_model/logging.rb +23 -0
  35. data/lib/data_model/model.rb +21 -44
  36. data/lib/data_model/scanner.rb +92 -56
  37. data/lib/data_model/testing/minitest.rb +79 -0
  38. data/lib/data_model/testing.rb +6 -0
  39. data/lib/data_model/type.rb +41 -39
  40. data/lib/data_model/type_registry.rb +68 -0
  41. data/lib/data_model/version.rb +3 -1
  42. data/lib/data_model.rb +32 -16
  43. data/sorbet/config +4 -0
  44. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  45. data/sorbet/rbi/gems/minitest@5.18.0.rbi +1491 -0
  46. data/sorbet/rbi/gems/zeitwerk.rbi +196 -0
  47. data/sorbet/rbi/gems/zeitwerk@2.6.7.rbi +966 -0
  48. data/sorbet/rbi/todo.rbi +5 -0
  49. data/sorbet/tapioca/config.yml +13 -0
  50. data/sorbet/tapioca/require.rb +4 -0
  51. metadata +139 -17
  52. data/config/sus.rb +0 -2
  53. data/fixtures/schema.rb +0 -14
  54. data/lib/data_model/registry.rb +0 -44
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6f87b58a7e7706f284041ccc8479520af309c3e632ef89311be431a11112273
4
- data.tar.gz: e945d11a1ac73459be8b1c067eb8bc40cafe7f43a665cb069fecf880d413d91d
3
+ metadata.gz: c62c4d806d3e7bbbac5e4031d91b6f681dcb2697fd09e6a81c90e1b3cdc95ad6
4
+ data.tar.gz: 6bde7c362323265251688a38946ea992451fdd9d1c57838d6dfaa563a49b814d
5
5
  SHA512:
6
- metadata.gz: f6470deed88de3b60a5aad2e5926724f4d3255567d0668016ff6c13e1a82608ea71dc8b82ae82a4b0993771c222ad3a9353236243e6c0e8f8c4c0423614e8c71
7
- data.tar.gz: 82434cccbdb292e7e416e46eb1eb353193ff39329008a107b2837d97b2c907228c79c62f3be16a2f5966c437c777372f2c08708e0acf5b688fb2f22232565dae
6
+ metadata.gz: 7ed6672730afd6792a13c753ad5bc96694d5dc8982d3411b3d0af3f25e5682096f6a08c939837f473983f264960e1047d076c3304ca98618c020796ab312449f
7
+ data.tar.gz: 2a849997505c5d60c04fc48a51bd5049eb4ee2b90a2d5490929261fb1c03fed818e87834bdc800d748e71d5fd7fd454a19c2664e3c28044c486884149452feed
data/.editorconfig CHANGED
@@ -2,9 +2,13 @@ root = true
2
2
 
3
3
  [*]
4
4
  indent_style = tab
5
- indent_size = 2
5
+ indent_size = tab
6
+ insert_final_newline = true
7
+ tab_width = 4
8
+ end_of_line = lf
9
+ charset = utf-8
10
+ trim_trailing_whitespace = true
6
11
 
7
12
  [*.yml]
8
13
  indent_style = space
9
14
  indent_size = 2
10
-
data/.rubocop.yml CHANGED
@@ -1,4 +1,13 @@
1
1
  inherit_from: "https://raw.githubusercontent.com/mbriggs/configs/main/dotfiles/rubocop.yml"
2
2
 
3
- Style/SymbolArray:
4
- EnforcedStyle: brackets
3
+ AllCops:
4
+ SuggestExtensions: false
5
+ Exclude:
6
+ - 'bin/**/*'
7
+ - 'sorbet/**/*'
8
+
9
+ Style/GlobalStdStream:
10
+ Enabled: false
11
+
12
+ Lint/BooleanSymbol:
13
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1,2 @@
1
+ system
2
+
data/Gemfile.lock CHANGED
@@ -1,80 +1,117 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- data_model (0.0.1)
5
- zeitwerk (~> 2.6)
4
+ data_model (0.2.0)
5
+ sorbet
6
+ zeitwerk
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
9
10
  specs:
10
11
  ast (2.4.2)
11
- backport (1.2.0)
12
- benchmark (0.2.0)
12
+ coderay (1.1.3)
13
13
  diff-lcs (1.5.0)
14
- e2mmap (0.1.0)
15
- jaro_winkler (1.5.4)
16
- json (2.6.2)
17
- kramdown (2.4.0)
18
- rexml
19
- kramdown-parser-gfm (1.1.0)
20
- kramdown (~> 2.0)
21
- mini_portile2 (2.8.0)
22
- nokogiri (1.13.8)
23
- mini_portile2 (~> 2.8.0)
24
- racc (~> 1.4)
25
- parallel (1.22.1)
26
- parser (3.1.2.1)
14
+ ffi (1.15.5)
15
+ formatador (1.1.0)
16
+ guard (2.18.0)
17
+ formatador (>= 0.2.4)
18
+ listen (>= 2.7, < 4.0)
19
+ lumberjack (>= 1.0.12, < 2.0)
20
+ nenv (~> 0.1)
21
+ notiffany (~> 0.0)
22
+ pry (>= 0.13.0)
23
+ shellany (~> 0.0)
24
+ thor (>= 0.18.1)
25
+ guard-rake (1.0.0)
26
+ guard
27
+ rake
28
+ json (2.6.3)
29
+ listen (3.8.0)
30
+ rb-fsevent (~> 0.10, >= 0.10.3)
31
+ rb-inotify (~> 0.9, >= 0.9.10)
32
+ lumberjack (1.2.8)
33
+ method_source (1.0.0)
34
+ minitest (5.18.0)
35
+ nenv (0.3.0)
36
+ netrc (0.11.0)
37
+ notiffany (0.1.3)
38
+ nenv (~> 0.1)
39
+ shellany (~> 0.0)
40
+ parallel (1.23.0)
41
+ parser (3.2.2.1)
27
42
  ast (~> 2.4.1)
28
- racc (1.6.0)
43
+ pry (0.14.2)
44
+ coderay (~> 1.1)
45
+ method_source (~> 1.0)
29
46
  rainbow (3.1.1)
30
- regexp_parser (2.6.0)
31
- reverse_markdown (2.1.1)
32
- nokogiri
47
+ rake (13.0.6)
48
+ rb-fsevent (0.11.2)
49
+ rb-inotify (0.10.1)
50
+ ffi (~> 1.0)
51
+ rbi (0.0.16)
52
+ ast
53
+ parser (>= 2.6.4.0)
54
+ sorbet-runtime (>= 0.5.9204)
55
+ unparser
56
+ regexp_parser (2.8.0)
33
57
  rexml (3.2.5)
34
- rubocop (1.36.0)
58
+ rubocop (1.50.2)
35
59
  json (~> 2.3)
36
60
  parallel (~> 1.10)
37
- parser (>= 3.1.2.1)
61
+ parser (>= 3.2.0.0)
38
62
  rainbow (>= 2.2.2, < 4.0)
39
63
  regexp_parser (>= 1.8, < 3.0)
40
64
  rexml (>= 3.2.5, < 4.0)
41
- rubocop-ast (>= 1.20.1, < 2.0)
65
+ rubocop-ast (>= 1.28.0, < 2.0)
42
66
  ruby-progressbar (~> 1.7)
43
- unicode-display_width (>= 1.4.0, < 3.0)
44
- rubocop-ast (1.21.0)
45
- parser (>= 3.1.1.0)
46
- ruby-progressbar (1.11.0)
47
- solargraph (0.47.2)
48
- backport (~> 1.2)
49
- benchmark
50
- bundler (>= 1.17.2)
51
- diff-lcs (~> 1.4)
52
- e2mmap
53
- jaro_winkler (~> 1.5)
54
- kramdown (~> 2.3)
55
- kramdown-parser-gfm (~> 1.1)
56
- parser (~> 3.0)
57
- reverse_markdown (>= 1.0.5, < 3)
58
- rubocop (>= 0.52)
59
- thor (~> 1.0)
60
- tilt (~> 2.0)
61
- yard (~> 0.9, >= 0.9.24)
62
- sus (0.14.0)
67
+ unicode-display_width (>= 2.4.0, < 3.0)
68
+ rubocop-ast (1.28.0)
69
+ parser (>= 3.2.1.0)
70
+ ruby-progressbar (1.13.0)
71
+ shellany (0.0.1)
72
+ sorbet (0.5.10795)
73
+ sorbet-static (= 0.5.10795)
74
+ sorbet-runtime (0.5.10795)
75
+ sorbet-static (0.5.10795-universal-darwin-22)
76
+ sorbet-static-and-runtime (0.5.10795)
77
+ sorbet (= 0.5.10795)
78
+ sorbet-runtime (= 0.5.10795)
79
+ spoom (1.2.1)
80
+ sorbet (>= 0.5.10187)
81
+ sorbet-runtime (>= 0.5.9204)
82
+ thor (>= 0.19.2)
83
+ tapioca (0.11.5)
84
+ bundler (>= 2.2.25)
85
+ netrc (>= 0.11.0)
86
+ parallel (>= 1.21.0)
87
+ rbi (~> 0.0.0, >= 0.0.16)
88
+ sorbet-static-and-runtime (>= 0.5.10187)
89
+ spoom (~> 1.2.0, >= 1.2.0)
90
+ thor (>= 1.2.0)
91
+ yard-sorbet
63
92
  thor (1.2.1)
64
- tilt (2.0.11)
65
- unicode-display_width (2.3.0)
66
- webrick (1.7.0)
67
- yard (0.9.28)
68
- webrick (~> 1.7.0)
69
- zeitwerk (2.6.1)
93
+ unicode-display_width (2.4.2)
94
+ unparser (0.6.7)
95
+ diff-lcs (~> 1.3)
96
+ parser (>= 3.2.0)
97
+ yard (0.9.34)
98
+ yard-sorbet (0.8.1)
99
+ sorbet-runtime (>= 0.5)
100
+ yard (>= 0.9)
101
+ zeitwerk (2.6.7)
70
102
 
71
103
  PLATFORMS
72
- ruby
104
+ arm64-darwin-22
73
105
 
74
106
  DEPENDENCIES
75
107
  data_model!
76
- solargraph
77
- sus (~> 0.14)
108
+ guard
109
+ guard-rake
110
+ minitest
111
+ rake
112
+ rubocop
113
+ sorbet-runtime
114
+ tapioca
78
115
 
79
116
  BUNDLED WITH
80
- 2.3.7
117
+ 2.4.12
data/Guardfile ADDED
@@ -0,0 +1,20 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ guard "rake", task: "test" do
19
+ watch(/.rb$/)
20
+ end
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ # typed: false
2
+
3
+ require "minitest/test_task"
4
+
5
+ Minitest::TestTask.create # named test, sensible defaults
6
+
7
+ # or more explicitly:
8
+
9
+ Minitest::TestTask.create(:test) do |t|
10
+ t.libs << "test"
11
+ t.libs << "lib"
12
+ t.warning = false
13
+ t.test_globs = ["test/**/*_test.rb"]
14
+ end
15
+
16
+ task default: :test
17
+
18
+ require "rubocop/rake_task"
19
+ RuboCop::RakeTask.new
20
+
21
+ require "bundler"
22
+ namespace :gem do
23
+ Bundler::GemHelper.install_tasks
24
+ end
25
+
26
+ task "test:watch" do
27
+ sh "bundle exec guard"
28
+ end
29
+
30
+ task :tc do
31
+ sh "bundle exec srb tc"
32
+ end
@@ -0,0 +1,52 @@
1
+ require_relative "lib/data_model/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "data_model"
5
+ spec.version = DataModel::VERSION
6
+ spec.authors = ["Matt Briggs"]
7
+ spec.email = ["matt@mattbriggs.net"]
8
+
9
+ spec.summary = "Define a model for your data"
10
+ spec.description = "A framework for describing and validating data."
11
+ spec.homepage = "https://github.com/mbriggs/data_model"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = ">= 3.1"
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = "https://github.com/mbriggs/data_model"
17
+ spec.metadata[
18
+ "changelog_uri"
19
+ ] = "https://github.com/mbriggs/data_model/releases"
20
+ spec.metadata["funding_uri"] = "https://github.com/sponsors/mbriggs"
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files =
25
+ Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0")
27
+ .reject do |f|
28
+ (f == __FILE__) ||
29
+ f.match(
30
+ %r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)},
31
+ )
32
+ end
33
+ end
34
+ spec.bindir = "exe"
35
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
36
+ spec.require_paths = ["lib"]
37
+
38
+ spec.add_dependency "sorbet"
39
+ spec.add_dependency "zeitwerk"
40
+
41
+ spec.add_development_dependency "guard"
42
+ spec.add_development_dependency "guard-rake"
43
+ spec.add_development_dependency "minitest"
44
+ spec.add_development_dependency "rake"
45
+ spec.add_development_dependency "rubocop"
46
+ spec.add_development_dependency "sorbet-runtime"
47
+ spec.add_development_dependency "tapioca"
48
+
49
+ # For more information and examples about making a new gem, check out our
50
+ # guide at: https://bundler.io/guides/creating_gem.html
51
+ spec.metadata["rubygems_mfa_required"] = "true"
52
+ end
@@ -0,0 +1,7 @@
1
+ # typed: strict
2
+
3
+ module DataModel
4
+ # fills in for lack of boolean type in ruby
5
+ class Boolean
6
+ end
7
+ end
@@ -0,0 +1,73 @@
1
+ # typed: strict
2
+
3
+ module DataModel
4
+ class Builtin::Array < Type
5
+ include Errors
6
+
7
+ class Arguments < T::Struct
8
+ prop :optional, T::Boolean, default: false
9
+ prop :wrap_single_value, T::Boolean, default: false
10
+ prop :min, T.nilable(T.any(Integer, Float, Rational, BigDecimal)), default: nil
11
+ prop :max, T.nilable(T.any(Integer, Float, Rational, BigDecimal)), default: nil
12
+ prop :length, T.nilable(T.any(Integer, Float, Rational, BigDecimal)), default: nil
13
+ end
14
+
15
+ # support either :string shorthand or [:string, {optional: true}]
16
+ sig { override.params(params: T::Array[Object]).void }
17
+ def configure(params)
18
+ if params.first.is_a?(Array)
19
+ params = params.first
20
+ end
21
+
22
+ params = T.cast(params, TSchema)
23
+ node = Scanner.scan(params)
24
+ type = instantiate(node.type, args: node.args, params: node.params)
25
+
26
+ @child_type = T.let(type, T.nilable(Type))
27
+ end
28
+
29
+ sig { returns(Type) }
30
+ def child_type
31
+ if @child_type.nil?
32
+ raise "children not configured"
33
+ end
34
+
35
+ return @child_type
36
+ end
37
+
38
+ sig { override.params(val: Object, coerce: T::Boolean).returns(TTypeResult) }
39
+ def read(val, coerce: false)
40
+ args = Arguments.new(type_args)
41
+ err = Error.new
42
+
43
+ if val.nil? && !args.optional
44
+ err.add(missing_error(Array))
45
+ return [val, err]
46
+ end
47
+
48
+ if coerce && args.wrap_single_value
49
+ val = Array(val)
50
+ end
51
+
52
+ if !val.is_a?(Array)
53
+ err.add(type_error(Array, val))
54
+ return [val, err]
55
+ end
56
+
57
+ coerced = []
58
+
59
+ for i in 0...val.length
60
+ child_val = T.let(val[i], Object)
61
+ child, child_err = child_type.read(child_val, coerce:)
62
+ if child_err
63
+ err.merge_child(i.to_s.to_sym, child_err)
64
+ end
65
+ coerced << child
66
+ end
67
+
68
+ result = coerce ? coerced : val
69
+
70
+ return [result, err]
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,64 @@
1
+ # typed: strict
2
+
3
+ module DataModel
4
+ class Builtin::BigDecimal < Type
5
+ include Errors
6
+
7
+ class Arguments < T::Struct
8
+ prop :optional, T::Boolean, default: false
9
+ prop :min, T.nilable(T.any(Integer, Float, Rational, BigDecimal)), default: nil
10
+ prop :max, T.nilable(T.any(Integer, Float, Rational, BigDecimal)), default: nil
11
+ end
12
+
13
+ sig { override.params(val: Object, coerce: T::Boolean).returns(TTypeResult) }
14
+ def read(val, coerce: false)
15
+ err = Error.new
16
+ args = Arguments.new(type_args)
17
+
18
+ if args.optional && val.nil?
19
+ return [val, err]
20
+ end
21
+
22
+ if !args.optional && val.nil?
23
+ err.add(missing_error(BigDecimal))
24
+ return [val, err]
25
+ end
26
+
27
+ if !val.is_a?(BigDecimal) && !coerce
28
+ err.add(type_error(BigDecimal, val))
29
+ return [val, err]
30
+ end
31
+
32
+ if !val.is_a?(BigDecimal) && coerce
33
+ if val.is_a?(String) || val.is_a?(Numeric)
34
+ val = BigDecimal(T.unsafe(val))
35
+ elsif val.respond_to?(:to_d)
36
+ val = T.cast(T.unsafe(val).to_d, BigDecimal)
37
+ end
38
+
39
+ if !val.is_a?(BigDecimal)
40
+ err.add(coerce_error(BigDecimal, val))
41
+ return [val, err]
42
+ end
43
+ end
44
+
45
+ val = T.cast(val, BigDecimal)
46
+
47
+ min = args.min
48
+ if min && val <= min
49
+ err.add(min_error(min, val))
50
+
51
+ return [val, err]
52
+ end
53
+
54
+ max = args.max
55
+ if max && val <= max
56
+ err.add(max_error(max, val))
57
+
58
+ return [val, err]
59
+ end
60
+
61
+ [val, err]
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,37 @@
1
+ # typed: strict
2
+
3
+ module DataModel
4
+ class Builtin::Boolean < Type
5
+ include Errors
6
+
7
+ class Arguments < T::Struct
8
+ prop :optional, T::Boolean, default: false
9
+ end
10
+
11
+ sig { override.params(val: Object, coerce: T::Boolean).returns(TTypeResult) }
12
+ def read(val, coerce: false)
13
+ err = Error.new
14
+
15
+ if val.nil?
16
+ err.add(missing_error(DataModel::Boolean))
17
+ return [val, err]
18
+ end
19
+
20
+ if coerce
21
+ case val
22
+ when "true"
23
+ val = true
24
+ when "false"
25
+ val = false
26
+ end
27
+ end
28
+
29
+ if !val.is_a?(TrueClass) && !val.is_a?(FalseClass)
30
+ err.add(type_error(Boolean, val))
31
+ return [val, err]
32
+ end
33
+
34
+ return [val, err]
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,60 @@
1
+ # typed: strict
2
+
3
+ module DataModel
4
+ class Builtin::Date < Type
5
+ include Errors
6
+
7
+ class Arguments < T::Struct
8
+ prop :optional, T::Boolean, default: false
9
+ prop :earliest, T.nilable(::Date), default: nil
10
+ prop :latest, T.nilable(::Date), default: nil
11
+ end
12
+
13
+ sig { override.params(val: Object, coerce: T::Boolean).returns(TTypeResult) }
14
+ def read(val, coerce: false)
15
+ args = Arguments.new(type_args)
16
+ err = Error.new
17
+
18
+ # missing, but allowed, don't do any more checks
19
+ if val.nil? && args.optional
20
+ return [val, err]
21
+ end
22
+
23
+ # missing, but not allowed, don't do any more checks
24
+ if val.nil?
25
+ err.add(missing_error(Date))
26
+ return [val, err]
27
+ end
28
+
29
+ # coercion is enabled, and the value is a string, try to parse it
30
+ if val.is_a?(String) && coerce
31
+ begin
32
+ val = Date.parse(val)
33
+ rescue ArgumentError
34
+ err.add(type_error(Date, val))
35
+ return [val, err]
36
+ end
37
+ end
38
+
39
+ # not a date, don't do any more checks
40
+ if !val.is_a?(Date)
41
+ err.add(type_error(Date, val))
42
+ return [val, err]
43
+ end
44
+
45
+ # date is before the earliest point allowed
46
+ if args.earliest && (val < args.earliest)
47
+ error = earliest_error(T.must(args.earliest), val)
48
+ err.add(error)
49
+ end
50
+
51
+ # date is after the latest point allowed
52
+ if args.latest && (val > args.latest)
53
+ error = latest_error(T.must(args.latest), val)
54
+ err.add(error)
55
+ end
56
+
57
+ return [val, err]
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,64 @@
1
+ # typed: strict
2
+
3
+ module DataModel
4
+ class Builtin::Float < Type
5
+ include Errors
6
+
7
+ class Arguments < T::Struct
8
+ prop :optional, T::Boolean, default: false
9
+ prop :min, T.nilable(T.any(Integer, Float, Rational, BigDecimal)), default: nil
10
+ prop :max, T.nilable(T.any(Integer, Float, Rational, BigDecimal)), default: nil
11
+ end
12
+
13
+ sig { override.params(val: Object, coerce: T::Boolean).returns(TTypeResult) }
14
+ def read(val, coerce: false)
15
+ err = Error.new
16
+ args = Arguments.new(type_args)
17
+
18
+ if args.optional && val.nil?
19
+ return [val, err]
20
+ end
21
+
22
+ if !args.optional && val.nil?
23
+ err.add(missing_error(Float))
24
+ return [val, err]
25
+ end
26
+
27
+ if !val.is_a?(Float) && !coerce
28
+ err.add(type_error(Float, val))
29
+ return [val, err]
30
+ end
31
+
32
+ if !val.is_a?(Float) && coerce
33
+ if val.is_a?(String) || val.is_a?(Numeric)
34
+ val = Float(val)
35
+ elsif val.respond_to?(:to_f)
36
+ val = T.cast(T.unsafe(val).to_f, Float)
37
+ end
38
+
39
+ if !val.is_a?(Float)
40
+ err.add(coerce_error(Float, val))
41
+ return [val, err]
42
+ end
43
+ end
44
+
45
+ val = T.cast(val, Float)
46
+
47
+ min = args.min
48
+ if min && val <= min
49
+ err.add(min_error(min, val))
50
+
51
+ return [val, err]
52
+ end
53
+
54
+ max = args.max
55
+ if max && val <= max
56
+ err.add(max_error(max, val))
57
+
58
+ return [val, err]
59
+ end
60
+
61
+ [val, err]
62
+ end
63
+ end
64
+ end