buchida 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d5794bc5a7586d8406ceae305adc564b6be4d45937fdf04902ff698757302a8
4
- data.tar.gz: 474b68adcfc795011bc1265a21d5b1d6c90738734e36072f807f3741d6028690
3
+ metadata.gz: 1fcf69caf904b7e44acd3566606f5f1ade948dfc6d01d094437067ae72310cbf
4
+ data.tar.gz: 7f7c9892d7b633081b75d464fe775406a33d34f6d351fdd434a32a08a1bd0cdb
5
5
  SHA512:
6
- metadata.gz: ab08556d489588178b1288a4d009e99c774ecbb50b56b5a6de7337fc8ff659fbe6a410b19986816c52bb1fe48a10994f4bc058907157e7ceba2193dc46596f53
7
- data.tar.gz: aaa14a68db35b21bacd69e9010e72e1b1f77a2b1aeb2c9a6e0377f7a5a9093f3e4fe456072920242cbb3b8a0203e98e1ea1382177f35c9f76ca7e927a81da686
6
+ metadata.gz: dc088609542a0c45a81021a4e8bcc6861893c09df6483dbf0ae26c7f3f5906dd3c05c221b2d284c13577c2a087838591331ba48212f9502d3f50c19438b98fba
7
+ data.tar.gz: 2f269621f280e5b4320be5d3d7b8079192c4686d09740db87186b51291f743c77f11c19bbd111b0c59fc0d2cbdb2f2112500d8f5b12317301044b77db4fea479
@@ -6,7 +6,7 @@ require "json"
6
6
 
7
7
  module Buchida
8
8
  class Client
9
- DEFAULT_BASE_URL = "https://api.buchida.com"
9
+ DEFAULT_BASE_URL = "https://api.buchida.com/v1"
10
10
  DEFAULT_TIMEOUT = 30
11
11
 
12
12
  attr_reader :emails, :domains, :api_keys, :webhooks, :templates, :metrics
@@ -57,8 +57,32 @@ module Buchida
57
57
  JSON.parse(response.body)
58
58
  end
59
59
 
60
+ # Request returning a single object: unwraps `{ "data" => {...} }` (204 => nil).
61
+ def request_object(method, path, body = nil)
62
+ unwrap_object(request(method, path, body))
63
+ end
64
+
65
+ # Request returning a list: unwraps `{ "data" => [...], "cursor" =>, "hasMore" => }`.
66
+ def request_list(method, path, body = nil)
67
+ resp = request(method, path, body)
68
+ if resp.is_a?(Hash)
69
+ ListResult.new(resp["data"] || [], cursor: resp["cursor"], has_more: resp["hasMore"] || resp["has_more"] || false)
70
+ elsif resp.is_a?(Array)
71
+ ListResult.new(resp)
72
+ else
73
+ resp
74
+ end
75
+ end
76
+
60
77
  private
61
78
 
79
+ def unwrap_object(resp)
80
+ return resp unless resp.is_a?(Hash)
81
+
82
+ inner = resp.key?("data") ? resp["data"] : resp
83
+ inner.is_a?(Hash) ? Result[inner] : inner
84
+ end
85
+
62
86
  def handle_error(response)
63
87
  body = begin
64
88
  JSON.parse(response.body)
@@ -66,7 +90,14 @@ module Buchida
66
90
  { "message" => response.message }
67
91
  end
68
92
 
69
- message = body["message"] || response.message
93
+ err = body["error"]
94
+ if err.is_a?(Hash)
95
+ message = err["message"] || response.message
96
+ error_code = err["code"]
97
+ else
98
+ message = body["message"] || response.message
99
+ error_code = body["code"]
100
+ end
70
101
  status = response.code.to_i
71
102
 
72
103
  case status
@@ -74,7 +105,7 @@ module Buchida
74
105
  when 404 then raise NotFoundError, message
75
106
  when 422 then raise ValidationError, message
76
107
  when 429 then raise RateLimitError, message
77
- else raise Error.new(message, status, body["code"])
108
+ else raise Error.new(message, status, error_code)
78
109
  end
79
110
  end
80
111
  end
@@ -8,11 +8,11 @@ module Buchida
8
8
  end
9
9
 
10
10
  def create(name:, permission:)
11
- @client.request(:post, "/api-keys", { name: name, permission: permission })
11
+ @client.request_object(:post, "/api-keys", { name: name, permission: permission })
12
12
  end
13
13
 
14
14
  def list
15
- @client.request(:get, "/api-keys")
15
+ @client.request_list(:get, "/api-keys")
16
16
  end
17
17
 
18
18
  def delete(id)
@@ -8,19 +8,19 @@ module Buchida
8
8
  end
9
9
 
