prosereflect 0.1.1 → 0.3.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 (158) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docs.yml +63 -0
  3. data/.github/workflows/links.yml +97 -0
  4. data/.github/workflows/rake.yml +4 -0
  5. data/.github/workflows/release.yml +5 -0
  6. data/.gitignore +4 -0
  7. data/.rubocop.yml +19 -1
  8. data/.rubocop_todo.yml +119 -183
  9. data/CLAUDE.md +78 -0
  10. data/Gemfile +8 -4
  11. data/README.adoc +2 -0
  12. data/Rakefile +3 -3
  13. data/docs/Gemfile +10 -0
  14. data/docs/INDEX.adoc +45 -0
  15. data/docs/_advanced/index.adoc +15 -0
  16. data/docs/_advanced/schema.adoc +112 -0
  17. data/docs/_advanced/step-map.adoc +66 -0
  18. data/docs/_advanced/steps.adoc +88 -0
  19. data/docs/_advanced/test-builder.adoc +61 -0
  20. data/docs/_advanced/transform.adoc +92 -0
  21. data/docs/_config.yml +174 -0
  22. data/docs/_features/html-input.adoc +69 -0
  23. data/docs/_features/html-output.adoc +45 -0
  24. data/docs/_features/index.adoc +15 -0
  25. data/docs/_features/marks.adoc +86 -0
  26. data/docs/_features/node-types.adoc +124 -0
  27. data/docs/_features/user-mentions.adoc +47 -0
  28. data/docs/_guides/custom-nodes.adoc +107 -0
  29. data/docs/_guides/index.adoc +13 -0
  30. data/docs/_guides/round-trip-html.adoc +91 -0
  31. data/docs/_guides/serialization.adoc +109 -0
  32. data/docs/_pages/index.adoc +67 -0
  33. data/docs/_reference/document-api.adoc +49 -0
  34. data/docs/_reference/index.adoc +14 -0
  35. data/docs/_reference/node-api.adoc +79 -0
  36. data/docs/_reference/schema-api.adoc +95 -0
  37. data/docs/_reference/transform-api.adoc +77 -0
  38. data/docs/_understanding/document-model.adoc +65 -0
  39. data/docs/_understanding/fragment.adoc +52 -0
  40. data/docs/_understanding/index.adoc +14 -0
  41. data/docs/_understanding/resolved-position.adoc +53 -0
  42. data/docs/_understanding/slice.adoc +54 -0
  43. data/docs/lychee.toml +63 -0
  44. data/lib/prosereflect/attribute/base.rb +4 -6
  45. data/lib/prosereflect/attribute/bold.rb +2 -4
  46. data/lib/prosereflect/attribute/href.rb +1 -3
  47. data/lib/prosereflect/attribute/id.rb +7 -7
  48. data/lib/prosereflect/attribute.rb +4 -7
  49. data/lib/prosereflect/blockquote.rb +19 -11
  50. data/lib/prosereflect/bullet_list.rb +36 -29
  51. data/lib/prosereflect/code_block.rb +23 -27
  52. data/lib/prosereflect/code_block_wrapper.rb +12 -13
  53. data/lib/prosereflect/document.rb +14 -22
  54. data/lib/prosereflect/fragment.rb +249 -0
  55. data/lib/prosereflect/hard_break.rb +6 -6
  56. data/lib/prosereflect/heading.rb +14 -15
  57. data/lib/prosereflect/horizontal_rule.rb +23 -14
  58. data/lib/prosereflect/image.rb +32 -23
  59. data/lib/prosereflect/input/html.rb +179 -104
  60. data/lib/prosereflect/input.rb +7 -0
  61. data/lib/prosereflect/list_item.rb +11 -12
  62. data/lib/prosereflect/mark/base.rb +9 -11
  63. data/lib/prosereflect/mark/bold.rb +1 -3
  64. data/lib/prosereflect/mark/code.rb +1 -3
  65. data/lib/prosereflect/mark/italic.rb +1 -3
  66. data/lib/prosereflect/mark/link.rb +1 -3
  67. data/lib/prosereflect/mark/strike.rb +1 -3
  68. data/lib/prosereflect/mark/subscript.rb +1 -3
  69. data/lib/prosereflect/mark/superscript.rb +1 -3
  70. data/lib/prosereflect/mark/underline.rb +1 -3
  71. data/lib/prosereflect/mark.rb +9 -5
  72. data/lib/prosereflect/node.rb +171 -33
  73. data/lib/prosereflect/ordered_list.rb +17 -14
  74. data/lib/prosereflect/output/html.rb +279 -50
  75. data/lib/prosereflect/output.rb +7 -0
  76. data/lib/prosereflect/paragraph.rb +11 -13
  77. data/lib/prosereflect/parser.rb +56 -66
  78. data/lib/prosereflect/resolved_pos.rb +256 -0
  79. data/lib/prosereflect/schema/attribute.rb +57 -0
  80. data/lib/prosereflect/schema/content_match.rb +656 -0
  81. data/lib/prosereflect/schema/fragment.rb +166 -0
  82. data/lib/prosereflect/schema/mark.rb +121 -0
  83. data/lib/prosereflect/schema/mark_type.rb +130 -0
  84. data/lib/prosereflect/schema/node.rb +236 -0
  85. data/lib/prosereflect/schema/node_type.rb +274 -0
  86. data/lib/prosereflect/schema/schema_main.rb +190 -0
  87. data/lib/prosereflect/schema/spec.rb +92 -0
  88. data/lib/prosereflect/schema.rb +39 -0
  89. data/lib/prosereflect/table.rb +12 -13
  90. data/lib/prosereflect/table_cell.rb +13 -13
  91. data/lib/prosereflect/table_header.rb +17 -17
  92. data/lib/prosereflect/table_row.rb +12 -12
  93. data/lib/prosereflect/text.rb +35 -11
  94. data/lib/prosereflect/transform/attr_step.rb +157 -0
  95. data/lib/prosereflect/transform/insert_step.rb +115 -0
  96. data/lib/prosereflect/transform/mapping.rb +82 -0
  97. data/lib/prosereflect/transform/mark_step.rb +269 -0
  98. data/lib/prosereflect/transform/replace_around_step.rb +181 -0
  99. data/lib/prosereflect/transform/replace_step.rb +157 -0
  100. data/lib/prosereflect/transform/slice.rb +91 -0
  101. data/lib/prosereflect/transform/step.rb +89 -0
  102. data/lib/prosereflect/transform/step_map.rb +126 -0
  103. data/lib/prosereflect/transform/structure.rb +120 -0
  104. data/lib/prosereflect/transform/transform.rb +341 -0
  105. data/lib/prosereflect/transform.rb +26 -0
  106. data/lib/prosereflect/user.rb +15 -15
  107. data/lib/prosereflect/version.rb +1 -1
  108. data/lib/prosereflect.rb +30 -17
  109. data/prosereflect.gemspec +17 -16
  110. data/spec/fixtures/documents/formatted_text.yaml +14 -0
  111. data/spec/fixtures/documents/heading_paragraph.yaml +16 -0
  112. data/spec/fixtures/documents/lists_doc.yaml +32 -0
  113. data/spec/fixtures/documents/mixed_content.yaml +40 -0
  114. data/spec/fixtures/documents/nested_doc.yaml +20 -0
  115. data/spec/fixtures/documents/simple_doc.yaml +6 -0
  116. data/spec/fixtures/documents/table_doc.yaml +32 -0
  117. data/spec/fixtures/documents/transform_test.yaml +14 -0
  118. data/spec/fixtures/schema/custom_schema.rb +37 -0
  119. data/spec/fixtures/schema/test_schema.rb +46 -0
  120. data/spec/fixtures/test_builder/helpers.rb +212 -0
  121. data/spec/prosereflect/document_spec.rb +332 -330
  122. data/spec/prosereflect/fragment_spec.rb +273 -0
  123. data/spec/prosereflect/hard_break_spec.rb +125 -125
  124. data/spec/prosereflect/input/html_spec.rb +718 -522
  125. data/spec/prosereflect/node_spec.rb +311 -182
  126. data/spec/prosereflect/output/html_spec.rb +105 -105
  127. data/spec/prosereflect/output/whitespace_spec.rb +248 -0
  128. data/spec/prosereflect/paragraph_spec.rb +275 -274
  129. data/spec/prosereflect/parser/round_trip_spec.rb +472 -0
  130. data/spec/prosereflect/parser_spec.rb +185 -180
  131. data/spec/prosereflect/resolved_pos_spec.rb +74 -0
  132. data/spec/prosereflect/schema/conftest.rb +68 -0
  133. data/spec/prosereflect/schema/content_match_spec.rb +237 -0
  134. data/spec/prosereflect/schema/mark_spec.rb +274 -0
  135. data/spec/prosereflect/schema/mark_type_spec.rb +86 -0
  136. data/spec/prosereflect/schema/node_type_spec.rb +142 -0
  137. data/spec/prosereflect/schema/schema_spec.rb +194 -0
  138. data/spec/prosereflect/table_cell_spec.rb +183 -183
  139. data/spec/prosereflect/table_row_spec.rb +149 -149
  140. data/spec/prosereflect/table_spec.rb +320 -318
  141. data/spec/prosereflect/test_builder/marks_spec.rb +127 -0
  142. data/spec/prosereflect/text_spec.rb +133 -132
  143. data/spec/prosereflect/transform/equivalence_spec.rb +487 -0
  144. data/spec/prosereflect/transform/mapping_spec.rb +226 -0
  145. data/spec/prosereflect/transform/replace_spec.rb +832 -0
  146. data/spec/prosereflect/transform/replace_step_spec.rb +157 -0
  147. data/spec/prosereflect/transform/slice_spec.rb +48 -0
  148. data/spec/prosereflect/transform/step_map_spec.rb +70 -0
  149. data/spec/prosereflect/transform/step_spec.rb +211 -0
  150. data/spec/prosereflect/transform/structure_spec.rb +98 -0
  151. data/spec/prosereflect/transform/transform_spec.rb +238 -0
  152. data/spec/prosereflect/user_spec.rb +31 -28
  153. data/spec/prosereflect_spec.rb +28 -26
  154. data/spec/spec_helper.rb +7 -6
  155. data/spec/support/matchers.rb +6 -6
  156. data/spec/support/shared_examples.rb +49 -49
  157. metadata +96 -5
  158. data/spec/prosereflect/version_spec.rb +0 -11
