ea 0.1.0 → 0.1.4

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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +125 -0
  3. data/Rakefile +12 -4
  4. data/TODO.next/00-publish-blocking-bugs.md +74 -0
  5. data/TODO.next/01-standalone-ea-gem-identity.md +76 -0
  6. data/TODO.next/02-optional-lutaml-uml-dependency.md +47 -0
  7. data/TODO.next/03-slim-lutaml-uml.md +79 -0
  8. data/TODO.next/04-loader-registry-for-uml-repository.md +49 -0
  9. data/TODO.next/05-extract-shared-transformer-methods.md +14 -0
  10. data/TODO.next/06-deduplicate-stereotype-loading.md +17 -0
  11. data/TODO.next/07-transformer-registry-in-factory.md +20 -0
  12. data/TODO.next/08-connector-type-registry.md +27 -0
  13. data/TODO.next/09-element-renderer-registry.md +29 -0
  14. data/TODO.next/10-connector-renderer-lsp.md +18 -0
  15. data/TODO.next/11-consolidate-style-knowledge.md +33 -0
  16. data/TODO.next/12-data-driven-from-db-row.md +24 -0
  17. data/TODO.next/13-extract-duplicated-methods.md +17 -0
  18. data/TODO.next/14-remove-dead-code.md +10 -0
  19. data/TODO.next/15-narrow-exception-handling.md +39 -0
  20. data/TODO.next/16-repository-indexes.md +28 -0
  21. data/TODO.next/17-fix-spec-quality-and-coverage.md +32 -0
  22. data/TODO.next/18-xmi-tool-specific-parser-architecture.md +172 -0
  23. data/TODO.next/19-fix-ea-gemspec-dependency-declarations.md +56 -0
  24. data/TODO.next/20-ci-requires-unreleased-lutaml-uml.md +63 -0
  25. data/TODO.next/21-qeatoxmi-via-xmi-gem.md +340 -0
  26. data/TODO.next/22-strip-respond-to-from-qeatoxmi-specs.md +32 -0
  27. data/TODO.next/23-cleanup-idallocator.md +41 -0
  28. data/TODO.next/24-tighten-parity-specs.md +42 -0
  29. data/TODO.next/25-sparx-eaid-format-for-synthesized-ids.md +62 -0
  30. data/TODO.next/26-fix-uppervalue-lowervalue-count-gap.md +51 -0
  31. data/TODO.next/27-extract-cardinality-module.md +68 -0
  32. data/TODO.next/28-extract-xml-sanitizer.md +51 -0
  33. data/TODO.next/29-ocp-registry-for-classifier-builders.md +58 -0
  34. data/TODO.next/30-struct-return-for-association-end.md +37 -0
  35. data/TODO.next/31-idallocator-specs.md +27 -0
  36. data/TODO.next/32-phase2-gap-sentinel-specs.md +53 -0
  37. data/TODO.next/33-normalize-lower-cleanup.md +30 -0
  38. data/TODO.next/34-document-member-end-order-rt-prefix.md +29 -0
  39. data/TODO.next/35-walk-runstate-for-instance-slots.md +76 -0
  40. data/TODO.next/36-wire-interface-realization.md +50 -0
  41. data/TODO.next/37-visibility-returns-real-booleans.md +36 -0
  42. data/config/diagram_styles.yml +200 -0
  43. data/config/model_transformations.yml +266 -0
  44. data/config/qea_schema.yml +1024 -0
  45. data/docs/ea_to_uml_type_mapping.md +89 -0
  46. data/docs/xmi_qea_conversion_capabilities.md +99 -0
  47. data/examples/lur/20251010_current_plateau_v5.1.lur +0 -0
  48. data/examples/lur/basic.lur +0 -0
  49. data/examples/lur/test-output.lur +0 -0
  50. data/examples/lur/test.lur +0 -0
  51. data/examples/lur_basic_usage.rb +221 -0
  52. data/examples/lur_cli_workflow.rb +263 -0
  53. data/examples/lur_statistics.rb +326 -0
  54. data/examples/qea/20251010_current_plateau_v5.1.qea +0 -0
  55. data/examples/qea/ArcGISWorkspace_template.qea +0 -0
  56. data/examples/qea/README_qea_parser.adoc +230 -0
  57. data/examples/qea/UmlModel_template.qea +0 -0
  58. data/examples/qea/basic.qea +0 -0
  59. data/examples/qea/simple.qea +0 -0
  60. data/examples/qea/simple_example.qea +0 -0
  61. data/examples/qea/test.qea +0 -0
  62. data/examples/qea_standalone_query.rb +73 -0
  63. data/examples/qea_to_repository.rb +51 -0
  64. data/examples/smoke_test_real_qea.rb +81 -0
  65. data/exe/ea +7 -0
  66. data/lib/ea/cli/app.rb +72 -0
  67. data/lib/ea/cli/command/base.rb +80 -0
  68. data/lib/ea/cli/command/convert.rb +62 -0
  69. data/lib/ea/cli/command/diagrams.rb +81 -0
  70. data/lib/ea/cli/command/list.rb +61 -0
  71. data/lib/ea/cli/command/parse.rb +29 -0
  72. data/lib/ea/cli/command/stats.rb +20 -0
  73. data/lib/ea/cli/command/validate.rb +41 -0
  74. data/lib/ea/cli/command.rb +15 -0
  75. data/lib/ea/cli/error.rb +34 -0
  76. data/lib/ea/cli/output/formatter.rb +34 -0
  77. data/lib/ea/cli/output/json_formatter.rb +20 -0
  78. data/lib/ea/cli/output/table_formatter.rb +42 -0
  79. data/lib/ea/cli/output/yaml_formatter.rb +20 -0
  80. data/lib/ea/cli/output.rb +56 -0
  81. data/lib/ea/cli.rb +17 -0
  82. data/lib/ea/diagram/configuration.rb +379 -0
  83. data/lib/ea/diagram/element_renderers/base_renderer.rb +77 -0
  84. data/lib/ea/diagram/element_renderers/class_renderer.rb +323 -0
  85. data/lib/ea/diagram/element_renderers/connector_renderer.rb +41 -0
  86. data/lib/ea/diagram/element_renderers/package_renderer.rb +61 -0
  87. data/lib/ea/diagram/element_renderers.rb +43 -0
  88. data/lib/ea/diagram/extractor.rb +560 -0
  89. data/lib/ea/diagram/layout_engine.rb +170 -0
  90. data/lib/ea/diagram/path_builder.rb +202 -0
  91. data/lib/ea/diagram/style_parser.rb +42 -0
  92. data/lib/ea/diagram/style_resolver.rb +276 -0
  93. data/lib/ea/diagram/svg_renderer.rb +274 -0
  94. data/lib/ea/diagram/util.rb +73 -0
  95. data/lib/ea/diagram.rb +47 -0
  96. data/lib/ea/qea/benchmark.rb +210 -0
  97. data/lib/ea/qea/database.rb +308 -0
  98. data/lib/ea/qea/factory/association_builder.rb +203 -0
  99. data/lib/ea/qea/factory/association_transformer.rb +91 -0
  100. data/lib/ea/qea/factory/attribute_tag_transformer.rb +57 -0
  101. data/lib/ea/qea/factory/attribute_transformer.rb +93 -0
  102. data/lib/ea/qea/factory/base_transformer.rb +177 -0
  103. data/lib/ea/qea/factory/class_transformer.rb +116 -0
  104. data/lib/ea/qea/factory/constraint_transformer.rb +75 -0
  105. data/lib/ea/qea/factory/data_type_transformer.rb +77 -0
  106. data/lib/ea/qea/factory/diagram_transformer.rb +157 -0
  107. data/lib/ea/qea/factory/document_builder.rb +283 -0
  108. data/lib/ea/qea/factory/ea_to_uml_factory.rb +229 -0
  109. data/lib/ea/qea/factory/enum_transformer.rb +74 -0
  110. data/lib/ea/qea/factory/generalization_builder.rb +227 -0
  111. data/lib/ea/qea/factory/generalization_transformer.rb +98 -0
  112. data/lib/ea/qea/factory/instance_transformer.rb +68 -0
  113. data/lib/ea/qea/factory/object_property_transformer.rb +58 -0
  114. data/lib/ea/qea/factory/operation_transformer.rb +66 -0
  115. data/lib/ea/qea/factory/package_transformer.rb +145 -0
  116. data/lib/ea/qea/factory/reference_resolver.rb +99 -0
  117. data/lib/ea/qea/factory/stereotype_loader.rb +39 -0
  118. data/lib/ea/qea/factory/tagged_value_transformer.rb +38 -0
  119. data/lib/ea/qea/factory/transformer_registry.rb +80 -0
  120. data/lib/ea/qea/factory.rb +37 -0
  121. data/lib/ea/qea/file_detector.rb +178 -0
  122. data/lib/ea/qea/infrastructure/database_connection.rb +100 -0
  123. data/lib/ea/qea/infrastructure/schema_reader.rb +136 -0
  124. data/lib/ea/qea/infrastructure/table_reader.rb +224 -0
  125. data/lib/ea/qea/infrastructure.rb +12 -0
  126. data/lib/ea/qea/models/base_model.rb +59 -0
  127. data/lib/ea/qea/models/ea_attribute.rb +109 -0
  128. data/lib/ea/qea/models/ea_attribute_tag.rb +100 -0
  129. data/lib/ea/qea/models/ea_complexity_type.rb +79 -0
  130. data/lib/ea/qea/models/ea_connector.rb +160 -0
  131. data/lib/ea/qea/models/ea_connector_type.rb +60 -0
  132. data/lib/ea/qea/models/ea_constraint_type.rb +63 -0
  133. data/lib/ea/qea/models/ea_datatype.rb +104 -0
  134. data/lib/ea/qea/models/ea_diagram.rb +115 -0
  135. data/lib/ea/qea/models/ea_diagram_link.rb +78 -0
  136. data/lib/ea/qea/models/ea_diagram_object.rb +73 -0
  137. data/lib/ea/qea/models/ea_diagram_type.rb +56 -0
  138. data/lib/ea/qea/models/ea_document.rb +63 -0
  139. data/lib/ea/qea/models/ea_object.rb +223 -0
  140. data/lib/ea/qea/models/ea_object_constraint.rb +53 -0
  141. data/lib/ea/qea/models/ea_object_property.rb +87 -0
  142. data/lib/ea/qea/models/ea_object_type.rb +73 -0
  143. data/lib/ea/qea/models/ea_operation.rb +127 -0
  144. data/lib/ea/qea/models/ea_operation_param.rb +76 -0
  145. data/lib/ea/qea/models/ea_package.rb +78 -0
  146. data/lib/ea/qea/models/ea_script.rb +62 -0
  147. data/lib/ea/qea/models/ea_status_type.rb +66 -0
  148. data/lib/ea/qea/models/ea_stereotype.rb +57 -0
  149. data/lib/ea/qea/models/ea_tagged_value.rb +99 -0
  150. data/lib/ea/qea/models/ea_xref.rb +165 -0
  151. data/lib/ea/qea/models.rb +35 -0
  152. data/lib/ea/qea/repositories/base_repository.rb +225 -0
  153. data/lib/ea/qea/repositories/object_repository.rb +219 -0
  154. data/lib/ea/qea/repositories.rb +10 -0
  155. data/lib/ea/qea/services/configuration.rb +211 -0
  156. data/lib/ea/qea/services/database_loader.rb +191 -0
  157. data/lib/ea/qea/services.rb +10 -0
  158. data/lib/ea/qea/validation/association_validator.rb +73 -0
  159. data/lib/ea/qea/validation/attribute_validator.rb +91 -0
  160. data/lib/ea/qea/validation/base_validator.rb +331 -0
  161. data/lib/ea/qea/validation/class_validator.rb +121 -0
  162. data/lib/ea/qea/validation/database/circular_reference_validator.rb +109 -0
  163. data/lib/ea/qea/validation/database/orphan_validator.rb +153 -0
  164. data/lib/ea/qea/validation/database/referential_integrity_validator.rb +128 -0
  165. data/lib/ea/qea/validation/database.rb +16 -0
  166. data/lib/ea/qea/validation/diagram_validator.rb +112 -0
  167. data/lib/ea/qea/validation/formatters/json_formatter.rb +137 -0
  168. data/lib/ea/qea/validation/formatters/text_formatter.rb +235 -0
  169. data/lib/ea/qea/validation/formatters.rb +12 -0
  170. data/lib/ea/qea/validation/operation_validator.rb +71 -0
  171. data/lib/ea/qea/validation/package_validator.rb +111 -0
  172. data/lib/ea/qea/validation/validation_engine.rb +387 -0
  173. data/lib/ea/qea/validation/validation_message.rb +144 -0
  174. data/lib/ea/qea/validation/validation_result.rb +210 -0
  175. data/lib/ea/qea/validation/validator_registry.rb +134 -0
  176. data/lib/ea/qea/validation.rb +28 -0
  177. data/lib/ea/qea/verification/comparison_result.rb +264 -0
  178. data/lib/ea/qea/verification/document_normalizer.rb +169 -0
  179. data/lib/ea/qea/verification/document_verifier.rb +322 -0
  180. data/lib/ea/qea/verification/element_comparator.rb +277 -0
  181. data/lib/ea/qea/verification/structure_matcher.rb +287 -0
  182. data/lib/ea/qea/verification.rb +14 -0
  183. data/lib/ea/qea.rb +185 -0
  184. data/lib/ea/transformations/configuration.rb +333 -0
  185. data/lib/ea/transformations/format_registry.rb +366 -0
  186. data/lib/ea/transformations/parsers/base_parser.rb +482 -0
  187. data/lib/ea/transformations/parsers/qea_parser.rb +401 -0
  188. data/lib/ea/transformations/parsers/xmi_parser.rb +243 -0
  189. data/lib/ea/transformations/transformation_engine.rb +390 -0
  190. data/lib/ea/transformations.rb +85 -0
  191. data/lib/ea/transformers/qea_to_xmi/association_end.rb +19 -0
  192. data/lib/ea/transformers/qea_to_xmi/cardinality.rb +96 -0
  193. data/lib/ea/transformers/qea_to_xmi/context.rb +106 -0
  194. data/lib/ea/transformers/qea_to_xmi/guid_format.rb +56 -0
  195. data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +92 -0
  196. data/lib/ea/transformers/qea_to_xmi/run_state.rb +107 -0
  197. data/lib/ea/transformers/qea_to_xmi/transformer.rb +607 -0
  198. data/lib/ea/transformers/qea_to_xmi/visibility.rb +73 -0
  199. data/lib/ea/transformers/qea_to_xmi.rb +29 -0
  200. data/lib/ea/transformers/uml_to_xmi/id_generator.rb +54 -0
  201. data/lib/ea/transformers/uml_to_xmi/transformer.rb +152 -0
  202. data/lib/ea/transformers/uml_to_xmi/writer.rb +96 -0
  203. data/lib/ea/transformers/uml_to_xmi.rb +16 -0
  204. data/lib/ea/transformers.rb +34 -0
  205. data/lib/ea/version.rb +1 -1
  206. data/lib/ea/xmi/liquid_drops/association_drop.rb +56 -0
  207. data/lib/ea/xmi/liquid_drops/attribute_drop.rb +72 -0
  208. data/lib/ea/xmi/liquid_drops/cardinality_drop.rb +35 -0
  209. data/lib/ea/xmi/liquid_drops/connector_drop.rb +54 -0
  210. data/lib/ea/xmi/liquid_drops/constraint_drop.rb +29 -0
  211. data/lib/ea/xmi/liquid_drops/data_type_drop.rb +63 -0
  212. data/lib/ea/xmi/liquid_drops/dependency_drop.rb +36 -0
  213. data/lib/ea/xmi/liquid_drops/diagram_drop.rb +34 -0
  214. data/lib/ea/xmi/liquid_drops/enum_drop.rb +49 -0
  215. data/lib/ea/xmi/liquid_drops/enum_owned_literal_drop.rb +25 -0
  216. data/lib/ea/xmi/liquid_drops/generalization_attribute_drop.rb +87 -0
  217. data/lib/ea/xmi/liquid_drops/generalization_drop.rb +127 -0
  218. data/lib/ea/xmi/liquid_drops/klass_drop.rb +191 -0
  219. data/lib/ea/xmi/liquid_drops/operation_drop.rb +29 -0
  220. data/lib/ea/xmi/liquid_drops/package_drop.rb +108 -0
  221. data/lib/ea/xmi/liquid_drops/root_drop.rb +34 -0
  222. data/lib/ea/xmi/liquid_drops/source_target_drop.rb +43 -0
  223. data/lib/ea/xmi/lookup_service.rb +89 -0
  224. data/lib/ea/xmi/parser.rb +919 -0
  225. data/lib/ea/xmi.rb +35 -0
  226. data/lib/ea.rb +10 -1
  227. metadata +382 -9
