Module: Pluggy::Util
- Defined in:
- lib/pluggy/util.rb
Overview
Naming conversion and request encoding.
Pluggy speaks camelCase; Ruby speaks snake_case. Two conversions run in opposite directions: responses are read through snake_case accessors (see PluggyObject), and request params are written from snake_case kwargs into camelCase wire keys.
The conversion is deliberately NOT round-tripped: camel_case(snake_case(x)) is lossy for acronym-bearing keys ("issuerCNPJ" -> :issuer_cnpj -> "issuerCnpj"). That is fine because only outgoing params are camelized, and those are an explicit per-service allowlist rather than something we derive from a response.
Constant Summary collapse
- ACRONYM_BOUNDARY =
"issuerCNPJName" -> "issuerCNPJ_Name"
/([A-Z\d]+)([A-Z][a-z])/- LOWER_UPPER =
"createdAt" -> "created_At"
/([a-z\d])([A-Z])/- COMMA_JOINED_PARAMS =
Comma-joined in the query string.
countriesandtypesarestyle: form, explode: falsein the spec;cnpjsis typed as a bare comma-separated string. Everything else (notablyids) repeats the key. %w[countries types cnpjs].freeze
- DATE_ONLY_PARAMS =
The spec types these as
format: date-timebut every description says "Format (yyyy-mm-dd)". The descriptions win — see plan §7. %w[from to dateFrom dateTo].freeze
- DATETIME_PARAMS =
...and this one really does want the full timestamp.
%w[createdAtFrom].freeze
- DATETIME_FORMAT =
"%Y-%m-%dT%H:%M:%S.%LZ"
Class Method Summary collapse
-
.camel_case(key) ⇒ Object
:item_id => "itemId", :oauth_redirect_uri => "oauthRedirectUri".
-
.encode_body(hash, opaque: []) ⇒ Object
Deep snake_case -> camelCase for request bodies.
- .encode_body_value(value, opaque) ⇒ Object
- .encode_query(params) ⇒ Object
-
.format_param(wire, value) ⇒ Object
Per-parameter date formatting (see DATE_ONLY_PARAMS / DATETIME_PARAMS).
-
.snake_case(key) ⇒ Object
"createdAt" => "created_at", "CET" => "cet", "hasMFA" => "has_mfa", "issuerCNPJ" => "issuer_cnpj", "payeeMCC" => "payee_mcc".
-
.stringify_keys(value) ⇒ Object
Shallow String-ify of a free-form map's keys, leaving them otherwise untouched.
- .to_date_string(value) ⇒ Object
- .to_datetime_string(value) ⇒ Object
-
.wire_key(key) ⇒ Object
Symbol keys are snake_case and get camelized.
Class Method Details
.camel_case(key) ⇒ Object
:item_id => "itemId", :oauth_redirect_uri => "oauthRedirectUri"
56 57 58 59 60 61 62 63 64 |
# File 'lib/pluggy/util.rb', line 56 def camel_case(key) k = key.to_s return k unless k.include?("_") @camel_cache[k] ||= begin head, *rest = k.split("_") head + rest.map(&:capitalize).join end end |
.encode_body(hash, opaque: []) ⇒ Object
Deep snake_case -> camelCase for request bodies.
opaque names keys whose contents must not be touched. POST /items
parameters and POST /items/id/mfa are free-form => String
maps whose keys are connector-defined ("user", "cpf", "cpf_cnpj"), so
camelizing them would silently break item creation.
135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/pluggy/util.rb', line 135 def encode_body(hash, opaque: []) (hash || {}).each_with_object({}) do |(key, value), out| next if value.nil? wire = wire_key(key) out[wire] = if opaque.include?(wire) stringify_keys(value) else encode_body_value(value, opaque) end end end |
.encode_body_value(value, opaque) ⇒ Object
149 150 151 152 153 154 155 |
# File 'lib/pluggy/util.rb', line 149 def encode_body_value(value, opaque) case value when Hash then encode_body(value, opaque: opaque) when Array then value.map { |e| e.is_a?(Hash) ? encode_body(e, opaque: opaque) : e } else value end end |
.encode_query(params) ⇒ Object
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/pluggy/util.rb', line 72 def encode_query(params) pairs = [] (params || {}).each do |key, value| next if value.nil? wire = wire_key(key) case value when Array next if value.empty? if COMMA_JOINED_PARAMS.include?(wire) pairs << [wire, value.map { |e| format_param(wire, e) }.join(",")] else value.each { |e| pairs << [wire, format_param(wire, e)] } end when Hash raise ArgumentError, "nested hash is not encodable in a query string: #{wire}" else pairs << [wire, format_param(wire, value)] end end URI.encode_www_form(pairs) end |
.format_param(wire, value) ⇒ Object
Per-parameter date formatting (see DATE_ONLY_PARAMS / DATETIME_PARAMS).
100 101 102 103 104 105 106 107 108 |
# File 'lib/pluggy/util.rb', line 100 def format_param(wire, value) if DATE_ONLY_PARAMS.include?(wire) to_date_string(value) elsif DATETIME_PARAMS.include?(wire) to_datetime_string(value) else value.to_s end end |
.snake_case(key) ⇒ Object
"createdAt" => "created_at", "CET" => "cet", "hasMFA" => "has_mfa", "issuerCNPJ" => "issuer_cnpj", "payeeMCC" => "payee_mcc"
47 48 49 50 51 52 53 |
# File 'lib/pluggy/util.rb', line 47 def snake_case(key) k = key.to_s @snake_cache[k] ||= k.gsub(ACRONYM_BOUNDARY, '\1_\2') .gsub(LOWER_UPPER, '\1_\2') .tr("-", "_") .downcase end |
.stringify_keys(value) ⇒ Object
Shallow String-ify of a free-form map's keys, leaving them otherwise untouched.
159 160 161 162 163 |
# File 'lib/pluggy/util.rb', line 159 def stringify_keys(value) return value unless value.is_a?(Hash) value.each_with_object({}) { |(k, v), out| out[k.to_s] = v } end |
.to_date_string(value) ⇒ Object
110 111 112 113 114 115 116 117 118 |
# File 'lib/pluggy/util.rb', line 110 def to_date_string(value) case value when Date then value.iso8601 when Time then value.to_date.iso8601 else # Tolerate a caller passing a full timestamp for a date-only param. value.to_s[0, 10] end end |
.to_datetime_string(value) ⇒ Object
120 121 122 123 124 125 126 127 |
# File 'lib/pluggy/util.rb', line 120 def to_datetime_string(value) case value when Time then value.utc.strftime(DATETIME_FORMAT) when DateTime then value.to_time.utc.strftime(DATETIME_FORMAT) when Date then Time.utc(value.year, value.month, value.day).strftime(DATETIME_FORMAT) else value.to_s end end |
.wire_key(key) ⇒ Object
Symbol keys are snake_case and get camelized. String keys pass through verbatim -- the escape hatch for any key our camelizer would mangle.
68 69 70 |
# File 'lib/pluggy/util.rb', line 68 def wire_key(key) key.is_a?(String) ? key : camel_case(key) end |