@@ -1,27 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'node'
4
- require_relative 'text'
5
- require_relative 'paragraph'
6
- require_relative 'table'
7
- require_relative 'table_row'
8
- require_relative 'table_cell'
9
- require_relative 'table_header'
10
- require_relative 'hard_break'
11
- require_relative 'document'
12
- require_relative 'heading'
13
- require_relative 'mark/bold'
14
- require_relative 'mark/italic'
15
- require_relative 'mark/code'
16
- require_relative 'mark/link'
17
- require_relative 'ordered_list'
18
- require_relative 'bullet_list'
19
- require_relative 'list_item'
20
- require_relative 'blockquote'
21
- require_relative 'horizontal_rule'
22
- require_relative 'image'
23
- require_relative 'user'
24
-
25
3
  module Prosereflect
26
4
  class Parser
27
5
  def self.parse(data)
@@ -33,57 +11,61 @@ module Prosereflect
33
11
  def self.parse_node(data)
34
12
  return nil unless data.is_a?(Hash)
35
13
 
36
- type = data['type']
37
- text = data['text']
38
- attrs = data['attrs']
39
- marks_data = data['marks']
14
+ type = data["type"]
15
+ text = data["text"]
16
+ attrs = data["attrs"]
17
+ marks_data = data["marks"]
40
18
 