@@ -0,0 +1,235 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Validation
6
+ module Formatters
7
+ # Formats validation results as human-readable text with colors
8
+ #
9
+ # @example Basic usage
10
+ # formatter = TextFormatter.new(result)
11
+ # puts formatter.format
12
+ #
13
+ # @example Without color
14
+ # formatter = TextFormatter.new(result, color: false)
15
+ # puts formatter.format
16
+ class TextFormatter
17
+ attr_reader :result, :options
18
+
19
+ # Creates a new text formatter
20
+ #
21
+ # @param result [ValidationResult] The validation result to format
22
+ # @param options [Hash] Formatting options
23
+ # @option options [Boolean] :color Enable colored output
24
+ # (default: true)
25
+ # @option options [Boolean] :verbose Show all messages
26
+ # (default: false)
27
+ # @option options [Integer] :limit Maximum messages per category
28
+ def initialize(result: nil, **options)
29
+ @result = result
30
+ @options = {
31
+ color: true,
32
+ verbose: false,
33
+ limit: nil,
34
+ }.merge(options)
35
+ end
36
+
37
+ # Formats the validation result as text
38
+ #
39
+ # @return [String] Formatted text output
40
+ def format # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
41
+ lines = []
42
+ lines << header
43
+ lines << ""
44
+ lines << summary
45
+ lines << ""
46
+
47
+ if result.has_errors?
48
+ lines << section_header("ERRORS", result.errors.size)
49
+ lines << format_messages(result.errors)
50
+ lines << ""
51
+ end
52
+
53
+ if result.has_warnings?
54
+ lines << section_header("WARNINGS", result.warnings.size)
55
+ lines << format_messages(result.warnings)
56
+ lines << ""
57
+ end
58
+
59
+ if result.has_info? && options[:verbose]
60
+ lines << section_header("INFO", result.info.size)
61
+ lines << format_messages(result.info)
62
+ lines << ""
63
+ end
64
+
65
+ lines << footer
66
+ lines.join("\n")
67
+ end
68
+
69
+ private
70
+
71
+ # Formats the header
72
+ #
73
+ # @return [String]
74
+ def header
75
+ line = "=" * 80
76
+ title = "QEA VALIDATION REPORT"
77
+ if options[:color]
78
+ "#{line}\n#{colorize(title, :cyan, bold: true)}\n#{line}"
79
+ else
80
+ "#{line}\n#{title}\n#{line}"
81
+ end
82
+ end
83
+
84
+ # Formats the summary
85
+ #
86
+ # @return [String]
87
+ def summary # rubocop:disable Metrics/AbcSize
88
+ status = if result.valid?
89
+ colorize("✓ VALID", :green, bold: true)
90
+ elsif result.has_errors?
91
+ colorize("✗ INVALID", :red, bold: true)
92
+ else
93
+ colorize("⚠ WARNINGS", :yellow, bold: true)
94
+ end
95
+
96
+ [
97
+ "Status: #{status}",
98
+ "",
99
+ "Messages:",
100
+ " Errors: #{colorize(result.errors.size.to_s, :red)}",
101
+ " Warnings: #{colorize(result.warnings.size.to_s, :yellow)}",
102
+ " Info: #{result.info.size}",
103
+ ].join("\n")
104
+ end
105
+
106
+ # Formats a section header
107
+ #
108
+ # @param title [String] Section title
109
+ # @param count [Integer] Message count
110
+ # @return [String]
111
+ def section_header(title, count)
112
+ color = case title
113
+ when "ERRORS" then :red
114
+ when "WARNINGS" then :yellow
115
+ else :cyan
116
+ end
117
+
118
+ colorize("#{title} (#{count}):", color, bold: true)
119
+ end
120
+
121
+ # Formats messages grouped by category
122
+ #
123
+ # @param messages [Array<ValidationMessage>] Messages to format
124
+ # @return [String]
125
+ def format_messages(messages) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
126
+ lines = []
127
+ messages_to_show = apply_limit(messages)
128
+
129
+ messages_to_show.group_by(&:category).each do |category, msgs|
130
+ category_name = format_category(category)
131
+ lines << colorize(" #{category_name} (#{msgs.size}):", :cyan)
132
+ lines << ""
133
+
134
+ msgs.each do |msg|
135
+ lines << format_message(msg)
136
+ lines << ""
137
+ end
138
+ end
139
+
140
+ if options[:limit] && messages.size > options[:limit]
141
+ remaining = messages.size - options[:limit]
142
+ lines << colorize(
143
+ " ... and #{remaining} more (use --verbose to see all)",
144
+ :yellow,
145
+ )
146
+ end
147
+
148
+ lines.join("\n")
149
+ end
150
+
151
+ # Formats a single message
152
+ #
153
+ # @param message [ValidationMessage] Message to format
154
+ # @return [String]
155
+ def format_message(message)
156
+ icon = severity_icon(message.severity)
157
+ entity_info = if message.entity_name
158
+ "#{message.entity_type}:#{message.entity_name}"
159
+ else
160
+ message.entity_type.to_s
161
+ end
162
+
163
+ [
164
+ " #{icon} #{colorize(entity_info, :blue)}",
165
+ " #{message.message}",
166
+ " ID: #{message.entity_id}",
167
+ ].join("\n")
168
+ end
169
+
170
+ # Formats the footer
171
+ #
172
+ # @return [String]
173
+ def footer
174
+ "=" * 80
175
+ end
176
+
177
+ # Applies message limit
178
+ #
179
+ # @param messages [Array<ValidationMessage>]
180
+ # @return [Array<ValidationMessage>]
181
+ def apply_limit(messages)
182
+ return messages unless options[:limit]
183
+
184
+ messages.first(options[:limit])
185
+ end
186
+
187
+ # Formats a category name
188
+ #
189
+ # @param category [Symbol] Category symbol
190
+ # @return [String]
191
+ def format_category(category)
192
+ category.to_s.split("_").map(&:capitalize).join(" ")
193
+ end
194
+
195
+ # Returns an icon for the severity level
196
+ #
197
+ # @param severity [Symbol] Severity level
198
+ # @return [String]
199
+ def severity_icon(severity)
200
+ case severity
201
+ when :error then colorize("✗", :red)
202
+ when :warning then colorize("⚠", :yellow)
203
+ when :info then colorize("ℹ", :blue)
204
+ else "•"
205
+ end
206
+ end
207
+
208
+ # Colorizes text if color is enabled
209
+ #
210
+ # @param text [String] Text to colorize
211
+ # @param color [Symbol] Color name
212
+ # @param bold [Boolean] Make text bold
213
+ # @return [String]
214
+ def colorize(text, color = nil, bold: false) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
215
+ return text unless options[:color]
216
+
217
+ codes = []
218
+ codes << 1 if bold
219
+
220
+ codes << case color
221
+ when :red then 31
222
+ when :green then 32
223
+ when :yellow then 33
224
+ when :blue then 34
225
+ when :cyan then 36
226
+ else 0
227
+ end
228
+
229
+ "\e[#{codes.join(';')}m#{text}\e[0m"
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Validation
6
+ module Formatters
7
+ autoload :TextFormatter, "ea/qea/validation/formatters/text_formatter"
8
+ autoload :JsonFormatter, "ea/qea/validation/formatters/json_formatter"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Validation
6
+ # Validates operation references and structure
7
+ class OperationValidator < BaseValidator
8
+ def validate
9
+ validate_parent_object_references
10
+ validate_return_types
11
+ end
12
+
13
+ private
14
+
15
+ def validate_parent_object_references # rubocop:disable Metrics/MethodLength
16
+ operations.each do |op|
17
+ unless reference_exists?("t_object", "ea_object_id",
18
+ op.ea_object_id)
19
+ result.add_error(
20
+ category: :missing_reference,
21
+ entity_type: :operation,
22
+ entity_id: op.operationid.to_s,
23
+ entity_name: op.name,
24
+ field: "ea_object_id",
25
+ reference: op.ea_object_id.to_s,
26
+ message: "Parent object #{op.ea_object_id} does not exist",
27
+ )
28
+ end
29
+ end
30
+ end
31
+
32
+ def validate_return_types # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
33
+ operations.each do |op|
34
+ next unless op.classifier && !op.classifier.empty?
35
+
36
+ # Classifier can be either an object name or a primitive type
37
+ next if primitive_type?(op.classifier)
38
+ next if classifier_exists?(op.classifier)
39
+
40
+ parent = database&.objects&.all&.find do |o|
41
+ o.ea_object_id == op.ea_object_id
42
+ end
43
+ result.add_warning(
44
+ category: :missing_reference,
45
+ entity_type: :operation,
46
+ entity_id: op.operationid.to_s,
47
+ entity_name: "#{parent&.name}.#{op.name}()",
48
+ field: "classifier",
49
+ reference: op.classifier,
50
+ message: "Return type '#{op.classifier}' not found",
51
+ )
52
+ end
53
+ end
54
+
55
+ def operations
56
+ @operations ||= context[:operations] || []
57
+ end
58
+
59
+ def classifier_exists?(classifier_id)
60
+ return false unless database
61
+
62
+ # Classifier field contains object_id, not name
63
+ database.objects.all.any? do |o|
64
+ o.ea_object_id.to_s == classifier_id.to_s
65
+ end
66
+ end
67
+
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Validation
6
+ # Validates package structure and hierarchy
7
+ class PackageValidator < BaseValidator
8
+ def validate
9
+ validate_parent_references
10
+ validate_duplicate_names
11
+ validate_circular_hierarchy
12
+ end
13
+
14
+ private
15
+
16
+ def validate_parent_references # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
17
+ packages.each do |package|
18
+ next if package.root?
19
+
20
+ unless parent_exists?(package.parent_id)
21
+ package_path = resolve_package_path(package.package_id)
22
+ result.add_error(
23
+ category: :missing_reference,
24
+ entity_type: :package,
25
+ entity_id: package.package_id.to_s,
26
+ entity_name: package.name,
27
+ field: "parent_id",
28
+ reference: package.parent_id.to_s,
29
+ message: "Parent package #{package.parent_id} does not exist",
30
+ location: package_path,
31
+ )
32
+ end
33
+ end
34
+ end
35
+
36
+ def validate_duplicate_names # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
37
+ packages_by_parent = packages.group_by(&:parent_id)
38
+
39
+ packages_by_parent.each do |parent_id, sibling_packages|
40
+ names = sibling_packages.map(&:name)
41
+ duplicates = names.select { |name| names.count(name) > 1 }.uniq
42
+
43
+ duplicates.each do |dup_name|
44
+ dup_packages = sibling_packages.select { |p| p.name == dup_name }
45
+ dup_packages.each do |package|
46
+ package_path = resolve_package_path(package.package_id)
47
+ parent_path = if parent_id
48
+ resolve_package_path(parent_id)
49
+ else
50
+ "Root"
51
+ end
52
+ result.add_warning(
53
+ category: :duplicate,
54
+ entity_type: :package,
55
+ entity_id: package.package_id.to_s,
56
+ entity_name: package.name,
57
+ field: "name",
58
+ message: "Duplicate package name '#{dup_name}' " \
59
+ "in parent #{parent_path}",
60
+ location: package_path,
61
+ )
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ def validate_circular_hierarchy # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
68
+ packages.each do |package|
69
+ next if package.root?
70
+
71
+ path = [package.package_id]
72
+ current_id = package.parent_id
73
+
74
+ while current_id && !current_id.zero?
75
+ if path.include?(current_id)
76
+ package_path = resolve_package_path(package.package_id)
77
+ result.add_error(
78
+ category: :circular_reference,
79
+ entity_type: :package,
80
+ entity_id: package.package_id.to_s,
81
+ entity_name: package.name,
82
+ field: "parent_id",
83
+ message: "Circular package hierarchy detected: " \
84
+ "#{path.join(' -> ')} -> #{current_id}",
85
+ location: package_path,
86
+ )
87
+ break
88
+ end
89
+
90
+ path << current_id
91
+ parent = packages.find { |p| p.package_id == current_id }
92
+ break unless parent
93
+
94
+ current_id = parent.parent_id
95
+ end
96
+ end
97
+ end
98
+
99
+ def packages
100
+ @packages ||= context[:db_packages] || []
101
+ end
102
+
103
+ def parent_exists?(parent_id)
104
+ return true if parent_id.nil? || parent_id.zero?
105
+
106
+ packages.any? { |p| p.package_id == parent_id }
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end