10
10
  def create(name:)
11
- @client.request(:post, "/domains", { name: name })
11
+ @client.request_object(:post, "/domains", { name: name })
12
12
  end
13
13
 
14
14
  def list
15
- @client.request(:get, "/domains")
15
+ @client.request_list(:get, "/domains")
16
16
  end
17
17
 
18
18
  def get(id)
19
- @client.request(:get, "/domains/#{id}")
19
+ @client.request_object(:get, "/domains/#{id}")
20
20
  end
21
21
 
22
22
  def verify(id)
23
- @client.request(:post, "/domains/#{id}/verify")
23
+ @client.request_object(:post, "/domains/#{id}/verify")
24
24
  end
25
25
  end
26
26
  end
@@ -7,20 +7,21 @@ module Buchida
7
7
  @client = client
8
8
  end
9
9
 
10
- def send(from:, to:, subject:, html: nil, text: nil, reply_to: nil, cc: nil, bcc: nil, tags: nil, scheduled_at: nil)
10
+ def send(from:, to:, subject:, html: nil, text: nil, reply_to: nil, cc: nil, bcc: nil, tags: nil, scheduled_at: nil, from_name: nil)
11
11
  body = { from: from, to: to, subject: subject }
12
+ body[:from_name] = from_name if from_name
12
13
  body[:html] = html if html
13
14
  body[:text] = text if text
14
- body[:replyTo] = reply_to if reply_to
15
+ body[:reply_to] = reply_to if reply_to
15
16
  body[:cc] = cc if cc
16
17
  body[:bcc] = bcc if bcc
17
18
  body[:tags] = tags if tags
18
- body[:scheduledAt] = scheduled_at if scheduled_at
19
- @client.request(:post, "/emails", body)
19
+ body[:scheduled_at] = scheduled_at if scheduled_at
20
+ @client.request_object(:post, "/emails", body)
20
21
  end
21
22
 
22
23
  def get(id)
23
- @client.request(:get, "/emails/#{id}")
24
+ @client.request_object(:get, "/emails/#{id}")
24
25
  end
25
26
 
26
27
  def list(cursor: nil, limit: nil, status: nil, from: nil, to: nil)
@@ -32,15 +33,15 @@ module Buchida
32
33
  params[:to] = to if to
33
34
  qs = URI.encode_www_form(params) unless params.empty?
34
35
  path = qs ? "/emails?#{qs}" : "/emails"
35
- @client.request(:get, path)
36
+ @client.request_list(:get, path)
36
37
  end
37
38
 
38
39
  def cancel(id)
39
- @client.request(:post, "/emails/#{id}/cancel")
40
+ @client.request_object(:post, "/emails/#{id}/cancel")
40
41
  end
41
42
 
42
43
  def send_batch(emails)
43
- @client.request(:post, "/emails/batch", emails)
44
+ @client.request_list(:post, "/emails/batch", emails)
44
45
  end
45
46
  end
46
47
  end
@@ -8,10 +8,11 @@ module Buchida
8
8
  end
9
9
 
10
10
  def get(from:, to:, granularity: nil)
11
- params = { from: from, to: to }
11
+ # The API filters on from_date/to_date; plain from/to were silently ignored.
12
+ params = { from_date: from, to_date: to }
12
13
  params[:granularity] = granularity if granularity
13
14
  qs = URI.encode_www_form(params)
14
- @client.request(:get, "/metrics?#{qs}")
15
+ @client.request_object(:get, "/metrics?#{qs}")
15
16
  end
16
17
  end
17
18
  end
@@ -8,11 +8,11 @@ module Buchida
8
8
  end
9
9
 
10
10
  def list
11
- @client.request(:get, "/templates")
11
+ @client.request_list(:get, "/templates")
12
12
  end
13
13
 
14
14
  def get(id)
15
- @client.request(:get, "/templates/#{id}")
15
+ @client.request_object(:get, "/templates/#{id}")
16
16
  end
17
17
  end
18
18
  end
@@ -8,11 +8,11 @@ module Buchida
8
8
  end
9
9
 
10
10
  def create(url:, events:)
11
- @client.request(:post, "/webhooks", { url: url, events: events })
11
+ @client.request_object(:post, "/webhooks", { url: url, events: events })
12
12
  end
13
13
 
14
14
  def list
15
- @client.request(:get, "/webhooks")
15
+ @client.request_list(:get, "/webhooks")
16
16
  end
17
17
 