41
19
  # Find the right class based on type
42
20
  node_class = case type
43
- when 'doc'
21
+ when "doc"
44
22
  Document
45
- when 'paragraph'
23
+ when "paragraph"
46
24
  Paragraph
47
- when 'text'
25
+ when "text"
48
26
  Text
49
- when 'table'
27
+ when "table"
50
28
  Table
51
- when 'table_row'
29
+ when "table_row"
52
30
  TableRow
53
- when 'table_cell'
31
+ when "table_cell"
54
32
  TableCell
55
- when 'table_header'
33
+ when "table_header"
56
34
  TableHeader
57
- when 'hard_break'
35
+ when "hard_break"
58
36
  HardBreak
59
- when 'heading'
37
+ when "heading"
60
38
  Heading
61
- when 'ordered_list'
39
+ when "ordered_list"
62
40
  OrderedList
63
- when 'bullet_list'
41
+ when "bullet_list"
64
42
  BulletList
65
- when 'list_item'
43
+ when "list_item"
66
44
  ListItem
67
- when 'blockquote'
45
+ when "blockquote"
68
46
  Blockquote
69
- when 'horizontal_rule'
47
+ when "horizontal_rule"
70
48
  HorizontalRule
71
- when 'image'
49
+ when "image"
72
50
  Image
