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.
- checksums.yaml +4 -4
- data/CLAUDE.md +125 -0
- data/Rakefile +12 -4
- data/TODO.next/00-publish-blocking-bugs.md +74 -0
- data/TODO.next/01-standalone-ea-gem-identity.md +76 -0
- data/TODO.next/02-optional-lutaml-uml-dependency.md +47 -0
- data/TODO.next/03-slim-lutaml-uml.md +79 -0
- data/TODO.next/04-loader-registry-for-uml-repository.md +49 -0
- data/TODO.next/05-extract-shared-transformer-methods.md +14 -0
- data/TODO.next/06-deduplicate-stereotype-loading.md +17 -0
- data/TODO.next/07-transformer-registry-in-factory.md +20 -0
- data/TODO.next/08-connector-type-registry.md +27 -0
- data/TODO.next/09-element-renderer-registry.md +29 -0
- data/TODO.next/10-connector-renderer-lsp.md +18 -0
- data/TODO.next/11-consolidate-style-knowledge.md +33 -0
- data/TODO.next/12-data-driven-from-db-row.md +24 -0
- data/TODO.next/13-extract-duplicated-methods.md +17 -0
- data/TODO.next/14-remove-dead-code.md +10 -0
- data/TODO.next/15-narrow-exception-handling.md +39 -0
- data/TODO.next/16-repository-indexes.md +28 -0
- data/TODO.next/17-fix-spec-quality-and-coverage.md +32 -0
- data/TODO.next/18-xmi-tool-specific-parser-architecture.md +172 -0
- data/TODO.next/19-fix-ea-gemspec-dependency-declarations.md +56 -0
- data/TODO.next/20-ci-requires-unreleased-lutaml-uml.md +63 -0
- data/TODO.next/21-qeatoxmi-via-xmi-gem.md +340 -0
- data/TODO.next/22-strip-respond-to-from-qeatoxmi-specs.md +32 -0
- data/TODO.next/23-cleanup-idallocator.md +41 -0
- data/TODO.next/24-tighten-parity-specs.md +42 -0
- data/TODO.next/25-sparx-eaid-format-for-synthesized-ids.md +62 -0
- data/TODO.next/26-fix-uppervalue-lowervalue-count-gap.md +51 -0
- data/TODO.next/27-extract-cardinality-module.md +68 -0
- data/TODO.next/28-extract-xml-sanitizer.md +51 -0
- data/TODO.next/29-ocp-registry-for-classifier-builders.md +58 -0
- data/TODO.next/30-struct-return-for-association-end.md +37 -0
- data/TODO.next/31-idallocator-specs.md +27 -0
- data/TODO.next/32-phase2-gap-sentinel-specs.md +53 -0
- data/TODO.next/33-normalize-lower-cleanup.md +30 -0
- data/TODO.next/34-document-member-end-order-rt-prefix.md +29 -0
- data/TODO.next/35-walk-runstate-for-instance-slots.md +76 -0
- data/TODO.next/36-wire-interface-realization.md +50 -0
- data/TODO.next/37-visibility-returns-real-booleans.md +36 -0
- data/config/diagram_styles.yml +200 -0
- data/config/model_transformations.yml +266 -0
- data/config/qea_schema.yml +1024 -0
- data/docs/ea_to_uml_type_mapping.md +89 -0
- data/docs/xmi_qea_conversion_capabilities.md +99 -0
- data/examples/lur/20251010_current_plateau_v5.1.lur +0 -0
- data/examples/lur/basic.lur +0 -0
- data/examples/lur/test-output.lur +0 -0
- data/examples/lur/test.lur +0 -0
- data/examples/lur_basic_usage.rb +221 -0
- data/examples/lur_cli_workflow.rb +263 -0
- data/examples/lur_statistics.rb +326 -0
- data/examples/qea/20251010_current_plateau_v5.1.qea +0 -0
- data/examples/qea/ArcGISWorkspace_template.qea +0 -0
- data/examples/qea/README_qea_parser.adoc +230 -0
- data/examples/qea/UmlModel_template.qea +0 -0
- data/examples/qea/basic.qea +0 -0
- data/examples/qea/simple.qea +0 -0
- data/examples/qea/simple_example.qea +0 -0
- data/examples/qea/test.qea +0 -0
- data/examples/qea_standalone_query.rb +73 -0
- data/examples/qea_to_repository.rb +51 -0
- data/examples/smoke_test_real_qea.rb +81 -0
- data/exe/ea +7 -0
- data/lib/ea/cli/app.rb +72 -0
- data/lib/ea/cli/command/base.rb +80 -0
- data/lib/ea/cli/command/convert.rb +62 -0
- data/lib/ea/cli/command/diagrams.rb +81 -0
- data/lib/ea/cli/command/list.rb +61 -0
- data/lib/ea/cli/command/parse.rb +29 -0
- data/lib/ea/cli/command/stats.rb +20 -0
- data/lib/ea/cli/command/validate.rb +41 -0
- data/lib/ea/cli/command.rb +15 -0
- data/lib/ea/cli/error.rb +34 -0
- data/lib/ea/cli/output/formatter.rb +34 -0
- data/lib/ea/cli/output/json_formatter.rb +20 -0
- data/lib/ea/cli/output/table_formatter.rb +42 -0
- data/lib/ea/cli/output/yaml_formatter.rb +20 -0
- data/lib/ea/cli/output.rb +56 -0
- data/lib/ea/cli.rb +17 -0
- data/lib/ea/diagram/configuration.rb +379 -0
- data/lib/ea/diagram/element_renderers/base_renderer.rb +77 -0
- data/lib/ea/diagram/element_renderers/class_renderer.rb +323 -0
- data/lib/ea/diagram/element_renderers/connector_renderer.rb +41 -0
- data/lib/ea/diagram/element_renderers/package_renderer.rb +61 -0
- data/lib/ea/diagram/element_renderers.rb +43 -0
- data/lib/ea/diagram/extractor.rb +560 -0
- data/lib/ea/diagram/layout_engine.rb +170 -0
- data/lib/ea/diagram/path_builder.rb +202 -0
- data/lib/ea/diagram/style_parser.rb +42 -0
- data/lib/ea/diagram/style_resolver.rb +276 -0
- data/lib/ea/diagram/svg_renderer.rb +274 -0
- data/lib/ea/diagram/util.rb +73 -0
- data/lib/ea/diagram.rb +47 -0
- data/lib/ea/qea/benchmark.rb +210 -0
- data/lib/ea/qea/database.rb +308 -0
- data/lib/ea/qea/factory/association_builder.rb +203 -0
- data/lib/ea/qea/factory/association_transformer.rb +91 -0
- data/lib/ea/qea/factory/attribute_tag_transformer.rb +57 -0
- data/lib/ea/qea/factory/attribute_transformer.rb +93 -0
- data/lib/ea/qea/factory/base_transformer.rb +177 -0
- data/lib/ea/qea/factory/class_transformer.rb +116 -0
- data/lib/ea/qea/factory/constraint_transformer.rb +75 -0
- data/lib/ea/qea/factory/data_type_transformer.rb +77 -0
- data/lib/ea/qea/factory/diagram_transformer.rb +157 -0
- data/lib/ea/qea/factory/document_builder.rb +283 -0
- data/lib/ea/qea/factory/ea_to_uml_factory.rb +229 -0
- data/lib/ea/qea/factory/enum_transformer.rb +74 -0
- data/lib/ea/qea/factory/generalization_builder.rb +227 -0
- data/lib/ea/qea/factory/generalization_transformer.rb +98 -0
- data/lib/ea/qea/factory/instance_transformer.rb +68 -0
- data/lib/ea/qea/factory/object_property_transformer.rb +58 -0
- data/lib/ea/qea/factory/operation_transformer.rb +66 -0
- data/lib/ea/qea/factory/package_transformer.rb +145 -0
- data/lib/ea/qea/factory/reference_resolver.rb +99 -0
- data/lib/ea/qea/factory/stereotype_loader.rb +39 -0
- data/lib/ea/qea/factory/tagged_value_transformer.rb +38 -0
- data/lib/ea/qea/factory/transformer_registry.rb +80 -0
- data/lib/ea/qea/factory.rb +37 -0
- data/lib/ea/qea/file_detector.rb +178 -0
- data/lib/ea/qea/infrastructure/database_connection.rb +100 -0
- data/lib/ea/qea/infrastructure/schema_reader.rb +136 -0
- data/lib/ea/qea/infrastructure/table_reader.rb +224 -0
- data/lib/ea/qea/infrastructure.rb +12 -0
- data/lib/ea/qea/models/base_model.rb +59 -0
- data/lib/ea/qea/models/ea_attribute.rb +109 -0
- data/lib/ea/qea/models/ea_attribute_tag.rb +100 -0
- data/lib/ea/qea/models/ea_complexity_type.rb +79 -0
- data/lib/ea/qea/models/ea_connector.rb +160 -0
- data/lib/ea/qea/models/ea_connector_type.rb +60 -0
- data/lib/ea/qea/models/ea_constraint_type.rb +63 -0
- data/lib/ea/qea/models/ea_datatype.rb +104 -0
- data/lib/ea/qea/models/ea_diagram.rb +115 -0
- data/lib/ea/qea/models/ea_diagram_link.rb +78 -0
- data/lib/ea/qea/models/ea_diagram_object.rb +73 -0
- data/lib/ea/qea/models/ea_diagram_type.rb +56 -0
- data/lib/ea/qea/models/ea_document.rb +63 -0
- data/lib/ea/qea/models/ea_object.rb +223 -0
- data/lib/ea/qea/models/ea_object_constraint.rb +53 -0
- data/lib/ea/qea/models/ea_object_property.rb +87 -0
- data/lib/ea/qea/models/ea_object_type.rb +73 -0
- data/lib/ea/qea/models/ea_operation.rb +127 -0
- data/lib/ea/qea/models/ea_operation_param.rb +76 -0
- data/lib/ea/qea/models/ea_package.rb +78 -0
- data/lib/ea/qea/models/ea_script.rb +62 -0
- data/lib/ea/qea/models/ea_status_type.rb +66 -0
- data/lib/ea/qea/models/ea_stereotype.rb +57 -0
- data/lib/ea/qea/models/ea_tagged_value.rb +99 -0
- data/lib/ea/qea/models/ea_xref.rb +165 -0
- data/lib/ea/qea/models.rb +35 -0
- data/lib/ea/qea/repositories/base_repository.rb +225 -0
- data/lib/ea/qea/repositories/object_repository.rb +219 -0
- data/lib/ea/qea/repositories.rb +10 -0
- data/lib/ea/qea/services/configuration.rb +211 -0
- data/lib/ea/qea/services/database_loader.rb +191 -0
- data/lib/ea/qea/services.rb +10 -0
- data/lib/ea/qea/validation/association_validator.rb +73 -0
- data/lib/ea/qea/validation/attribute_validator.rb +91 -0
- data/lib/ea/qea/validation/base_validator.rb +331 -0
- data/lib/ea/qea/validation/class_validator.rb +121 -0
- data/lib/ea/qea/validation/database/circular_reference_validator.rb +109 -0
- data/lib/ea/qea/validation/database/orphan_validator.rb +153 -0
- data/lib/ea/qea/validation/database/referential_integrity_validator.rb +128 -0
- data/lib/ea/qea/validation/database.rb +16 -0
- data/lib/ea/qea/validation/diagram_validator.rb +112 -0
- data/lib/ea/qea/validation/formatters/json_formatter.rb +137 -0
- data/lib/ea/qea/validation/formatters/text_formatter.rb +235 -0
- data/lib/ea/qea/validation/formatters.rb +12 -0
- data/lib/ea/qea/validation/operation_validator.rb +71 -0
- data/lib/ea/qea/validation/package_validator.rb +111 -0
- data/lib/ea/qea/validation/validation_engine.rb +387 -0
- data/lib/ea/qea/validation/validation_message.rb +144 -0
- data/lib/ea/qea/validation/validation_result.rb +210 -0
- data/lib/ea/qea/validation/validator_registry.rb +134 -0
- data/lib/ea/qea/validation.rb +28 -0
- data/lib/ea/qea/verification/comparison_result.rb +264 -0
- data/lib/ea/qea/verification/document_normalizer.rb +169 -0
- data/lib/ea/qea/verification/document_verifier.rb +322 -0
- data/lib/ea/qea/verification/element_comparator.rb +277 -0
- data/lib/ea/qea/verification/structure_matcher.rb +287 -0
- data/lib/ea/qea/verification.rb +14 -0
- data/lib/ea/qea.rb +185 -0
- data/lib/ea/transformations/configuration.rb +333 -0
- data/lib/ea/transformations/format_registry.rb +366 -0
- data/lib/ea/transformations/parsers/base_parser.rb +482 -0
- data/lib/ea/transformations/parsers/qea_parser.rb +401 -0
- data/lib/ea/transformations/parsers/xmi_parser.rb +243 -0
- data/lib/ea/transformations/transformation_engine.rb +390 -0
- data/lib/ea/transformations.rb +85 -0
- data/lib/ea/transformers/qea_to_xmi/association_end.rb +19 -0
- data/lib/ea/transformers/qea_to_xmi/cardinality.rb +96 -0
- data/lib/ea/transformers/qea_to_xmi/context.rb +106 -0
- data/lib/ea/transformers/qea_to_xmi/guid_format.rb +56 -0
- data/lib/ea/transformers/qea_to_xmi/id_allocator.rb +92 -0
- data/lib/ea/transformers/qea_to_xmi/run_state.rb +107 -0
- data/lib/ea/transformers/qea_to_xmi/transformer.rb +607 -0
- data/lib/ea/transformers/qea_to_xmi/visibility.rb +73 -0
- data/lib/ea/transformers/qea_to_xmi.rb +29 -0
- data/lib/ea/transformers/uml_to_xmi/id_generator.rb +54 -0
- data/lib/ea/transformers/uml_to_xmi/transformer.rb +152 -0
- data/lib/ea/transformers/uml_to_xmi/writer.rb +96 -0
- data/lib/ea/transformers/uml_to_xmi.rb +16 -0
- data/lib/ea/transformers.rb +34 -0
- data/lib/ea/version.rb +1 -1
- data/lib/ea/xmi/liquid_drops/association_drop.rb +56 -0
- data/lib/ea/xmi/liquid_drops/attribute_drop.rb +72 -0
- data/lib/ea/xmi/liquid_drops/cardinality_drop.rb +35 -0
- data/lib/ea/xmi/liquid_drops/connector_drop.rb +54 -0
- data/lib/ea/xmi/liquid_drops/constraint_drop.rb +29 -0
- data/lib/ea/xmi/liquid_drops/data_type_drop.rb +63 -0
- data/lib/ea/xmi/liquid_drops/dependency_drop.rb +36 -0
- data/lib/ea/xmi/liquid_drops/diagram_drop.rb +34 -0
- data/lib/ea/xmi/liquid_drops/enum_drop.rb +49 -0
- data/lib/ea/xmi/liquid_drops/enum_owned_literal_drop.rb +25 -0
- data/lib/ea/xmi/liquid_drops/generalization_attribute_drop.rb +87 -0
- data/lib/ea/xmi/liquid_drops/generalization_drop.rb +127 -0
- data/lib/ea/xmi/liquid_drops/klass_drop.rb +191 -0
- data/lib/ea/xmi/liquid_drops/operation_drop.rb +29 -0
- data/lib/ea/xmi/liquid_drops/package_drop.rb +108 -0
- data/lib/ea/xmi/liquid_drops/root_drop.rb +34 -0
- data/lib/ea/xmi/liquid_drops/source_target_drop.rb +43 -0
- data/lib/ea/xmi/lookup_service.rb +89 -0
- data/lib/ea/xmi/parser.rb +919 -0
- data/lib/ea/xmi.rb +35 -0
- data/lib/ea.rb +10 -1
- 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
|