Class: Expressir::Manifest::Validator

Inherits:
Object
  • Object
show all
Defined in:
lib/expressir/manifest/validator.rb

Overview

Validates schema manifests for completeness and integrity Performs three types of validation:

  1. File existence - checks if schema files exist

  2. Path completeness - warns about missing paths

  3. Referential integrity - validates USE/REFERENCE FROM resolution

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(manifest, options = {}) ⇒ Validator

Initialize validator

Parameters:

  • manifest (SchemaManifest)

    The manifest to validate

  • options (Hash) (defaults to: {})

    Validation options

Options Hash (options):

  • :verbose (Boolean)

    Enable verbose output



19
20
21
22
23
# File 'lib/expressir/manifest/validator.rb', line 19

def initialize(manifest, options = {})
  @manifest = manifest
  @options = options
  @base_dirs = extract_base_dirs_from_manifest
end

Instance Attribute Details

#manifestObject (readonly)

Returns the value of attribute manifest.



13
14
15
# File 'lib/expressir/manifest/validator.rb', line 13

def manifest
  @manifest
end

#optionsObject (readonly)

Returns the value of attribute options.



13
14
15
# File 'lib/expressir/manifest/validator.rb', line 13

def options
  @options
end

Instance Method Details

#validate_file_existenceArray<Hash>

Validate that all schema files exist on disk

Returns:

  • (Array<Hash>)

    Array of error hashes with :schema, :path, :message



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/expressir/manifest/validator.rb', line 27

def validate_file_existence
  errors = []
  manifest.schemas.each do |schema_entry|
    next if schema_entry.path.nil? || schema_entry.path.empty?

    unless File.exist?(schema_entry.path)
      errors << {
        schema: schema_entry.id,
        path: schema_entry.path,
        message: "Schema file not found: #{schema_entry.path} (#{schema_entry.id})",
      }
    end
  end
  errors
end

#validate_path_completenessArray<Hash>

Validate that all schemas have paths specified

Returns:

  • (Array<Hash>)

    Array of warning hashes with :schema, :message



45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/expressir/manifest/validator.rb', line 45

def validate_path_completeness
  warnings = []
  manifest.schemas.each do |schema_entry|
    if schema_entry.path.nil? || schema_entry.path.empty?
      warnings << {
        schema: schema_entry.id,
        message: "Schema '#{schema_entry.id}' has no path specified - please provide path",
      }
    end
  end
  warnings
end

#validate_referential_integrityArray<Hash>

Validate referential integrity using DependencyResolver Checks that all USE FROM and REFERENCE FROM interfaces can be resolved

Returns:

  • (Array<Hash>)

    Array of error hashes with interface details



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/expressir/manifest/validator.rb', line 87

def validate_referential_integrity
  errors = []

  # Filter schemas to validate
  schemas_to_validate = manifest.schemas.select do |s|
    !s.path.nil? && !s.path.empty? && File.exist?(s.path)
  end

  # Show header if verbose
  if @options[:verbose]
    puts "Validating referential integrity for #{schemas_to_validate.size} schemas..."
    puts ""
  end

  # Create resolver ONCE for all schemas (efficiency)
  # Share verbose output behavior with manifest create command (DRY)
  resolver = Model::DependencyResolver.new(
    base_dirs: @base_dirs,
    schema_registry: build_schema_registry,
    verbose: @options[:verbose], # Let resolver show detailed interface resolution
  )

  schemas_to_validate.each do |schema_entry|
    # Extract interfaces from this schema
    begin
      interfaces = resolver.extract_interfaces(schema_entry.path)
    rescue StandardError => e
      errors << {
        schema: schema_entry.id,
        path: schema_entry.path,
        message: "Failed to parse schema: #{e.message}",
      }
      next
    end

    # Check each interface can be resolved
    # The resolver will print verbose output for each resolution when verbose=true
    interfaces.each do |interface|
      # Print schema name context if verbose (matches create command style)
      if @options[:verbose]
        print "  #{interface[:kind]} FROM #{interface[:schema_name]} (in #{schema_entry.id}): "
      end

      resolved_path = resolver.resolve_schema_location(
        interface[:schema_name],
        interface[:kind],
        schema_entry.path,
      )

      if resolved_path.nil?
        puts "\e[31m✗ not found\e[0m" if @options[:verbose]

        errors << {
          schema: schema_entry.id,
          path: schema_entry.path,
          interface_kind: interface[:kind],
          referenced_schema: interface[:schema_name],
          message: "Cannot resolve #{interface[:kind]} FROM #{interface[:schema_name]} in #{schema_entry.id}",
        }
      elsif @options[:verbose]
        relative_path = File.basename(resolved_path)
        puts "\e[32m✓\e[0m #{relative_path}"
      end
    end
  end

  errors
end

#validate_schema_namesArray<Hash>

Validate that manifest IDs match actual schema names in files

Returns:

  • (Array<Hash>)

    Array of error hashes with :schema, :path, :actual_name, :message



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/expressir/manifest/validator.rb', line 60

def validate_schema_names
  errors = []
  manifest.schemas.each do |schema_entry|
    next if schema_entry.path.nil? || schema_entry.path.empty?
    next unless File.exist?(schema_entry.path)

    begin
      actual_name = extract_schema_name(schema_entry.path)
      if actual_name != schema_entry.id
        errors << {
          schema: schema_entry.id,
          path: schema_entry.path,
          actual_name: actual_name,
          message: "Schema ID mismatch: manifest has '#{schema_entry.id}' but file declares '#{actual_name}'",
        }
      end
    rescue StandardError
      # Skip validation if we can't parse the file
      next
    end
  end
  errors
end