Class: AgentCode::Blueprint::BlueprintValidator

Inherits:
Object
  • Object
show all
Defined in:
lib/agentcode/blueprint/blueprint_validator.rb

Overview

Validates parsed blueprint data structures. Port of agentcode-server BlueprintValidator.php / agentcode-adonis-server blueprint_validator.ts.

Constant Summary collapse

VALID_COLUMN_TYPES =
%w[
  string text integer bigInteger boolean date datetime
  timestamp decimal float json uuid foreignId
].freeze
VALID_ACTIONS =
%w[
  index show store update destroy trashed restore forceDelete
].freeze

Instance Method Summary collapse

Instance Method Details

#validate_columns(columns) ⇒ Array<String>

Validate columns.

Parameters:

  • columns (Array<Hash>)

Returns:

  • (Array<String>)

    errors



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/agentcode/blueprint/blueprint_validator.rb', line 80

def validate_columns(columns)
  errors = []
  seen = Set.new

  columns.each do |col|
    if col[:name].nil? || col[:name].strip.empty?
      errors << "Column name is required"
      next
    end

    if seen.include?(col[:name])
      errors << "Duplicate column name '#{col[:name]}'"
    end
    seen.add(col[:name])

    unless VALID_COLUMN_TYPES.include?(col[:type])
      errors << "Invalid column type '#{col[:type]}' for column '#{col[:name]}'"
    end

    if col[:type] == "foreignId" && col[:foreign_model].nil?
      errors << "Column '#{col[:name]}' is foreignId but missing 'foreign_model'"
    end
  end

  errors
end

#validate_model(blueprint, valid_roles = {}) ⇒ Hash

Validate a full model blueprint.

Parameters:

  • blueprint (Hash)
  • valid_roles (Hash) (defaults to: {})

    optional role definitions for cross-reference

Returns:

  • (Hash)

    { valid:, errors:, warnings: }



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/agentcode/blueprint/blueprint_validator.rb', line 47

def validate_model(blueprint, valid_roles = {})
  errors = []
  warnings = []

  # Model name
  if blueprint[:model].nil? || blueprint[:model].strip.empty?
    errors << "Model name is required"
  elsif !blueprint[:model].match?(/\A[A-Z][a-zA-Z0-9]*\z/)
    errors << "Invalid model name '#{blueprint[:model]}' — must be PascalCase (match /^[A-Z][a-zA-Z0-9]*$/)"
  end

  # Columns
  errors.concat(validate_columns(blueprint[:columns]))

  # Permissions
  column_names = blueprint[:columns].map { |c| c[:name] }
  perm_result = validate_permissions(blueprint[:permissions], valid_roles, column_names)
  errors.concat(perm_result[:errors])
  warnings.concat(perm_result[:warnings])

  # Options
  errors.concat(validate_options(blueprint[:options]))

  # Relationships
  errors.concat(validate_relationships(blueprint[:relationships]))

  { valid: errors.empty?, errors: errors, warnings: warnings }
end

#validate_options(options) ⇒ Object

Validate options.



160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/agentcode/blueprint/blueprint_validator.rb', line 160

def validate_options(options)
  errors = []

  if options[:except_actions]
    options[:except_actions].each do |action|
      unless VALID_ACTIONS.include?(action)
        errors << "Invalid action '#{action}' in except_actions"
      end
    end
  end

  errors
end

#validate_permissions(permissions, valid_roles, column_names) ⇒ Hash

Validate permissions.

Returns:

  • (Hash)

    { errors:, warnings: }



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
155
156
157
# File 'lib/agentcode/blueprint/blueprint_validator.rb', line 110

def validate_permissions(permissions, valid_roles, column_names)
  errors = []
  warnings = []
  has_roles = !valid_roles.empty?

  permissions.each do |role, perm|
    # Check role exists
    if has_roles && !valid_roles.key?(role)
      errors << "Unknown role '#{role}' in permissions"
    end

    # Check actions
    perm[:actions].each do |action|
      unless VALID_ACTIONS.include?(action)
        errors << "Invalid action '#{action}' for role '#{role}'"
      end
    end

    # Check field references
    all_column_names = ["id"] + column_names
    check_field_references(perm[:show_fields], all_column_names, role, "show_fields", warnings)
    check_field_references(perm[:create_fields], all_column_names, role, "create_fields", warnings)
    check_field_references(perm[:update_fields], all_column_names, role, "update_fields", warnings)

    # Warn on conflicts
    if perm[:hidden_fields].any? && perm[:show_fields].any?
      perm[:hidden_fields].each do |field|
        if perm[:show_fields].include?(field)
          warnings << "Role '#{role}': field '#{field}' is in both show_fields and hidden_fields"
        end
      end
    end

    # Warn on create_fields without store action
    if perm[:create_fields].any? && !perm[:create_fields].include?("*") &&
       perm[:create_fields].any? { |f| f != "*" } && !perm[:actions].include?("store")
      warnings << "Role '#{role}': has create_fields but no 'store' action"
    end

    # Warn on update_fields without update action
    if perm[:update_fields].any? && !perm[:update_fields].include?("*") &&
       perm[:update_fields].any? { |f| f != "*" } && !perm[:actions].include?("update")
      warnings << "Role '#{role}': has update_fields but no 'update' action"
    end
  end

  { errors: errors, warnings: warnings }
end

#validate_relationships(relationships) ⇒ Object

Validate relationships.



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/agentcode/blueprint/blueprint_validator.rb', line 175

def validate_relationships(relationships)
  errors = []
  valid_types = %w[belongsTo hasMany hasOne belongsToMany]

  relationships.each do |rel|
    rel = rel.transform_keys(&:to_s) if rel.is_a?(Hash)

    if rel["type"].nil?
      errors << "Relationship is missing type"
    elsif !valid_types.include?(rel["type"])
      errors << "Invalid relationship type '#{rel["type"]}'"
    end

    if rel["model"].nil?
      errors << "Relationship is missing model"
    end
  end

  errors
end

#validate_roles(roles) ⇒ Hash

Validate role definitions.

Parameters:

  • roles (Hash<String, Hash>)

Returns:

  • (Hash)

    { valid:, errors: }



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/agentcode/blueprint/blueprint_validator.rb', line 21

def validate_roles(roles)
  errors = []

  if roles.empty?
    errors << "At least one role is required"
    return { valid: false, errors: errors }
  end

  roles.each do |slug, role|
    unless slug.match?(/\A[a-z][a-z0-9_]*\z/)
      errors << "Invalid role slug '#{slug}' — must match /^[a-z][a-z0-9_]*$/"
    end

    if role[:name].nil? || role[:name].strip.empty?
      errors << "Role '#{slug}' must have a non-empty name"
    end
  end

  { valid: errors.empty?, errors: errors }
end