Class: Mailkube::Config

Inherits:
Object
  • Object
show all
Defined in:
lib/mailkube/config.rb,
sig/mailkube/config.rbs

Overview

Resolved client configuration: the key, the origin, the timeout and the default headers.

This is the only place configuration is read, and the only place a URL is built. Keeping the origin guard here rather than in a resource protects every future link-following feature for free. Instances are frozen: a client that cannot be reconfigured after construction is a client that cannot develop a concurrency bug.

Constant Summary collapse

ENV_API_KEY =

Environment variable holding the API key.

Returns:

  • (String)
"MAILKUBE_API_KEY"
ENV_BASE_URL =

Environment variable overriding the API base URL.

Returns:

  • (String)
"MAILKUBE_BASE_URL"
DEFAULT_TIMEOUT =

Per-request timeout in seconds when the caller does not set one.

Returns:

  • (Integer)
30

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_key: nil, base_url: nil, timeout: DEFAULT_TIMEOUT, user_agent_suffix: nil) ⇒ Config

Resolve configuration from the arguments, then the environment, then the defaults.

Parameters:

  • api_key (String, nil) (defaults to: nil)

    the API key; falls back to MAILKUBE_API_KEY.

  • base_url (String, nil) (defaults to: nil)

    the API base URL; falls back to MAILKUBE_BASE_URL.

  • timeout (Integer, Float) (defaults to: DEFAULT_TIMEOUT)

    the per-request timeout in seconds.

  • user_agent_suffix (String, nil) (defaults to: nil)

    a name/version token identifying software that wraps this SDK, appended after this SDK's own token.

  • api_key: (String, nil) (defaults to: nil)
  • base_url: (String, nil) (defaults to: nil)
  • timeout: (Numeric) (defaults to: DEFAULT_TIMEOUT)
  • user_agent_suffix: (String, nil) (defaults to: nil)

Raises:



36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/mailkube/config.rb', line 36

def initialize(api_key: nil, base_url: nil, timeout: DEFAULT_TIMEOUT, user_agent_suffix: nil)
  key = api_key || ENV.fetch(ENV_API_KEY, nil)
  raise ConfigurationError, "no API key provided: pass api_key: or set #{ENV_API_KEY}" if key.nil? || key.empty?

  @api_key = key
  @base_url = base_url || ENV.fetch(ENV_BASE_URL, nil) || Mailkube::DEFAULT_BASE_URL
  @timeout = timeout
  suffix = user_agent_suffix.to_s.strip
  # Dropped rather than sanitized: a header value that could split the request is not one this
  # gem will send, and silently repairing it hides the caller's bug.
  @user_agent_suffix = suffix.match?(/[\r\n]/) ? "" : suffix
  freeze
end

Instance Attribute Details

#base_urlString (readonly)

Returns the resolved API base URL, always ending in a slash.

Returns:

  • (String)

    the resolved API base URL, always ending in a slash.



24
25
26
# File 'lib/mailkube/config.rb', line 24

def base_url
  @base_url
end

#timeoutInteger, Float (readonly)

Returns the per-request timeout in seconds.

Returns:

  • (Integer, Float)

    the per-request timeout in seconds.



26
27
28
# File 'lib/mailkube/config.rb', line 26

def timeout
  @timeout
end

Instance Method Details

#build_url(path, params = {}) ⇒ String

Join a relative path onto the base URL, attach the query, and refuse any absolute URL off the base URL's origin.

Every request carries the Authorization header, so following a link that names a foreign host would hand that host the API key.

The query is attached after the origin check and only when there is one, so an absolute page link the API issued keeps its own query untouched and an unfiltered listing produces no ? at all. URI.encode_www_form — not Serialization.escape_segment — is correct here: a space in a query value is +, and a space in a path segment is %20.

Parameters:

  • path (String)

    a relative path, or an absolute URL the API itself issued.

  • params (Hash{String => String}) (defaults to: {})

    query parameters, already rendered to strings by Serialization.query.

  • (String)
  • (Hash[String, String])

Returns:

  • (String)

    the absolute URL to request.

Raises:



93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/mailkube/config.rb', line 93

def build_url(path, params = {})
  base = URI.parse(@base_url)
  resolved = base.merge(path)
  unless resolved.scheme == base.scheme && resolved.host == base.host && resolved.port == base.port
    raise ConfigurationError, "refusing to follow #{resolved}: it is not on the configured API origin"
  end

  resolved.query = URI.encode_www_form(params) unless params.empty?
  resolved.to_s
rescue URI::InvalidURIError, URI::InvalidComponentError => e
  raise ConfigurationError, "invalid URL #{path.inspect}: #{e.message}"
end

#default_headersHash{String => String}

The auth and non-browser identification headers sent on every request.

The User-Agent is required: the API rejects a request without one. It reports Mailkube::VERSION, which the gemspec also reads, so it cannot drift from the released version.

Returns:

  • (Hash{String => String})

    the default headers.



57
58
59
60
61
62
63
64
# File 'lib/mailkube/config.rb', line 57

def default_headers
  {
    "Authorization" => "Bearer #{@api_key}",
    "User-Agent" => user_agent,
    "Content-Type" => "application/json",
    "Accept" => "application/json"
  }
end

#user_agentString

This gem's token, plus any suffix a wrapping tool supplied.

The SDK token always leads, so attribution of the SDK itself never depends on what the wrapper chose to call itself.

Returns:

  • (String)

    the User-Agent value.



72
73
74
75
# File 'lib/mailkube/config.rb', line 72

def user_agent
  agent = "mailkube-ruby/#{VERSION}"
  @user_agent_suffix.empty? ? agent : "#{agent} #{@user_agent_suffix}"
end