73
- when 'user'
51
+ when "code_block"
52
+ CodeBlock
53
+ when "code_block_wrapper"
54
+ CodeBlockWrapper
55
+ when "user"
74
56
  User
75
57
  else
76
58
  Node
77
59
  end
78
60
 
79
- if type == 'text'
61
+ if type == "text"
80
62
  node = Text.new(text: text)
81
63
  else
82
64
  node = node_class.create(attrs)
83
65
 
84
66
  # Process content recursively
85
- if data['content'].is_a?(Array)
86
- data['content'].each do |content_data|
67
+ if data["content"].is_a?(Array)
68
+ data["content"].each do |content_data|
87
69
  child_node = parse_node(content_data)
88
70
  node.add_child(child_node) if child_node
89
71
  end
@@ -92,30 +74,35 @@ module Prosereflect
92
74
 
93
75
  # Handle special attributes for specific node types
94
76
  case type
95
- when 'ordered_list'
96
- node.start = attrs['start'].to_i if attrs && attrs['start']
97
- when 'bullet_list'
98
- node.bullet_style = attrs['bullet_style'] if attrs && attrs['bullet_style']
99
- when 'blockquote'
100
- node.citation = attrs['cite'] if attrs && attrs['cite']
101
- when 'horizontal_rule'
77
+ when "ordered_list"
78
+ node.start = attrs["start"].to_i if attrs && attrs["start"]
79
+ when "bullet_list"
80
+ node.bullet_style = attrs["bullet_style"] if attrs && attrs["bullet_style"]
81
+ when "blockquote"
82
+ node.citation = attrs["cite"] if attrs && attrs["cite"]
83
+ when "horizontal_rule"
84
+ if attrs
85
+ node.style = attrs["border_style"] if attrs["border_style"]
86
+ node.width = attrs["width"] if attrs["width"]
87
+ node.thickness = attrs["thickness"].to_i if attrs["thickness"]
88
+ end
89
+ when "image"
102
90
  if attrs
103
- node.style = attrs['border_style'] if attrs['border_style']
104
- node.width = attrs['width'] if attrs['width']
105
- node.thickness = attrs['thickness'].to_i if attrs['thickness']
91
+ node.src = attrs["src"] if attrs["src"]
92
+ node.alt = attrs["alt"] if attrs["alt"]
93
+ node.title = attrs["title"] if attrs["title"]
94
+ node.dimensions = [attrs["width"]&.to_i, attrs["height"]&.to_i]
106
95
  end
107
- when 'image'
96
+ when "table_header"
108
97
  if attrs
109
- node.src = attrs['src'] if attrs['src']
110
- node.alt = attrs['alt'] if attrs['alt']
111
- node.title = attrs['title'] if attrs['title']
112
- node.dimensions = [attrs['width']&.to_i, attrs['height']&.to_i]
98
+ node.scope = attrs["scope"] if attrs["scope"]
99
+ node.abbr = attrs["abbr"] if attrs["abbr"]
100
+ node.colspan = attrs["colspan"] if attrs["colspan"]
113
101
  end
114
- when 'table_header'
102
+ when "code_block"
115
103
  if attrs
116
- node.scope = attrs['scope'] if attrs['scope']
117
- node.abbr = attrs['abbr'] if attrs['abbr']
118
- node.colspan = attrs['colspan'] if attrs['colspan']
104
+ node.language = attrs["language"] if attrs["language"]
105
+ node.line_numbers = attrs["line_numbers"] if attrs["line_numbers"]
119
106
  end
120
107
  end
121
108
 
@@ -125,8 +112,11 @@ module Prosereflect
125
112
  end
126
113
 
127
114
  def self.parse_document(data)
128
- raise ArgumentError, 'Input cannot be nil' if data.nil?
129
- raise ArgumentError, "Input must be a hash, got #{data.class}" unless data.is_a?(Hash)
115
+ raise ArgumentError, "Input cannot be nil" if data.nil?
116
+ unless data.is_a?(Hash)
117
+ raise ArgumentError,
118
+ "Input must be a hash, got #{data.class}"
119
+ end
130
120
 