18
18
  def delete(id)
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Buchida
4
+ # Deprecation helper for the pre-0.2 raw-envelope access (`res["data"]`).
5
+ module Legacy
6
+ @warned = false
7
+
8
+ def self.warn_once(what)
9
+ return if @warned
10
+
11
+ @warned = true
12
+ Kernel.warn("[buchida] DEPRECATION: #{what} is deprecated. Since buchida 0.2 the SDK " \
13
+ "returns the unwrapped payload; use the result directly.")
14
+ end
15
+
16
+ def self.data_key?(key)
17
+ key == "data" || key == :data
18
+ end
19
+ end
20
+
21
+ # An unwrapped single-object response (a Hash with String keys, as before).
22
+ # `result["data"]` / `result[:data]` still return the same object, with a one-time
23
+ # deprecation warning, so code written against the raw `{ "data" => {...} }` envelope keeps working.
24
+ # No self-reference is stored, so `to_json` / `JSON.generate` are unaffected.
25
+ class Result < Hash
26
+ REAL_KEY = Hash.instance_method(:key?)
27
+
28
+ def [](key)
29
+ if Legacy.data_key?(key) && !REAL_KEY.bind(self).call("data")
30
+ Legacy.warn_once("result[\"data\"]")
31
+ return self
32
+ end
33
+ super
34
+ end
35
+
36
+ def key?(key)
37
+ Legacy.data_key?(key) || super
38
+ end
39
+ alias_method :has_key?, :key?
40
+ alias_method :include?, :key?
41
+ alias_method :member?, :key?
42
+
43
+ def fetch(key, *args, &block)
44
+ if Legacy.data_key?(key) && !REAL_KEY.bind(self).call("data")
45
+ Legacy.warn_once("result.fetch(\"data\")")
46
+ return self
47
+ end
48
+ super
49
+ end
50
+
51
+ def dig(key, *rest)
52
+ value = self[key]
53
+ rest.empty? || value.nil? ? value : value.dig(*rest)
54
+ end
55
+ end
56
+
57
+ # An unwrapped list response: an Array of items plus `cursor` and `has_more`.
58
+ # The old envelope keys (`res["data"]`, `res["cursor"]`, `res["hasMore"]`) still work, with a
59
+ # one-time deprecation warning.
60
+ class ListResult < Array
61
+ attr_reader :cursor, :has_more
62
+
63
+ def initialize(items = [], cursor: nil, has_more: false)
64
+ super(items)
65
+ @cursor = cursor
66
+ @has_more = has_more
67
+ end
68
+
69
+ def data
70
+ self
71
+ end
72
+
73
+ def has_more?
74
+ @has_more ? true : false
75
+ end
76
+
77
+ LEGACY_KEYS = %w[data cursor hasMore has_more].freeze
78
+
79
+ def self.legacy_key?(key)
80
+ (key.is_a?(String) || key.is_a?(Symbol)) && LEGACY_KEYS.include?(key.to_s)
81
+ end
82
+
83
+ def key?(key)
84
+ ListResult.legacy_key?(key)
85
+ end
86
+ alias_method :has_key?, :key?
87
+
88
+ def include?(item)
89
+ ListResult.legacy_key?(item) || super
90
+ end
91
+
92
+ def fetch(key, *args, &block)
93
+ return self[key] if ListResult.legacy_key?(key)
94
+
95
+ super
96
+ end
97
+
98
+ def dig(key, *rest)
99
+ if ListResult.legacy_key?(key)
100
+ value = self[key]
101
+ return rest.empty? || value.nil? ? value : value.dig(*rest)
102
+ end
103
+
104
+ super
105
+ end
106
+
107
+ def [](*args)
108
+ key = args.first
109
+ if args.length == 1 && (key.is_a?(String) || key.is_a?(Symbol))
110
+ case key.to_s
111
+ when "data" then (Legacy.warn_once("list[\"data\"]"); return self)
112
+ when "cursor" then (Legacy.warn_once("list[\"cursor\"]"); return @cursor)
113
+ when "hasMore", "has_more" then (Legacy.warn_once("list[\"hasMore\"]"); return @has_more)
114
+ end
115
+ return nil
116
+ end
117
+ super
118
+ end
119
+ end
120
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Buchida
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/buchida.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "buchida/version"
4
4
  require_relative "buchida/errors"
5
+ require_relative "buchida/response"
5
6
  require_relative "buchida/client"
6
7
  require_relative "buchida/resources/emails"
7
8
  require_relative "buchida/resources/domains"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: buchida
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - buchida
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-12 00:00:00.000000000 Z
11
+ date: 2026-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -54,6 +54,7 @@ files:
54
54
  - lib/buchida/resources/metrics.rb
55
55
  - lib/buchida/resources/templates.rb
56
56
  - lib/buchida/resources/webhooks.rb
57
+ - lib/buchida/response.rb
57
58
  - lib/buchida/version.rb
58
59
  homepage: https://github.com/Vyblor/buchida-ruby
59
60
  licenses: