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,387 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Validation
6
+ # Main orchestrator for the validation system
7
+ # Coordinates all validators and consolidates results
8
+ #
9
+ # @example Basic usage
10
+ # engine = ValidationEngine.new(document, database: db)
11
+ # result = engine.validate
12
+ # puts result.summary
13
+ #
14
+ # @example With specific validators
15
+ # engine = ValidationEngine.new(document, database: db)
16
+ # result = engine.validate(validators: [:package, :class])
17
+ class ValidationEngine
18
+ attr_reader :document, :database, :registry, :options
19
+
20
+ # Creates a new validation engine
21
+ #
22
+ # @param document [Object] The document to validate
23
+ # @param database [Ea::Qea::Database] Database connection
24
+ # @param options [Hash] Validation options
25
+ # @option options [Boolean] :strict Fail on errors
26
+ # @option options [Boolean] :verbose Detailed output
27
+ # @option options [Symbol] :min_severity Minimum severity to report
28
+ # @option options [Array<Symbol>] :categories Categories to check
29
+ def initialize(document, database: nil, **options)
30
+ @document = document
31
+ @database = database
32
+ @options = options
33
+ @registry = ValidatorRegistry.new
34
+ setup_default_validators
35
+ end
36
+
37
+ # Runs validation using two-phase architecture
38
+ #
39
+ # Phase 1: QEA Database Integrity Validation
40
+ # - Validates EA database schema constraints
41
+ # - Checks referential integrity
42
+ # - Detects orphaned records
43
+ # - Finds circular references
44
+ #
45
+ # Phase 2: UML Tree Structure Validation
46
+ # - Validates transformed UML document tree
47
+ # - Checks proper nesting
48
+ # - Validates duplicate names
49
+ # - Verifies type references
50
+ #
51
+ # @param validators [Array<Symbol>, nil] List of validators to run,
52
+ # or nil to run all
53
+ # @return [ValidationResult]
54
+ def validate(validators: nil) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
55
+ result = ValidationResult.new
56
+ context = build_context
57
+ context[:result] = result
58
+
59
+ # Phase 1: QEA Database Validation
60
+ phase1_result = validate_qea_database(context, validators)
61
+
62
+ # Phase 2: UML Tree Validation
63
+ phase2_result = validate_uml_tree(context, validators)
64
+
65
+ # Merge results
66
+ phase1_result.messages.each { |msg| result.messages << msg }
67
+ phase2_result.messages.each { |msg| result.messages << msg }
68
+
69
+ filter_result(result)
70
+ end
71
+
72
+ # Validates QEA database integrity
73
+ #
74
+ # @param context [Hash] Validation context
75
+ # @param validators [Array<Symbol>, nil] Optional validator filter
76
+ # @return [ValidationResult]
77
+ def validate_qea_database(context, validators = nil) # rubocop:disable Metrics/MethodLength
78
+ result = ValidationResult.new
79
+ db_context = context.merge(result: result)
80
+
81
+ database_validators = %i[
82
+ referential_integrity
83
+ orphan
84
+ circular_reference
85
+ package
86
+ ]
87
+
88
+ validator_names = if validators
89
+ (database_validators & validators)
90
+ else
91
+ database_validators
92
+ end
93
+
94
+ validator_names.each do |name|
95
+ next unless @registry.registered?(name)
96
+
97
+ @registry.validate(name, db_context)
98
+ end
99
+
100
+ result
101
+ end
102
+
103
+ # Validates UML document tree structure
104
+ #
105
+ # @param context [Hash] Validation context
106
+ # @param validators [Array<Symbol>, nil] Optional validator filter
107
+ # @return [ValidationResult]
108
+ def validate_uml_tree(context, validators = nil) # rubocop:disable Metrics/MethodLength
109
+ result = ValidationResult.new
110
+ uml_context = context.merge(result: result)
111
+
112
+ uml_validators = %i[
113
+ document_structure
114
+ class
115
+ attribute
116
+ operation
117
+ association
118
+ diagram
119
+ ]
120
+
121
+ validator_names = if validators
122
+ (uml_validators & validators)
123
+ else
124
+ uml_validators
125
+ end
126
+
127
+ validator_names.each do |name|
128
+ next unless @registry.registered?(name)
129
+
130
+ @registry.validate(name, uml_context)
131
+ end
132
+
133
+ result
134
+ end
135
+
136
+ # Validates and displays the result
137
+ #
138
+ # @param validators [Array<Symbol>, nil] List of validators to run
139
+ # @param formatter [Symbol] Output format (:text, :json, :html)
140
+ # @return [ValidationResult]
141
+ def validate_and_display(validators: nil, formatter: :text)
142
+ result = validate(validators: validators)
143
+ display_result(result, formatter)
144
+ result
145
+ end
146
+
147
+ # Registers a custom validator
148
+ #
149
+ # @param name [Symbol] Validator name
150
+ # @param validator_class [Class] Validator class
151
+ # @return [void]
152
+ def register_validator(name, validator_class)
153
+ @registry.register(name, validator_class)
154
+ end
155
+
156
+ # Checks if validation passed (no errors)
157
+ #
158
+ # @param validators [Array<Symbol>, nil] List of validators to run
159
+ # @return [Boolean]
160
+ def valid?(validators: nil)
161
+ result = validate(validators: validators)
162
+ !result.has_errors?
163
+ end
164
+
165
+ # Builds validation context from document and database
166
+ #
167
+ # @return [Hash]
168
+ def build_context
169
+ @context_cache ||= build_context_internal
170
+ end
171
+
172
+ private
173
+
174
+ def build_context_internal # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
175
+ context = {
176
+ document: @document,
177
+ database: @database,
178
+ options: @options,
179
+ }
180
+
181
+ # Extract entities from transformed document (preferred source)
182
+ if @document
183
+ context[:classes] = extract_all_from_document(@document, :classes)
184
+ context[:packages] = extract_all_packages(@document)
185
+ context[:enumerations] = extract_all_from_document(@document, :enums)
186
+ context[:data_types] = extract_all_from_document(@document, :data_types)
187
+ context[:associations] = @document.associations || []
188
+ end
189
+
190
+ # Add database entities for referential integrity checks only
191
+ # Note: Use document entities for primary validation
192
+ if @database
193
+ context[:db_packages] = @database.packages || []
194
+ context[:db_objects] = @database.objects.all
195
+ context[:attributes] = @database.attributes || []
196
+ context[:operations] = @database.operations || []
197
+ context[:connectors] = @database.connectors || []
198
+ context[:diagrams] = @database.diagrams || []
199
+ context[:diagram_objects] = @database.diagram_objects || []
200
+ context[:diagram_links] = @database.diagram_links || []
201
+ end
202
+
203
+ context
204
+ end
205
+
206
+ #
207
+ # @return [void]
208
+ def setup_default_validators # rubocop:disable Metrics/MethodLength
209
+ # Phase 1: QEA Database Integrity Validators
210
+ @registry.register(:referential_integrity,
211
+ ReferentialIntegrityValidator)
212
+ @registry.register(:orphan, OrphanValidator)
213
+ @registry.register(:circular_reference,
214
+ CircularReferenceValidator)
215
+ @registry.register(:package, PackageValidator)
216
+
217
+ # Phase 2: UML Tree Structure Validators
218
+ # DocumentStructureValidator lives in lutaml-uml and may not be
219
+ # available (or may reference a different BaseValidator namespace).
220
+ # Register it only if it can be loaded.
221
+ begin
222
+ @registry.register(:document_structure,
223
+ Lutaml::Uml::Validation::DocumentStructureValidator)
224
+ rescue NameError
225
+ # lutaml-uml's DocumentStructureValidator not available — skip
226
+ end
227
+ @registry.register(:class, ClassValidator)
228
+ @registry.register(:attribute, AttributeValidator)
229
+ @registry.register(:operation, OperationValidator)
230
+ @registry.register(:association, AssociationValidator)
231
+ @registry.register(:diagram, DiagramValidator)
232
+ end
233
+
234
+ # Extract all elements of a given type from document hierarchy
235
+ # @param document [Lutaml::Uml::Document]
236
+ # @param collection_method [Symbol] Method name on Package/Document
237
+ # @return [Array<Object>]
238
+ def extract_all_from_document(document, collection_method)
239
+ items = (document.public_send(collection_method) || []).dup
240
+ (document.packages || []).each do |package|
241
+ concat_from_package_tree(items, package, collection_method)
242
+ end
243
+ items
244
+ end
245
+
246
+ # Recursively collect items from package tree
247
+ # @param items [Array] Accumulator
248
+ # @param package [Lutaml::Uml::Package]
249
+ # @param collection_method [Symbol] Method name on Package
250
+ def concat_from_package_tree(items, package, collection_method)
251
+ items.concat(package.public_send(collection_method) || [])
252
+ (package.packages || []).each do |child|
253
+ concat_from_package_tree(items, child, collection_method)
254
+ end
255
+ end
256
+
257
+ # Extract all packages from document hierarchy (includes packages
258
+ # themselves, not just their contents)
259
+ # @param document [Lutaml::Uml::Document]
260
+ # @return [Array<Lutaml::Uml::Package>]
261
+ def extract_all_packages(document)
262
+ packages = []
263
+ (document.packages || []).each { |pkg| collect_packages(packages, pkg) }
264
+ packages
265
+ end
266
+
267
+ def collect_packages(result, package)
268
+ result << package
269
+ (package.packages || []).each { |child| collect_packages(result, child) }
270
+ end
271
+
272
+ # Filters result based on options
273
+ #
274
+ # @param result [ValidationResult]
275
+ # @return [ValidationResult]
276
+ def filter_result(result)
277
+ return result unless @options[:min_severity] ||
278
+ @options[:categories]
279
+
280
+ filtered_result = ValidationResult.new
281
+
282
+ result.messages.each do |message|
283
+ next if severity_filtered?(message)
284
+ next if category_filtered?(message)
285
+
286
+ filtered_result.messages << message
287
+ end
288
+
289
+ filtered_result
290
+ end
291
+
292
+ # Checks if message should be filtered by severity
293
+ #
294
+ # @param message [ValidationMessage]
295
+ # @return [Boolean]
296
+ def severity_filtered?(message)
297
+ return false unless @options[:min_severity]
298
+
299
+ severity_levels = {
300
+ info: 0,
301
+ warning: 1,
302
+ error: 2,
303
+ }
304
+
305
+ min_level = severity_levels[@options[:min_severity]] || 0
306
+ message_level = severity_levels[message.severity] || 0
307
+
308
+ message_level < min_level
309
+ end
310
+
311
+ # Checks if message should be filtered by category
312
+ #
313
+ # @param message [ValidationMessage]
314
+ # @return [Boolean]
315
+ def category_filtered?(message)
316
+ return false unless @options[:categories]
317
+
318
+ !@options[:categories].include?(message.category)
319
+ end
320
+
321
+ # Displays validation result
322
+ #
323
+ # @param result [ValidationResult]
324
+ # @param formatter [Symbol] Output format
325
+ # @return [void]
326
+ def display_result(result, formatter)
327
+ case formatter
328
+ when :json
329
+ puts result.to_json
330
+ else
331
+ display_text_result(result)
332
+ end
333
+ end
334
+
335
+ # Displays result in text format
336
+ #
337
+ # @param result [ValidationResult]
338
+ # @return [void]
339
+ def display_text_result(result) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
340
+ puts "=" * 80
341
+ puts "VALIDATION REPORT"
342
+ puts "=" * 80
343
+ puts
344
+ puts result.summary
345
+ puts
346
+
347
+ if result.has_errors?
348
+ puts "ERRORS (#{result.errors.size}):"
349
+ puts
350
+ display_messages_by_category(result.errors)
351
+ end
352
+
353
+ if result.has_warnings?
354
+ puts
355
+ puts "WARNINGS (#{result.warnings.size}):"
356
+ puts
357
+ display_messages_by_category(result.warnings)
358
+ end
359
+
360
+ if result.has_info? && @options[:verbose]
361
+ puts
362
+ puts "INFO (#{result.info.size}):"
363
+ puts
364
+ display_messages_by_category(result.info)
365
+ end
366
+
367
+ puts "=" * 80
368
+ end
369
+
370
+ # Displays messages grouped by category
371
+ #
372
+ # @param messages [Array<ValidationMessage>]
373
+ # @return [void]
374
+ def display_messages_by_category(messages)
375
+ messages.group_by(&:category).each do |category, msgs|
376
+ puts " #{category.to_s.split('_').map(&:capitalize).join(' ')} " \
377
+ "(#{msgs.size}):"
378
+ msgs.each do |msg|
379
+ puts " #{msg}"
380
+ puts
381
+ end
382
+ end
383
+ end
384
+ end
385
+ end
386
+ end
387
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Validation
6
+ # Represents a single validation message with severity, category, and
7
+ # context information
8
+ #
9
+ # @example Creating an error message
10
+ # message = ValidationMessage.new(
11
+ # severity: :error,
12
+ # category: :missing_reference,
13
+ # entity_type: :association,
14
+ # entity_id: "FA86EB3B-198A-4141-83F6-DE9FACC76425",
15
+ # entity_name: "Association_1",
16
+ # field: "member_end",
17
+ # reference: "GPLR_Compression",
18
+ # message: "member_end references non-existent class",
19
+ # location: "Package::SubPackage"
20
+ # )
21
+ class ValidationMessage
22
+ # Severity levels for validation messages
23
+ module Severity
24
+ ERROR = :error # Breaks integrity, must fix
25
+ WARNING = :warning # May cause issues, should review
26
+ INFO = :info # Informational, may be intentional
27
+ end
28
+
29
+ # Categories of validation issues
30
+ module Category
31
+ MISSING_REFERENCE = :missing_reference
32
+ ORPHANED = :orphaned
33
+ DUPLICATE = :duplicate
34
+ INVALID_TYPE = :invalid_type
35
+ CIRCULAR_REFERENCE = :circular_reference
36
+ MISSING_REQUIRED = :missing_required
37
+ CONSTRAINT_VIOLATION = :constraint_violation
38
+ end
39
+
40
+ attr_reader :severity, :category, :entity_type, :entity_id,
41
+ :entity_name, :field, :reference, :message, :location,
42
+ :context
43
+
44
+ # Creates a new validation message
45
+ #
46
+ # @param severity [Symbol] Message severity (:error, :warning, :info)
47
+ # @param category [Symbol] Category of the issue
48
+ # @param entity_type [Symbol] Type of entity (e.g., :class,
49
+ # :association)
50
+ # @param entity_id [String] XMI ID or database ID of the entity
51
+ # @param entity_name [String] Human-readable name of the entity
52
+ # @param field [String, nil] Field with the issue
53
+ # @param reference [String, nil] What it's trying to reference
54
+ # @param message [String] Human-readable description
55
+ # @param location [String, nil] Package path or context
56
+ # @param context [Hash] Additional context information
57
+ def initialize( # rubocop:disable Metrics/ParameterLists
58
+ severity:,
59
+ category:,
60
+ entity_type:,
61
+ entity_id:,
62
+ entity_name:,
63
+ message:,
64
+ field: nil,
65
+ reference: nil,
66
+ location: nil,
67
+ context: {}
68
+ )
69
+ @severity = severity
70
+ @category = category
71
+ @entity_type = entity_type
72
+ @entity_id = entity_id
73
+ @entity_name = entity_name
74
+ @field = field
75
+ @reference = reference
76
+ @message = message
77
+ @location = location
78
+ @context = context
79
+ end
80
+
81
+ # Checks if this is an error message
82
+ #
83
+ # @return [Boolean]
84
+ def error?
85
+ severity == Severity::ERROR
86
+ end
87
+
88
+ # Checks if this is a warning message
89
+ #
90
+ # @return [Boolean]
91
+ def warning?
92
+ severity == Severity::WARNING
93
+ end
94
+
95
+ # Checks if this is an info message
96
+ #
97
+ # @return [Boolean]
98
+ def info?
99
+ severity == Severity::INFO
100
+ end
101
+
102
+ # Returns a formatted string representation of the message
103
+ #
104
+ # @return [String]
105
+ def to_s # rubocop:disable Metrics/AbcSize
106
+ parts = []
107
+ parts << "#{entity_type.to_s.capitalize} '#{entity_name}'"
108
+ parts << "{#{entity_id}}"
109
+ parts << "└─ #{message}"
110
+ parts << "└─ Field: #{field}" if field
111
+ parts << "└─ Reference: #{reference}" if reference
112
+ parts << "└─ Location: #{location}" if location
113
+ parts.join("\n")
114
+ end
115
+
116
+ # Returns a hash representation of the message
117
+ #
118
+ # @return [Hash]
119
+ def to_h # rubocop:disable Metrics/MethodLength
120
+ {
121
+ severity: severity,
122
+ category: category,
123
+ entity_type: entity_type,
124
+ entity_id: entity_id,
125
+ entity_name: entity_name,
126
+ field: field,
127
+ reference: reference,
128
+ message: message,
129
+ location: location,
130
+ context: context,
131
+ }.compact
132
+ end
133
+
134
+ # Returns a JSON representation of the message
135
+ #
136
+ # @return [String]
137
+ def to_json(*)
138
+ require "json"
139
+ to_h.to_json(*)
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end