Module: Smplkit::ConfigResolution Private

Defined in:
lib/smplkit/config_resolution.rb

Overview

This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.

SDK configuration resolution: defaults -> file -> env vars -> constructor args.

Defined Under Namespace

Classes: ResolvedClientConfig, ResolvedConfig

Constant Summary collapse

CONFIG_KEYS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

{
  "api_key" => "SMPLKIT_API_KEY",
  "base_domain" => "SMPLKIT_BASE_DOMAIN",
  "scheme" => "SMPLKIT_SCHEME",
  "environment" => "SMPLKIT_ENVIRONMENT",
  "service" => "SMPLKIT_SERVICE",
  "debug" => "SMPLKIT_DEBUG",
  "telemetry" => "SMPLKIT_TELEMETRY"
}.freeze
BOOL_TRUE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

%w[true 1 yes].freeze
BOOL_FALSE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

%w[false 0 no].freeze
DEFAULTS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

{
  "api_key" => nil,
  "base_domain" => "smplkit.com",
  "scheme" => "https",
  "environment" => nil,
  "service" => nil,
  "debug" => false,
  "telemetry" => true
}.freeze

Class Method Summary collapse

Class Method Details

.missing_required(resolved, key, profile) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Raises:



204
205
206
207
208
209
210
211
212
213
# File 'lib/smplkit/config_resolution.rb', line 204

def missing_required(resolved, key, profile)
  return if resolved[key]

  env_var = CONFIG_KEYS[key]
  raise Error,
        "No #{key} provided. Set one of:\n  " \
        "1. Pass #{key} to the constructor\n  " \
        "2. Set the #{env_var} environment variable\n  " \
        "3. Add #{key} to the [#{profile}] section in ~/.smplkit"
end

.parse_bool(value, key) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Raises:



45
46
47
48
49
50
51
52
53
# File 'lib/smplkit/config_resolution.rb', line 45

def parse_bool(value, key)
  lower = value.to_s.strip.downcase
  return true if BOOL_TRUE.include?(lower)
  return false if BOOL_FALSE.include?(lower)

  raise Error,
        "Invalid boolean value for #{key}: #{value.inspect}. " \
        "Expected one of: true, false, 1, 0, yes, no"
end

.parse_ini(text) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Minimal INI parser. Returns { section_name => { key => value, … } }.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/smplkit/config_resolution.rb', line 61

def parse_ini(text)
  sections = {}
  current = nil
  text.each_line do |line|
    line = line.strip
    next if line.empty? || line.start_with?("#", ";")

    if line.start_with?("[") && line.end_with?("]")
      name = line[1..-2].strip
      current = (sections[name] ||= {})
    elsif current
      key, _, value = line.partition("=")
      next if value.empty? && !line.include?("=")

      current[key.strip] = value.strip
    end
  end
  sections
end

.read_config_file(profile, home_dir: nil) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



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
106
107
108
109
110
# File 'lib/smplkit/config_resolution.rb', line 81

def read_config_file(profile, home_dir: nil)
  home_dir ||= Dir.home
  path = File.join(home_dir, ".smplkit")
  return {} unless File.file?(path)

  sections =
    begin
      # Force UTF-8 — the file may contain comments with non-ASCII bytes
      # (em dashes, smart quotes) and the system's default external
      # encoding is locale-dependent.
      parse_ini(File.read(path, encoding: "UTF-8"))
    rescue StandardError
      return {}
    end

  values = {}
  sections.fetch("common", {}).each { |k, v| values[k] = v unless v.empty? }

  if sections.key?(profile)
    sections[profile].each { |k, v| values[k] = v unless v.empty? }
  else
    non_common = sections.keys.reject { |s| s == "common" }
    if !non_common.empty? && profile != "default"
      raise Error,
            "Profile [#{profile}] not found in ~/.smplkit. Available profiles: #{non_common.join(", ")}"
    end
  end

  values
end

.resolve_client_config(profile: nil, api_key: nil, base_domain: nil, scheme: nil, debug: nil, home_dir: nil) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/smplkit/config_resolution.rb', line 162

def resolve_client_config(profile: nil, api_key: nil, base_domain: nil,
                          scheme: nil, debug: nil, home_dir: nil)
  resolved = {
    "api_key" => nil,
    "base_domain" => "smplkit.com",
    "scheme" => "https",
    "debug" => false
  }

  active_profile = profile || ENV["SMPLKIT_PROFILE"] || "default"

  file_values = read_config_file(active_profile, home_dir: home_dir)
  %w[api_key base_domain scheme debug].each do |key|
    next unless file_values.key?(key)

    val = file_values[key]
    resolved[key] = key == "debug" ? parse_bool(val, key) : val
  end

  [
    %w[api_key SMPLKIT_API_KEY], %w[base_domain SMPLKIT_BASE_DOMAIN],
    %w[scheme SMPLKIT_SCHEME], %w[debug SMPLKIT_DEBUG]
  ].each do |key, env_var|
    env_val = ENV.fetch(env_var, "")
    next if env_val.empty?

    resolved[key] = key == "debug" ? parse_bool(env_val, env_var) : env_val
  end

  ctor = { "api_key" => api_key, "base_domain" => base_domain, "scheme" => scheme, "debug" => debug }
  ctor.each { |k, v| resolved[k] = v unless v.nil? }

  missing_required(resolved, "api_key", active_profile)

  ResolvedClientConfig.new(
    api_key: resolved["api_key"].to_s,
    base_domain: resolved["base_domain"].to_s,
    scheme: resolved["scheme"].to_s,
    debug: resolved["debug"] ? true : false
  )
end

.resolve_config(profile: nil, api_key: nil, base_domain: nil, scheme: nil, environment: nil, service: nil, debug: nil, telemetry: nil, home_dir: nil) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



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
158
159
160
# File 'lib/smplkit/config_resolution.rb', line 112

def resolve_config(profile: nil, api_key: nil, base_domain: nil, scheme: nil,
                   environment: nil, service: nil, debug: nil, telemetry: nil,
                   home_dir: nil)
  resolved = DEFAULTS.dup

  active_profile = profile || ENV["SMPLKIT_PROFILE"] || "default"

  file_values = read_config_file(active_profile, home_dir: home_dir)
  CONFIG_KEYS.each_key do |key|
    next unless file_values.key?(key)

    val = file_values[key]
    resolved[key] = %w[debug telemetry].include?(key) ? parse_bool(val, key) : val
  end

  CONFIG_KEYS.each do |key, env_var|
    env_val = ENV.fetch(env_var, "")
    next if env_val.empty?

    resolved[key] = %w[debug telemetry].include?(key) ? parse_bool(env_val, env_var) : env_val
  end

  ctor = {
    "api_key" => api_key, "base_domain" => base_domain, "scheme" => scheme,
    "environment" => environment, "service" => service,
    "debug" => debug, "telemetry" => telemetry
  }
  ctor.each { |k, v| resolved[k] = v unless v.nil? }

  # Validate required fields.
  #
  # +environment+ and +service+ are OPTIONAL: an audit-only or jobs-only
  # customer needs neither, and when +environment+ is absent the server
  # derives it from the API key (the key can be scoped to an environment).
  # config/flags/logging simply send no environment signal when it's unset.
  # +api_key+ remains required.
  missing_required(resolved, "api_key", active_profile)

  ResolvedConfig.new(
    api_key: resolved["api_key"].to_s,
    base_domain: resolved["base_domain"].to_s,
    scheme: resolved["scheme"].to_s,
    # Preserve nil rather than coercing to the literal string "".
    environment: resolved["environment"]&.to_s,
    service: resolved["service"]&.to_s,
    debug: resolved["debug"] ? true : false,
    telemetry: resolved["telemetry"] ? true : false
  )
end

.service_url(scheme, subdomain, base_domain) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Build a service URL: scheme://subdomain.base_domain



56
57
58
# File 'lib/smplkit/config_resolution.rb', line 56

def service_url(scheme, subdomain, base_domain)
  "#{scheme}://#{subdomain}.#{base_domain}"
end