Module: AnnFlavorCore::StrictModeChecker
- Defined in:
- lib/vendor/ann_flavor_core/lib/ann_flavor_core/strict_mode_checker.rb
Overview
Opt-in unknown-key detection for annspec.yaml.
Per-level VALID KEYS are derived automatically from each Struct's members (Ruby's
built-in reflection for Struct.new(...)-defined classes) -- this part can never
drift out of sync with the model, the same way Kotlin's reflection-based checker
can't. Ruby has no static typing, so which nested Struct class a field recurses
into cannot be derived by reflection alone; RECURSION_TARGETS is the one small,
explicit map needed for that -- it is a target-class lookup, not a key list, so it
stays small (~20 entries) and stable even as fields are added to existing structs.
Does NOT modify AnnFlavorCore::Parser in any way -- a fully separate, additive pass over the same raw Hash the parser already receives.
Usage: AnnFlavorCore::StrictModeChecker.check(raw_hash, AnnFlavorCore::AnnSpec)
Constant Summary collapse
- FREE_FORM_FIELDS =
Fields whose value is a free-form Hash (arbitrary user-declared keys are data, not schema) -- exempted from unknown-key checking entirely, at any level.
%i[custom dart_defines].freeze
- RECURSION_TARGETS =
(containing_struct, field_name) => target_struct — only the "does this field recurse, and into what" relationship; the valid-keys-within-that-struct question is still fully derived from Struct#members.
{ [AnnSpec, :app] => AnnApp, [AnnSpec, :tooling] => ToolingConfig, [AnnApp, :android] => AndroidPlatform, [AnnApp, :ios] => IosPlatform, [AnnApp, :web] => WebPlatform, [AnnApp, :windows] => WindowsPlatform, [AnnApp, :integrations] => AnnIntegrations, [AndroidPlatform, :default] => AndroidDefault, [AndroidPlatform, :flavors] => AndroidFlavor, # via `flavor:` YAML key, map values [IosPlatform, :default] => IosDefault, [IosPlatform, :flavors] => IosFlavor, [WebPlatform, :default] => WebDefault, [WebPlatform, :flavors] => WebFlavor, [WindowsPlatform, :default] => WindowsDefault, [WindowsPlatform, :flavors] => WindowsFlavor, [AndroidDefault, :admob] => AdmobConfig, [AndroidDefault, :sdk] => AndroidSdk, [AndroidDefault, :credentials] => AndroidCredentials, [AndroidDefault, :firebase] => FirebaseConfig, [AndroidDefault, :build_types] => BuildTypeConfig, # map values [AndroidFlavor, :admob] => AdmobConfig, [AndroidFlavor, :credentials] => AndroidCredentials, [AndroidFlavor, :firebase] => FirebaseConfig, [AndroidFlavor, :stores] => FlavorStores, [AndroidFlavor, :build_types] => BuildTypeConfig, [IosDefault, :admob] => AdmobConfig, [IosDefault, :credentials] => IosCredentials, [IosDefault, :firebase] => FirebaseConfig, [IosDefault, :build_types] => BuildTypeConfig, [IosFlavor, :admob] => AdmobConfig, [IosFlavor, :credentials] => IosCredentials, [IosFlavor, :firebase] => FirebaseConfig, [IosFlavor, :stores] => FlavorStores, [IosFlavor, :build_types] => BuildTypeConfig, [WebDefault, :cloudflare] => CloudflareConfig, [WebDefault, :build_types] => BuildTypeConfig, [WebFlavor, :cloudflare] => CloudflareConfig, [WebFlavor, :build_types] => BuildTypeConfig, [WindowsDefault, :build_types] => BuildTypeConfig, [WindowsFlavor, :build_types] => BuildTypeConfig, [BuildTypeConfig, :admob] => AdmobConfig, [BuildTypeConfig, :firebase] => FirebaseConfig, [BuildTypeConfig, :auth] => AuthConfig, [AndroidCredentials, :signing] => AndroidSigning, [AndroidCredentials, :google_play] => GooglePlayCredentials, [AndroidCredentials, :samsung_galaxy] => SamsungGalaxyCredentials, [AndroidCredentials, :amazon] => AmazonCredentials, [IosCredentials, :signing] => IosSigning, [IosCredentials, :app_store] => AppStoreCredentials, [FlavorStores, :google_play] => GooglePlayFlavorStore, [FlavorStores, :samsung_galaxy] => SamsungGalaxyFlavorStore, [FlavorStores, :amazon] => AmazonFlavorStore, [FlavorStores, :app_store] => AppStoreFlavorStore, }.freeze
- MAP_VALUED_FIELDS =
Fields whose target struct's instances live inside a Hash keyed by an arbitrary user-chosen name (flavor name, build-type name), not a single nested Hash.
%i[flavors build_types].freeze
Class Method Summary collapse
Class Method Details
.check(raw, root_struct) ⇒ Object
96 97 98 99 100 |
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/strict_mode_checker.rb', line 96 def self.check(raw, root_struct) issues = [] walk(raw, root_struct, '', issues) issues end |
.walk(raw, struct_class, path, issues) ⇒ Object
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 |
# File 'lib/vendor/ann_flavor_core/lib/ann_flavor_core/strict_mode_checker.rb', line 102 def self.walk(raw, struct_class, path, issues) return unless raw.is_a?(Hash) valid_keys = struct_class.members.map(&:to_s) # `flavors` (Struct member) reads YAML key `flavor` (singular) -- the one # deliberate field-name <-> YAML-key exception in the schema. valid_keys = valid_keys.map { |k| k == 'flavors' ? 'flavor' : k } raw.each do |raw_key, raw_value| key = raw_key.to_s field_path = path.empty? ? key : "#{path}.#{key}" unless valid_keys.include?(key) context = path.empty? ? 'top-level annspec.yaml' : "#{path} block" issues << "Unknown field \"#{key}\" in #{context}. Check for typos or remove the field." next end field_sym = (key == 'flavor' ? 'flavors' : key).to_sym next if FREE_FORM_FIELDS.include?(field_sym) target_struct = RECURSION_TARGETS[[struct_class, field_sym]] next unless target_struct if MAP_VALUED_FIELDS.include?(field_sym) raw_value.is_a?(Hash) && raw_value.each_value do |entry_val| walk(entry_val, target_struct, field_path, issues) end else walk(raw_value, target_struct, field_path, issues) end end end |