expressir 2.1.30 → 2.1.31
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/.github/workflows/docs.yml +98 -0
- data/.github/workflows/links.yml +100 -0
- data/.github/workflows/rake.yml +4 -0
- data/.github/workflows/release.yml +5 -0
- data/.github/workflows/validate_schemas.yml +1 -1
- data/.gitignore +3 -0
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +244 -39
- data/Gemfile +2 -1
- data/README.adoc +621 -54
- data/docs/Gemfile +12 -0
- data/docs/_config.yml +141 -0
- data/docs/_guides/changes/changes-format.adoc +778 -0
- data/docs/_guides/changes/importing-eengine.adoc +898 -0
- data/docs/_guides/changes/index.adoc +396 -0
- data/docs/_guides/changes/programmatic-usage.adoc +1038 -0
- data/docs/_guides/changes/validating-changes.adoc +681 -0
- data/docs/_guides/cli/benchmark-performance.adoc +834 -0
- data/docs/_guides/cli/coverage-analysis.adoc +921 -0
- data/docs/_guides/cli/format-schemas.adoc +547 -0
- data/docs/_guides/cli/index.adoc +8 -0
- data/docs/_guides/cli/managing-changes.adoc +927 -0
- data/docs/_guides/cli/validate-ascii.adoc +645 -0
- data/docs/_guides/cli/validate-schemas.adoc +534 -0
- data/docs/_guides/index.adoc +165 -0
- data/docs/_guides/ler/creating-packages.adoc +664 -0
- data/docs/_guides/ler/index.adoc +305 -0
- data/docs/_guides/ler/loading-packages.adoc +707 -0
- data/docs/_guides/ler/package-formats.adoc +748 -0
- data/docs/_guides/ler/querying-packages.adoc +826 -0
- data/docs/_guides/ler/validating-packages.adoc +750 -0
- data/docs/_guides/liquid/basic-templates.adoc +813 -0
- data/docs/_guides/liquid/documentation-generation.adoc +1042 -0
- data/docs/_guides/liquid/drops-reference.adoc +829 -0
- data/docs/_guides/liquid/filters-and-tags.adoc +912 -0
- data/docs/_guides/liquid/index.adoc +468 -0
- data/docs/_guides/manifests/creating-manifests.adoc +483 -0
- data/docs/_guides/manifests/index.adoc +307 -0
- data/docs/_guides/manifests/resolving-manifests.adoc +557 -0
- data/docs/_guides/manifests/validating-manifests.adoc +713 -0
- data/docs/_guides/ruby-api/formatting-schemas.adoc +605 -0
- data/docs/_guides/ruby-api/index.adoc +257 -0
- data/docs/_guides/ruby-api/parsing-files.adoc +421 -0
- data/docs/_guides/ruby-api/search-engine.adoc +609 -0
- data/docs/_guides/ruby-api/working-with-repository.adoc +577 -0
- data/docs/_pages/data-model.adoc +665 -0
- data/docs/_pages/express-language.adoc +506 -0
- data/docs/_pages/getting-started.adoc +414 -0
- data/docs/_pages/index.adoc +116 -0
- data/docs/_pages/introduction.adoc +256 -0
- data/docs/_pages/ler-packages.adoc +837 -0
- data/docs/_pages/parsers.adoc +683 -0
- data/docs/_pages/schema-manifests.adoc +431 -0
- data/docs/_references/index.adoc +228 -0
- data/docs/_tutorials/creating-ler-package.adoc +735 -0
- data/docs/_tutorials/documentation-coverage.adoc +795 -0
- data/docs/_tutorials/index.adoc +221 -0
- data/docs/_tutorials/liquid-templates.adoc +806 -0
- data/docs/_tutorials/parsing-your-first-schema.adoc +522 -0
- data/docs/_tutorials/querying-schemas.adoc +751 -0
- data/docs/_tutorials/working-with-multiple-schemas.adoc +676 -0
- data/docs/index.adoc +242 -0
- data/docs/lychee.toml +84 -0
- data/examples/demo_ler_usage.sh +86 -0
- data/examples/ler/README.md +111 -0
- data/examples/ler/simple_example.ler +0 -0
- data/examples/ler/simple_schema.exp +33 -0
- data/examples/ler_build.rb +75 -0
- data/examples/ler_cli.rb +79 -0
- data/examples/ler_demo_complete.rb +276 -0
- data/examples/ler_query.rb +91 -0
- data/examples/ler_query_examples.rb +305 -0
- data/examples/ler_stats.rb +81 -0
- data/examples/phase3_demo.rb +159 -0
- data/examples/query_demo_simple.rb +131 -0
- data/expressir.gemspec +2 -0
- data/lib/expressir/cli.rb +12 -4
- data/lib/expressir/commands/manifest.rb +427 -0
- data/lib/expressir/commands/package.rb +1274 -0
- data/lib/expressir/commands/validate.rb +70 -37
- data/lib/expressir/commands/validate_ascii.rb +607 -0
- data/lib/expressir/commands/validate_load.rb +88 -0
- data/lib/expressir/express/formatter.rb +5 -1
- data/lib/expressir/express/formatters/remark_item_formatter.rb +25 -0
- data/lib/expressir/express/parser.rb +33 -0
- data/lib/expressir/manifest/resolver.rb +213 -0
- data/lib/expressir/manifest/validator.rb +195 -0
- data/lib/expressir/model/declarations/entity.rb +6 -0
- data/lib/expressir/model/dependency_resolver.rb +270 -0
- data/lib/expressir/model/indexes/entity_index.rb +103 -0
- data/lib/expressir/model/indexes/reference_index.rb +148 -0
- data/lib/expressir/model/indexes/type_index.rb +149 -0
- data/lib/expressir/model/interface_validator.rb +384 -0
- data/lib/expressir/model/repository.rb +400 -5
- data/lib/expressir/model/repository_validator.rb +295 -0
- data/lib/expressir/model/search_engine.rb +525 -0
- data/lib/expressir/model.rb +4 -94
- data/lib/expressir/package/builder.rb +200 -0
- data/lib/expressir/package/metadata.rb +81 -0
- data/lib/expressir/package/reader.rb +165 -0
- data/lib/expressir/schema_manifest.rb +11 -1
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +15 -2
- metadata +114 -4
- data/docs/benchmarking.adoc +0 -107
- data/docs/liquid_drops.adoc +0 -1547
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "interface_validator"
|
|
4
|
+
|
|
5
|
+
module Expressir
|
|
6
|
+
module Model
|
|
7
|
+
# Validator for repository consistency checks
|
|
8
|
+
# Handles schema reference validation, interface item validation,
|
|
9
|
+
# circular dependency detection, and unused schema warnings
|
|
10
|
+
# Coordinates with InterfaceValidator for detailed interface validation
|
|
11
|
+
class RepositoryValidator
|
|
12
|
+
# Initialize validator with schemas and reference index
|
|
13
|
+
# @param schemas [Array<Declarations::Schema>] Schemas to validate
|
|
14
|
+
# @param reference_index [Indexes::ReferenceIndex] Reference index for dependency tracking
|
|
15
|
+
def initialize(schemas, reference_index)
|
|
16
|
+
@schemas = schemas
|
|
17
|
+
@reference_index = reference_index
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Validate repository consistency
|
|
21
|
+
# @param strict [Boolean] Enable strict validation (unused schemas)
|
|
22
|
+
# @param check_interfaces [Boolean] Enable detailed interface validation
|
|
23
|
+
# @param check_completeness [Boolean] Check for schema completeness
|
|
24
|
+
# @param detailed [Boolean] Generate detailed reports
|
|
25
|
+
# @param check_duplicates [Boolean] Check for duplicate interface aliases
|
|
26
|
+
# @param check_self_references [Boolean] Check for self-referencing interfaces
|
|
27
|
+
# @return [Hash] Validation results with :valid?, :errors, :warnings
|
|
28
|
+
def validate(
|
|
29
|
+
strict: false,
|
|
30
|
+
check_interfaces: false,
|
|
31
|
+
check_completeness: false,
|
|
32
|
+
detailed: false,
|
|
33
|
+
check_duplicates: false,
|
|
34
|
+
check_self_references: false
|
|
35
|
+
)
|
|
36
|
+
errors = []
|
|
37
|
+
warnings = []
|
|
38
|
+
|
|
39
|
+
# Existing basic validations (always run)
|
|
40
|
+
validate_schema_references(errors)
|
|
41
|
+
validate_interface_items(errors)
|
|
42
|
+
detect_circular_dependencies(warnings)
|
|
43
|
+
|
|
44
|
+
if strict
|
|
45
|
+
detect_unused_schemas(warnings)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# NEW: Detailed interface validation
|
|
49
|
+
interface_result = nil
|
|
50
|
+
if check_interfaces
|
|
51
|
+
begin
|
|
52
|
+
interface_result = run_interface_validation(
|
|
53
|
+
detailed: detailed,
|
|
54
|
+
check_duplicates: check_duplicates,
|
|
55
|
+
check_self_references: check_self_references,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Merge results, avoiding duplicates
|
|
59
|
+
merge_validation_results(errors, warnings, interface_result)
|
|
60
|
+
rescue StandardError => e
|
|
61
|
+
# If interface validation fails, add a warning instead of crashing
|
|
62
|
+
warnings << {
|
|
63
|
+
type: :interface_validation_error,
|
|
64
|
+
message: "Could not perform detailed interface validation: #{e.message}",
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# NEW: Completeness checks
|
|
70
|
+
if check_completeness
|
|
71
|
+
check_schema_completeness(warnings)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
result = {
|
|
75
|
+
valid?: errors.empty?,
|
|
76
|
+
errors: errors,
|
|
77
|
+
warnings: warnings,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# Include detailed report if requested and available
|
|
81
|
+
if detailed && interface_result && interface_result[:interface_report]
|
|
82
|
+
result[:interface_report] = interface_result[:interface_report]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
result
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
# Run InterfaceValidator
|
|
91
|
+
# @param options [Hash] Validation options
|
|
92
|
+
# @return [Hash] Interface validation results
|
|
93
|
+
def run_interface_validation(options = {})
|
|
94
|
+
# Build temporary repository for InterfaceValidator
|
|
95
|
+
repository = build_temporary_repository
|
|
96
|
+
interface_validator = InterfaceValidator.new(repository)
|
|
97
|
+
interface_validator.validate(options)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Build a temporary repository from schemas
|
|
101
|
+
# @return [Repository] Temporary repository
|
|
102
|
+
def build_temporary_repository
|
|
103
|
+
# Find the parent repository if schemas have one
|
|
104
|
+
repo = @schemas.first&.parent
|
|
105
|
+
return repo if repo.is_a?(Repository)
|
|
106
|
+
|
|
107
|
+
# Create temporary repository
|
|
108
|
+
temp_repo = Repository.new
|
|
109
|
+
temp_repo.schemas = @schemas
|
|
110
|
+
temp_repo
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Merge interface validation results, avoiding duplicates
|
|
114
|
+
# @param errors [Array] Existing errors
|
|
115
|
+
# @param warnings [Array] Existing warnings
|
|
116
|
+
# @param interface_result [Hash] Interface validation results
|
|
117
|
+
# @return [void]
|
|
118
|
+
def merge_validation_results(errors, warnings, interface_result)
|
|
119
|
+
# Add interface errors, avoiding duplicates
|
|
120
|
+
interface_result[:errors].each do |error|
|
|
121
|
+
# Check if this error type already exists for the same schema/item
|
|
122
|
+
unless errors.any? { |e| error_matches?(e, error) }
|
|
123
|
+
errors << error
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Add interface warnings, avoiding duplicates
|
|
128
|
+
interface_result[:warnings].each do |warning|
|
|
129
|
+
unless warnings.any? { |w| warning_matches?(w, warning) }
|
|
130
|
+
warnings << warning
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Check if two errors match (to avoid duplicates)
|
|
136
|
+
# @param e1 [Hash] First error
|
|
137
|
+
# @param e2 [Hash] Second error
|
|
138
|
+
# @return [Boolean] True if errors match
|
|
139
|
+
def error_matches?(e1, e2)
|
|
140
|
+
e1[:type] == e2[:type] &&
|
|
141
|
+
e1[:schema] == e2[:schema] &&
|
|
142
|
+
e1[:referenced_schema] == e2[:referenced_schema] &&
|
|
143
|
+
e1[:item] == e2[:item]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Check if two warnings match (to avoid duplicates)
|
|
147
|
+
# @param w1 [Hash] First warning
|
|
148
|
+
# @param w2 [Hash] Second warning
|
|
149
|
+
# @return [Boolean] True if warnings match
|
|
150
|
+
def warning_matches?(w1, w2)
|
|
151
|
+
w1[:type] == w2[:type] &&
|
|
152
|
+
w1[:schema] == w2[:schema]
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Check schema completeness
|
|
156
|
+
# @param warnings [Array] Accumulator for warnings
|
|
157
|
+
# @return [void]
|
|
158
|
+
def check_schema_completeness(warnings)
|
|
159
|
+
@schemas.each do |schema|
|
|
160
|
+
# Warn if schema has no entities
|
|
161
|
+
if schema.entities.nil? || schema.entities.empty?
|
|
162
|
+
warnings << {
|
|
163
|
+
type: :empty_schema,
|
|
164
|
+
schema: schema.id,
|
|
165
|
+
message: "Schema '#{schema.id}' has no entities defined",
|
|
166
|
+
}
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Warn if schema has entities but no types
|
|
170
|
+
if schema.entities&.any? && (schema.types.nil? || schema.types.empty?)
|
|
171
|
+
warnings << {
|
|
172
|
+
type: :no_types,
|
|
173
|
+
schema: schema.id,
|
|
174
|
+
message: "Schema '#{schema.id}' has entities but no types defined (common but may indicate incomplete schema)",
|
|
175
|
+
}
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Validate that all referenced schemas exist
|
|
181
|
+
# @param errors [Array] Accumulator for errors
|
|
182
|
+
# @return [void]
|
|
183
|
+
def validate_schema_references(errors)
|
|
184
|
+
@schemas.each do |schema|
|
|
185
|
+
next unless schema.interfaces
|
|
186
|
+
|
|
187
|
+
schema.interfaces.each do |interface|
|
|
188
|
+
referenced_schema = find_schema(interface.schema.id)
|
|
189
|
+
|
|
190
|
+
if referenced_schema.nil?
|
|
191
|
+
errors << {
|
|
192
|
+
type: :missing_schema_reference,
|
|
193
|
+
schema: schema.id,
|
|
194
|
+
interface_type: interface.kind,
|
|
195
|
+
referenced_schema: interface.schema.id,
|
|
196
|
+
message: "Schema '#{schema.id}' references non-existent schema '#{interface.schema.id}'",
|
|
197
|
+
}
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Validate that all interface items exist in referenced schemas
|
|
204
|
+
# @param errors [Array] Accumulator for errors
|
|
205
|
+
# @return [void]
|
|
206
|
+
def validate_interface_items(errors)
|
|
207
|
+
@schemas.each do |schema|
|
|
208
|
+
next unless schema.interfaces
|
|
209
|
+
|
|
210
|
+
schema.interfaces.each do |interface|
|
|
211
|
+
referenced_schema = find_schema(interface.schema.id)
|
|
212
|
+
next if referenced_schema.nil? # Already caught by validate_schema_references
|
|
213
|
+
|
|
214
|
+
interface.items.each do |item|
|
|
215
|
+
ref_id = item.ref.id.safe_downcase
|
|
216
|
+
found = item_exists_in_schema?(referenced_schema, ref_id)
|
|
217
|
+
|
|
218
|
+
unless found
|
|
219
|
+
errors << {
|
|
220
|
+
type: :missing_interface_item,
|
|
221
|
+
schema: schema.id,
|
|
222
|
+
interface_type: interface.kind,
|
|
223
|
+
referenced_schema: interface.schema.id,
|
|
224
|
+
item: item.ref.id,
|
|
225
|
+
message: "Interface item '#{item.ref.id}' not found in schema '#{interface.schema.id}'",
|
|
226
|
+
}
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Detect circular dependencies using the reference index
|
|
234
|
+
# Note: Circular dependencies are valid in EXPRESS (mutual USE FROM)
|
|
235
|
+
# @param warnings [Array] Accumulator for warnings
|
|
236
|
+
# @return [void]
|
|
237
|
+
def detect_circular_dependencies(warnings)
|
|
238
|
+
cycles = @reference_index.detect_circular_dependencies
|
|
239
|
+
|
|
240
|
+
cycles.each do |cycle|
|
|
241
|
+
warnings << {
|
|
242
|
+
type: :circular_dependency,
|
|
243
|
+
cycle: cycle,
|
|
244
|
+
message: "Circular dependency (valid in EXPRESS): #{cycle.join(' -> ')}",
|
|
245
|
+
}
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Detect unused schemas in strict mode
|
|
250
|
+
# @param warnings [Array] Accumulator for warnings
|
|
251
|
+
# @return [void]
|
|
252
|
+
def detect_unused_schemas(warnings)
|
|
253
|
+
# Don't warn for single schema repositories
|
|
254
|
+
return if @schemas.size == 1
|
|
255
|
+
|
|
256
|
+
used_schemas = Set.new
|
|
257
|
+
@schemas.each do |schema|
|
|
258
|
+
next unless schema.interfaces
|
|
259
|
+
|
|
260
|
+
schema.interfaces.each do |interface|
|
|
261
|
+
used_schemas << interface.schema.id.safe_downcase
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
schema_id = schema.id.safe_downcase
|
|
265
|
+
next if used_schemas.include?(schema_id)
|
|
266
|
+
|
|
267
|
+
warnings << {
|
|
268
|
+
type: :unused_schema,
|
|
269
|
+
schema: schema.id,
|
|
270
|
+
message: "Schema '#{schema.id}' is not referenced by any other schema",
|
|
271
|
+
}
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Find schema by name
|
|
276
|
+
# @param schema_name [String] Schema name
|
|
277
|
+
# @return [Declarations::Schema, nil] Found schema or nil
|
|
278
|
+
def find_schema(schema_name)
|
|
279
|
+
@schemas.find { |s| s.id.safe_downcase == schema_name.safe_downcase }
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Check if an item exists in a schema
|
|
283
|
+
# @param schema [Declarations::Schema] Schema to search
|
|
284
|
+
# @param ref_id [String] Item reference ID (normalized)
|
|
285
|
+
# @return [Boolean] True if item exists
|
|
286
|
+
def item_exists_in_schema?(schema, ref_id)
|
|
287
|
+
schema.entities&.any? { |e| e.id.safe_downcase == ref_id } ||
|
|
288
|
+
schema.types&.any? { |t| t.id.safe_downcase == ref_id } ||
|
|
289
|
+
schema.functions&.any? { |f| f.id.safe_downcase == ref_id } ||
|
|
290
|
+
schema.procedures&.any? { |p| p.id.safe_downcase == ref_id } ||
|
|
291
|
+
schema.constants&.any? { |c| c.id.safe_downcase == ref_id }
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|