131
121
  document = parse_node(data)
132
122
 
@@ -0,0 +1,256 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prosereflect
4
+ # ResolvedPos represents a document position that has been resolved
5
+ # to a specific location in the document tree.
6
+ #
7
+ # The path array contains: [parent_node, index, start, parent_node, index, start, ...]
8
+ # depth 0 = before any nodes, depth N = inside node at path[N*2]
9
+ class ResolvedPos
10
+ attr_reader :pos, :path, :depth
11
+
12
+ def initialize(pos, path, depth)
13
+ @pos = pos
14
+ @path = path
15
+ @depth = depth
16
+ @parent_offset = nil
17
+ end
18
+
19
+ # The parent node at current depth
20
+ def parent
21
+ @path[@depth * 3]
22
+ end
23
+
24
+ # Index within parent
25
+ def index(depth = @depth)
26
+ @path[(depth * 3) + 1]
27
+ end
28
+
29
+ # Start position of current parent node
30
+ def start(depth = @depth)
31
+ @path[(depth * 3) + 2]
32
+ end
33
+
34
+ # End position of current parent node
35
+ def end_(depth = @depth)
36
+ start(depth) + parent.content.size
37
+ end
38
+
39
+ # The node at a given depth
40
+ def node(depth = @depth)
41
+ @path[depth * 3]
42
+ end
43
+
44
+ # Position within the parent node
45
+ def parent_offset
46
+ @parent_offset ||= @pos - start
47
+ end
48
+
49
+ # Marks at this position
50
+ def marks
51
+ if depth.zero?
52
+ # At root - no marks
53
+ []
54
+ else
55
+ parent_mark = parent.respond_to?(:marks) ? parent.marks : []
56
+ parent_mark || []
57
+ end
58
+ end
59
+
60
+ # Marks between two positions
61
+ def marks_between(from, to, marks)
62
+ result = marks.dup
63
+ nodes_between(from, to) do |node|
64
+ if node.respond_to?(:marks) && node.marks
65
+ result = result | node.marks
66
+ end
67
+ end
68
+ result
69
+ end
70
+
71
+ # Find shared depth with another position
72
+ def shared_depth(other_pos)
73
+ my_depth = depth
74
+ other_depth = other_pos.depth
75
+
76
+ while my_depth > other_depth
77
+ my_depth -= 1
78
+ end
79
+
80
+ while other_depth > my_depth
81
+ other_depth -= 1
82
+ end
83
+
84
+ while my_depth.positive?
85
+ break unless index(my_depth) == other_pos.index(my_depth)
86
+
87
+ my_depth -= 1
88
+
89
+ end
90
+
91
+ my_depth
92
+ end
93
+
94
+ # Get block range to another position
95
+ def block_range(other_pos = nil)
96
+ other_pos ||= self
97
+ NodeRange.new(self, other_pos)
98
+ end
99
+
100
+ # Check if at block boundary
101
+ def block?
102
+ parent.respond_to?(:is_block?) && parent.is_block?
103
+ end
104
+
105
+ # Check if at inline boundary
106
+ def inline?
107
+ !block?
108
+ end
109
+
110
+ # Check if in text block
111
+ def text_block?
112
+ parent.respond_to?(:is_textblock?) && parent.is_textblock?
113
+ end
114
+
115
+ # Check if at start of parent
116
+ def start_of_parent?
117
+ parent_offset.zero?
118
+ end
119
+
120
+ # Check if at end of parent
121
+ def end_of_parent?
122
+ parent_offset >= parent.content.size - 1
123
+ end
124
+
125
+ # Get position before current node
126
+ def before?
127
+ if depth.zero?
128
+ @pos.zero?
129
+ else
130
+ index.zero?
131
+ end
132
+ end
133
+
134
+ # Get position after current node
135
+ def after?
136
+ if depth.zero?
137
+ @pos >= 0
138
+ else
139
+ index >= parent.content.size
140
+ end
141
+ end
142
+
143
+ def eq?(other)
144
+ return false unless other.is_a?(ResolvedPos)
145
+
146
+ @pos == other.pos && @depth == other.depth
147
+ end
148
+
149
+ alias == eq?
150
+
151
+ def hash
152
+ [@pos, @depth].hash
153
+ end
154
+
155
+ def to_s
156
+ "<ResolvedPos #{@pos}:#{depth}>"
157
+ end
158
+
159
+ def inspect
160
+ to_s
161
+ end
162
+
163
+ private
164
+
165
+ def nodes_between(from, to, &block)
166
+ return unless to > from
167
+
168
+ depth.times do |d|
169
+ node = node(d)
170
+ node.nodes_between(from, to, &block) if node.respond_to?(:nodes_between)
171
+ end
172
+ end
173
+ end
174
+
175
+ # NodeRange represents a range between two resolved positions
176
+ class NodeRange
177
+ attr_reader :start, :end_
178
+
179
+ alias end end_
180
+
181
+ def initialize(start_resolved, end_resolved)
182
+ @start = start_resolved
183
+ @end_ = end_resolved
184
+ end
185
+
186
+ # Content fragment between start and end
187
+ def content
188
+ # Would extract the fragment
189
+ Fragment.new([])
190
+ end
191
+
192
+ # Nodes within this range
193
+ def nodes
194
+ result = []
195
+ start.node.nodes_between(start.pos, end_.pos) { |n| result << n }
196
+ result
197
+ end
198
+
199
+ def to_s
200
+ "<NodeRange #{start.pos}:#{end_.pos}>"
201
+ end
202
+
203
+ def inspect
204
+ to_s
205
+ end
206
+ end
207
+
208
+ # Extension to Node for position resolution
209
+ class Node
210
+ # Resolve a position to a ResolvedPos
211
+ def resolve(pos)
212
+ path = []
213
+ build_path_for_pos(pos, path)
214
+ depth = [(path.length / 3) - 1, 0].max
215
+ ResolvedPos.new(pos, path, depth)
216
+ end
217
+
218
+ private
219
+
220
+ def find_block_depth(common_depth)
221
+ block_depth = common_depth
222
+ while block_depth.positive?
223
+ current_node = node(block_depth)
224
+ break if current_node.respond_to?(:is_block?) && current_node.is_block?
225
+
226
+ block_depth -= 1
227
+ end
228
+ block_depth
229
+ end
230
+
231
+ def build_path_for_pos(pos, path, index = 0, start_offset = 0)
232
+ path << self << index << start_offset
233
+ return if pos.zero?
234
+
235
+ traverse_children_for_resolve(pos, path)
236
+ end
237
+
238
+ def traverse_children_for_resolve(pos, path)
239
+ return unless content
240
+
241
+ content_offset = 1
242
+ child_index = 0
243
+
244
+ content.each do |child|
245
+ child_end = content_offset + child.node_size
246
+ if pos < child_end
247
+ child.send(:build_path_for_pos, pos - content_offset, path, child_index, content_offset)
248
+ return
249
+ end
250
+
251
+ content_offset = child_end
252
+ child_index += 1
253
+ end
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prosereflect
4
+ class Schema
5
+ class Attribute
6
+ attr_reader :name, :default
7
+
8
+ def initialize(name:, default: nil, validate: nil)
9
+ @name = name
10
+ @default = default
11
+ @validate = validate
12
+ end
13
+
14
+ def has_default?
15
+ !@default.nil?
16
+ end
17
+
18
+ def required?
19
+ !has_default?
20
+ end
21
+
22
+ def validate_value(value)
23
+ return true if @validate.nil?
24
+ return @validate.call(value) if @validate.respond_to?(:call)
25
+
26
+ # Handle string-based type validation like "string", "number", "string|null"
27
+ validate_type(value, @validate.to_s)
28
+ end
29
+
30
+ private
31
+
32
+ def validate_type(value, type_str)
33
+ types = type_str.split("|")
34
+ actual_type = get_type_name(value)
35
+
36
+ unless types.include?(actual_type)
37
+ raise ::Prosereflect::SchemaErrors::ValidationError,
38
+ "Expected value of type #{types} for attribute #{@name}, got #{actual_type}"
39
+ end
40
+ true
41
+ end
42
+
43
+ def get_type_name(value)
44
+ case value
45
+ when nil then "null"
46
+ when String then "string"
47
+ when Integer, Float then "number"
48
+ when TrueClass, FalseClass then "boolean"
49
+ when Hash then "object"
50
+ when Array then "object"
51
+ else
52
+ value.class.name.downcase
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end