Module: Quonfig::Datadir
- Defined in:
- lib/quonfig/datadir.rb
Overview
Loads a Quonfig workspace from the local filesystem (offline / datadir mode). Mirrors sdk-node/src/datadir.ts.
The workspace directory layout matches integration-test-data:
<datadir>/quonfig.json
<datadir>/configs/*.json
<datadir>/feature-flags/*.json
<datadir>/segments/*.json
<datadir>/log-levels/*.json
schemas/ is intentionally excluded — those files are raw JSON Schema documents, not Configs, and SDKs do not consume them (qfg-uzsl).
Each
Constant Summary collapse
- CONFIG_SUBDIRS =
%w[configs feature-flags segments log-levels].freeze
Class Method Summary collapse
- .coerce_numeric_value_field(hash) ⇒ Object
-
.coerce_numeric_values(node) ⇒ Object
Config files store int/double Value fields as JSON strings (
{"type":"int","value":"123"}). - .effective_send_to_client_sdk(type, raw) ⇒ Object
-
.load_envelope(datadir, environment = nil) ⇒ Object
Read every config JSON in
datadir, project to ConfigResponse hashes, and wrap in a ConfigEnvelope. -
.load_store(datadir, environment = nil) ⇒ Object
Convenience: load the envelope and populate a fresh ConfigStore.
- .resolve_environment(quonfig_path, environment) ⇒ Object
- .to_config_response(raw, env_id) ⇒ Object
Class Method Details
.coerce_numeric_value_field(hash) ⇒ Object
122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/quonfig/datadir.rb', line 122 def coerce_numeric_value_field(hash) value = hash['value'] return unless value.is_a?(String) case hash['type'] when 'int' hash['value'] = Integer(value, 10) when 'double' hash['value'] = Float(value) end rescue ArgumentError, TypeError # Unparseable numeric string — leave the original value untouched. end |
.coerce_numeric_values(node) ⇒ Object
Config files store int/double Value fields as JSON strings
({"type":"int","value":"123"}). api-delivery normalizes these to real
numbers at config-load time (Value.UnmarshalJSON), so every envelope it
emits over HTTP/SSE already carries JSON numbers. In datadir mode we read
the files directly, so we must coerce here to match.
Walks the parsed config document in place, coercing every Value node — any
Hash with a type of "int"/"double" and a String value — to a real
number. A generic recursive walk covers default.rules[].value,
environment rules, criteria[].valueToMatch, weighted-value arms, and
variants without enumerating each location. On parse failure the original
string is left in place (passthrough — never raise).
111 112 113 114 115 116 117 118 119 120 |
# File 'lib/quonfig/datadir.rb', line 111 def coerce_numeric_values(node) case node when Hash coerce_numeric_value_field(node) node.each_value { |child| coerce_numeric_values(child) } when Array node.each { |child| coerce_numeric_values(child) } end node end |
.effective_send_to_client_sdk(type, raw) ⇒ Object
93 94 95 96 97 |
# File 'lib/quonfig/datadir.rb', line 93 def effective_send_to_client_sdk(type, raw) return true if type == 'feature_flag' raw || false end |
.load_envelope(datadir, environment = nil) ⇒ Object
Read every config JSON in datadir, project to ConfigResponse hashes,
and wrap in a ConfigEnvelope. Does no network I/O.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/quonfig/datadir.rb', line 29 def load_envelope(datadir, environment = nil) env_id = resolve_environment(File.join(datadir, 'quonfig.json'), environment) configs = [] CONFIG_SUBDIRS.each do |subdir| dir = File.join(datadir, subdir) next unless Dir.exist?(dir) Dir.children(dir) .select { |name| name.end_with?('.json') } .sort .each do |filename| path = File.join(dir, filename) raw = JSON.parse(File.read(path)) raise ArgumentError, "[quonfig] config has empty key — file is not a Quonfig Config: #{path}" if raw['key'].nil? || raw['key'].to_s.empty? coerce_numeric_values(raw) configs << to_config_response(raw, env_id) end end Quonfig::ConfigEnvelope.new( configs: configs, meta: { 'version' => "datadir:#{datadir}", 'environment' => env_id } ) end |
.load_store(datadir, environment = nil) ⇒ Object
Convenience: load the envelope and populate a fresh ConfigStore.
57 58 59 60 61 62 |
# File 'lib/quonfig/datadir.rb', line 57 def load_store(datadir, environment = nil) envelope = load_envelope(datadir, environment) store = Quonfig::ConfigStore.new envelope.configs.each { |cfg| store.set(cfg['key'], cfg) } store end |
.resolve_environment(quonfig_path, environment) ⇒ Object
64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/quonfig/datadir.rb', line 64 def resolve_environment(quonfig_path, environment) environment ||= ENV.fetch('QUONFIG_ENVIRONMENT', nil) raise Quonfig::Errors::MissingEnvironmentError if environment.nil? || environment.empty? raise ArgumentError, "[quonfig] Datadir is missing quonfig.json: #{quonfig_path}" unless File.exist?(quonfig_path) environments = JSON.parse(File.read(quonfig_path)).fetch('environments', []) raise Quonfig::Errors::InvalidEnvironmentError.new(environment, environments) if !environments.empty? && !environments.include?(environment) environment end |
.to_config_response(raw, env_id) ⇒ Object
78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/quonfig/datadir.rb', line 78 def to_config_response(raw, env_id) environment = Array(raw['environments']).find { |e| e['id'] == env_id } type = raw['type'] { 'id' => raw['id'] || '', 'key' => raw['key'], 'type' => type, 'valueType' => raw['valueType'], 'sendToClientSdk' => effective_send_to_client_sdk(type, raw['sendToClientSdk']), 'default' => raw['default'] || { 'rules' => [] }, 'environment' => environment } end |