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,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Validation
6
+ # Detects orphaned entities (invalid foreign keys)
7
+ class OrphanValidator < BaseValidator
8
+ def validate
9
+ find_orphaned_objects
10
+ find_orphaned_attributes
11
+ find_orphaned_operations
12
+ find_unreferenced_objects
13
+ end
14
+
15
+ private
16
+
17
+ def find_orphaned_objects # rubocop:disable Metrics/MethodLength
18
+ objects.each do |obj|
19
+ next unless obj.package_id
20
+ next if package_exists?(obj.package_id)
21
+
22
+ result.add_error(
23
+ category: :orphaned,
24
+ entity_type: :class,
25
+ entity_id: obj.ea_object_id.to_s,
26
+ entity_name: obj.name,
27
+ field: "package_id",
28
+ reference: obj.package_id.to_s,
29
+ message: "Orphaned object: package #{obj.package_id} " \
30
+ "does not exist",
31
+ )
32
+ end
33
+ end
34
+
35
+ def find_orphaned_attributes # rubocop:disable Metrics/MethodLength
36
+ attributes.each do |attr|
37
+ next if object_exists?(attr.ea_object_id)
38
+
39
+ result.add_error(
40
+ category: :orphaned,
41
+ entity_type: :attribute,
42
+ entity_id: attr.id.to_s,
43
+ entity_name: attr.name,
44
+ field: "object_id",
45
+ reference: attr.ea_object_id.to_s,
46
+ message: "Orphaned attribute: parent object " \
47
+ "#{attr.ea_object_id} does not exist",
48
+ )
49
+ end
50
+ end
51
+
52
+ def find_orphaned_operations # rubocop:disable Metrics/MethodLength
53
+ operations.each do |op|
54
+ next if object_exists?(op.ea_object_id)
55
+
56
+ result.add_error(
57
+ category: :orphaned,
58
+ entity_type: :operation,
59
+ entity_id: op.operationid.to_s,
60
+ entity_name: op.name,
61
+ field: "object_id",
62
+ reference: op.ea_object_id.to_s,
63
+ message: "Orphaned operation: parent object " \
64
+ "#{op.ea_object_id} does not exist",
65
+ )
66
+ end
67
+ end
68
+
69
+ def find_unreferenced_objects # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
70
+ # Find objects that are not referenced by any connector,
71
+ # attribute, or diagram
72
+ object_ids = objects.to_set(&:ea_object_id)
73
+
74
+ referenced_ids = Set.new
75
+
76
+ # Objects referenced by connectors
77
+ connectors.each do |conn|
78
+ referenced_ids << conn.start_object_id
79
+ referenced_ids << conn.end_object_id
80
+ end
81
+
82
+ # Objects referenced by attributes (as classifiers)
83
+ attributes.each do |attr|
84
+ referenced_ids << attr.ea_object_id
85
+ if attr.classifier&.to_i&.positive?
86
+ referenced_ids << attr.classifier.to_i
87
+ end
88
+ end
89
+
90
+ # Objects referenced by operations
91
+ operations.each do |op|
92
+ referenced_ids << op.ea_object_id
93
+ end
94
+
95
+ # Objects referenced by diagrams
96
+ diagram_objects.each do |diag_obj|
97
+ referenced_ids << diag_obj.ea_object_id
98
+ end
99
+
100
+ # Unreferenced objects (except root packages which
101
+ # may be unreferenced)
102
+ unreferenced = object_ids - referenced_ids
103
+ unreferenced.each do |obj_id|
104
+ obj = objects.find { |o| o.ea_object_id == obj_id }
105
+ next unless obj
106
+ # Skip DataType and Enumeration as they may not be referenced
107
+ next if obj.data_type? || obj.enumeration?
108
+
109
+ result.add_info(
110
+ category: :unreferenced,
111
+ entity_type: :class,
112
+ entity_id: obj_id.to_s,
113
+ entity_name: obj.name,
114
+ message: "Unreferenced object (not used in any relationship)",
115
+ )
116
+ end
117
+ end
118
+
119
+ def packages
120
+ @packages ||= context[:db_packages] || []
121
+ end
122
+
123
+ def objects
124
+ @objects ||= context[:db_objects] || []
125
+ end
126
+
127
+ def attributes
128
+ @attributes ||= context[:attributes] || []
129
+ end
130
+
131
+ def operations
132
+ @operations ||= context[:operations] || []
133
+ end
134
+
135
+ def connectors
136
+ @connectors ||= context[:connectors] || []
137
+ end
138
+
139
+ def diagram_objects
140
+ @diagram_objects ||= context[:diagram_objects] || []
141
+ end
142
+
143
+ def package_exists?(package_id)
144
+ packages.any? { |p| p.package_id == package_id }
145
+ end
146
+
147
+ def object_exists?(object_id)
148
+ objects.any? { |o| o.ea_object_id == object_id }
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Validation
6
+ # Validates referential integrity across all entities
7
+ class ReferentialIntegrityValidator < BaseValidator
8
+ def validate
9
+ validate_all_package_references
10
+ validate_all_object_references
11
+ validate_all_connector_references
12
+ end
13
+
14
+ private
15
+
16
+ def validate_all_package_references # rubocop:disable Metrics/MethodLength
17
+ # Check all parent_id references in packages
18
+ packages.each do |pkg|
19
+ next if pkg.root?
20
+
21
+ unless package_exists?(pkg.parent_id)
22
+ result.add_error(
23
+ category: :referential_integrity,
24
+ entity_type: :package,
25
+ entity_id: pkg.package_id.to_s,
26
+ entity_name: pkg.name,
27
+ field: "parent_id",
28
+ reference: pkg.parent_id.to_s,
29
+ message: "Foreign key violation: parent_id #{pkg.parent_id} " \
30
+ "not found in t_package",
31
+ )
32
+ end
33
+ end
34
+ end
35
+
36
+ def validate_all_object_references # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
37
+ # Check all package_id references in objects
38
+ objects.each do |obj| # rubocop:disable Metrics/BlockLength
39
+ next unless obj.package_id
40
+
41
+ unless package_exists?(obj.package_id)
42
+ result.add_error(
43
+ category: :referential_integrity,
44
+ entity_type: :class,
45
+ entity_id: obj.ea_object_id.to_s,
46
+ entity_name: obj.name,
47
+ field: "package_id",
48
+ reference: obj.package_id.to_s,
49
+ message: "Foreign key violation: package_id " \
50
+ "#{obj.package_id} not found in t_package",
51
+ )
52
+ end
53
+
54
+ # Check classifier references
55
+ next unless obj.classifier&.positive?
56
+
57
+ unless object_exists?(obj.classifier)
58
+ result.add_warning(
59
+ category: :referential_integrity,
60
+ entity_type: :class,
61
+ entity_id: obj.ea_object_id.to_s,
62
+ entity_name: obj.name,
63
+ field: "classifier",
64
+ reference: obj.classifier.to_s,
65
+ message: "Classifier object #{obj.classifier} not found",
66
+ )
67
+ end
68
+ end
69
+ end
70
+
71
+ def validate_all_connector_references # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
72
+ # Check all object references in connectors
73
+ connectors.each do |conn|
74
+ unless object_exists?(conn.start_object_id)
75
+ result.add_error(
76
+ category: :referential_integrity,
77
+ entity_type: :connector,
78
+ entity_id: conn.connector_id.to_s,
79
+ entity_name: conn.name || "Unnamed",
80
+ field: "start_object_id",
81
+ reference: conn.start_object_id.to_s,
82
+ message: "Foreign key violation: start_object_id " \
83
+ "#{conn.start_object_id} not found in t_object",
84
+ )
85
+ end
86
+
87
+ unless object_exists?(conn.end_object_id)
88
+ result.add_error(
89
+ category: :referential_integrity,
90
+ entity_type: :connector,
91
+ entity_id: conn.connector_id.to_s,
92
+ entity_name: conn.name || "Unnamed",
93
+ field: "end_object_id",
94
+ reference: conn.end_object_id.to_s,
95
+ message: "Foreign key violation: end_object_id " \
96
+ "#{conn.end_object_id} not found in t_object",
97
+ )
98
+ end
99
+ end
100
+ end
101
+
102
+ def packages
103
+ @packages ||= context[:db_packages] || []
104
+ end
105
+
106
+ def objects
107
+ @objects ||= context[:db_objects] || []
108
+ end
109
+
110
+ def connectors
111
+ @connectors ||= context[:connectors] || []
112
+ end
113
+
114
+ def package_exists?(package_id)
115
+ return false unless package_id
116
+
117
+ packages.any? { |p| p.package_id == package_id }
118
+ end
119
+
120
+ def object_exists?(object_id)
121
+ return false unless object_id
122
+
123
+ objects.any? { |o| o.ea_object_id == object_id }
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Validation
6
+ module Database
7
+ autoload :CircularReferenceValidator,
8
+ "ea/qea/validation/database/circular_reference_validator"
9
+ autoload :OrphanValidator,
10
+ "ea/qea/validation/database/orphan_validator"
11
+ autoload :ReferentialIntegrityValidator,
12
+ "ea/qea/validation/database/referential_integrity_validator"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ea
4
+ module Qea
5
+ module Validation
6
+ # Validates diagram references and structure
7
+ class DiagramValidator < BaseValidator
8
+ def validate
9
+ validate_package_references
10
+ validate_diagram_objects
11
+ validate_diagram_links
12
+ end
13
+
14
+ private
15
+
16
+ def validate_package_references # rubocop:disable Metrics/MethodLength
17
+ diagrams.each do |diagram|
18
+ next unless diagram.package_id
19
+
20
+ unless package_exists?(diagram.package_id)
21
+ result.add_error(
22
+ category: :missing_reference,
23
+ entity_type: :diagram,
24
+ entity_id: diagram.diagram_id.to_s,
25
+ entity_name: diagram.name,
26
+ field: "package_id",
27
+ reference: diagram.package_id.to_s,
28
+ message: "Package #{diagram.package_id} does not exist",
29
+ )
30
+ end
31
+ end
32
+ end
33
+
34
+ def validate_diagram_objects # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
35
+ diagram_objects.each do |diag_obj|
36
+ unless object_exists?(diag_obj.ea_object_id)
37
+ diagram = diagrams.find do |d|
38
+ d.diagram_id == diag_obj.diagram_id
39
+ end
40
+ result.add_warning(
41
+ category: :missing_reference,
42
+ entity_type: :diagram_object,
43
+ entity_id: diag_obj.instance_id.to_s,
44
+ entity_name: diagram&.name || "Unknown diagram",
45
+ field: "object_id",
46
+ reference: diag_obj.ea_object_id.to_s,
47
+ message: "Diagram object references non-existent " \
48
+ "object #{diag_obj.ea_object_id}",
49
+ )
50
+ end
51
+ end
52
+ end
53
+
54
+ def validate_diagram_links # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
55
+ diagram_links.each do |diag_link|
56
+ unless connector_exists?(diag_link.connectorid)
57
+ diagram = diagrams.find do |d|
58
+ d.diagram_id == diag_link.diagramid
59
+ end
60
+ result.add_warning(
61
+ category: :missing_reference,
62
+ entity_type: :diagram_link,
63
+ entity_id: diag_link.instance_id.to_s,
64
+ entity_name: diagram&.name || "Unknown diagram",
65
+ field: "connectorid",
66
+ reference: diag_link.connectorid.to_s,
67
+ message: "Diagram link references non-existent " \
68
+ "connector #{diag_link.connectorid}",
69
+ )
70
+ end
71
+ end
72
+ end
73
+
74
+ def diagrams
75
+ @diagrams ||= context[:diagrams] || []
76
+ end
77
+
78
+ def diagram_objects
79
+ @diagram_objects ||= context[:diagram_objects] || []
80
+ end
81
+
82
+ def diagram_links
83
+ @diagram_links ||= context[:diagram_links] || []
84
+ end
85
+
86
+ def packages
87
+ @packages ||= context[:db_packages] || []
88
+ end
89
+
90
+ def objects
91
+ @objects ||= context[:db_objects] || []
92
+ end
93
+
94
+ def connectors
95
+ @connectors ||= context[:connectors] || []
96
+ end
97
+
98
+ def package_exists?(package_id)
99
+ packages.any? { |p| p.package_id == package_id }
100
+ end
101
+
102
+ def object_exists?(object_id)
103
+ objects.any? { |o| o.ea_object_id == object_id }
104
+ end
105
+
106
+ def connector_exists?(connector_id)
107
+ connectors.any? { |c| c.connector_id == connector_id }
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Ea
6
+ module Qea
7
+ module Validation
8
+ module Formatters
9
+ # Formats validation results as JSON for machine consumption
10
+ #
11
+ # @example Basic usage
12
+ # formatter = JsonFormatter.new(result)
13
+ # puts formatter.format
14
+ #
15
+ # @example Pretty printed
16
+ # formatter = JsonFormatter.new(result, pretty: true)
17
+ # puts formatter.format
18
+ class JsonFormatter
19
+ attr_reader :result, :options
20
+
21
+ # Creates a new JSON formatter
22
+ #
23
+ # @param result [ValidationResult] The validation result to format
24
+ # @param options [Hash] Formatting options
25
+ # @option options [Boolean] :pretty Pretty print JSON (default: false)
26
+ def initialize(result: nil, **options)
27
+ @result = result
28
+ @options = {
29
+ pretty: false,
30
+ }.merge(options)
31
+ end
32
+
33
+ # Formats the validation result as JSON
34
+ #
35
+ # @return [String] JSON output
36
+ def format
37
+ data = build_data
38
+
39
+ if options[:pretty]
40
+ JSON.pretty_generate(data)
41
+ else
42
+ JSON.generate(data)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ # Builds the data structure for JSON output
49
+ #
50
+ # @return [Hash]
51
+ def build_data
52
+ {
53
+ summary: build_summary,
54
+ messages: build_messages,
55
+ by_category: build_by_category,
56
+ by_severity: build_by_severity,
57
+ }
58
+ end
59
+
60
+ # Builds the summary section
61
+ #
62
+ # @return [Hash]
63
+ def build_summary
64
+ {
65
+ valid: result.valid?,
66
+ total_messages: result.messages.size,
67
+ error_count: result.errors.size,
68
+ warning_count: result.warnings.size,
69
+ info_count: result.info.size,
70
+ }
71
+ end
72
+
73
+ # Builds the messages array
74
+ #
75
+ # @return [Array<Hash>]
76
+ def build_messages # rubocop:disable Metrics/MethodLength
77
+ result.messages.map do |message|
78
+ {
79
+ severity: message.severity,
80
+ category: message.category,
81
+ entity_type: message.entity_type,
82
+ entity_id: message.entity_id,
83
+ entity_name: message.entity_name,
84
+ message: message.message,
85
+ context: message.context,
86
+ }
87
+ end
88
+ end
89
+
90
+ # Builds messages grouped by category
91
+ #
92
+ # @return [Hash]
93
+ def build_by_category
94
+ result.messages.group_by(&:category).transform_values do |msgs|
95
+ {
96
+ count: msgs.size,
97
+ messages: msgs.map(&:message),
98
+ }
99
+ end
100
+ end
101
+
102
+ # Builds messages grouped by severity
103
+ #
104
+ # @return [Hash]
105
+ def build_by_severity
106
+ {
107
+ errors: format_severity_group(result.errors),
108
+ warnings: format_severity_group(result.warnings),
109
+ info: format_severity_group(result.info),
110
+ }
111
+ end
112
+
113
+ # Formats a group of messages by severity
114
+ #
115
+ # @param messages [Array<ValidationMessage>]
116
+ # @return [Hash]
117
+ def format_severity_group(messages) # rubocop:disable Metrics/MethodLength
118
+ {
119
+ count: messages.size,
120
+ by_category: messages
121
+ .group_by(&:category).transform_values do |msgs|
122
+ msgs.map do |msg|
123
+ {
124
+ entity_type: msg.entity_type,
125
+ entity_id: msg.entity_id,
126
+ entity_name: msg.entity_name,
127
+ message: msg.message,
128
+ }
129
+ end
130
+ end,
131
+ }
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end