squake 0.2.4 → 0.3.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 +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +75 -46
- data/lib/squake/calculation.rb +16 -8
- data/lib/squake/calculation_with_pricing.rb +15 -7
- data/lib/squake/client.rb +4 -4
- data/lib/squake/config.rb +5 -5
- data/lib/squake/errors/api_error_result.rb +22 -0
- data/lib/squake/errors/api_error_source.rb +12 -0
- data/lib/squake/model/carbon.rb +2 -2
- data/lib/squake/model/price.rb +7 -9
- data/lib/squake/model/pricing.rb +8 -5
- data/lib/squake/model/product.rb +4 -4
- data/lib/squake/products.rb +15 -7
- data/lib/squake/purchase.rb +46 -21
- data/lib/squake/return.rb +64 -0
- data/lib/squake/version.rb +1 -1
- data/lib/squake.rb +29 -4
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f7838d5d10515e9d94811494249d36e789e441a04ab31f7e94c83c306a9fb2bf
|
4
|
+
data.tar.gz: 61d6f7415ec816567ebf260493d43f1a3515090b9062e1740b339f4eb40de405
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 314d29865f690a9d4f7faf867269686dc5b184e9e36aa988d5ea19c60b75d608e09d9e90ccd086f733cfb01dd753c8c8b036a406cb48f1d3444d61f57b049fdc
|
7
|
+
data.tar.gz: 1234e584e92277e455c8d3094b3624e54e37b972af10c27c1f6836ce5bb4ca54f92f2b548fe95be5dca2531b6c9d7f0a87072aae1fb0afd4e5f06be163287394
|
data/CHANGELOG.md
CHANGED
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [0.3.0] - 2023-06-21
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
* added `Squake.configure`
|
15
|
+
* introduced global config allowig to omit the `client` from all service calls
|
16
|
+
|
17
|
+
### Changed
|
18
|
+
|
19
|
+
* All public services now return a `Return` class that contains the actual data or an error object. This replaces the throwing of errors for control-flow.
|
20
|
+
|
21
|
+
## [0.2.1 - 0.2.4] - 2023-06-21
|
22
|
+
|
23
|
+
### Fixed
|
24
|
+
|
25
|
+
* Boilerplate bugs
|
26
|
+
|
10
27
|
## [0.2.0] - 2023-06-15
|
11
28
|
|
12
29
|
### Added
|
data/README.md
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
Find the documentation here: [docs](https://docs.squake.earth/).
|
4
4
|
|
5
|
+
This gem follows SemVer, however only after a stable release 1.0.0 is made.
|
6
|
+
|
5
7
|
## Installation
|
6
8
|
|
7
9
|
Via rubygems:
|
@@ -10,10 +12,10 @@ Via rubygems:
|
|
10
12
|
gem 'squake'
|
11
13
|
```
|
12
14
|
|
13
|
-
|
15
|
+
Test the latest version via git:
|
14
16
|
|
15
17
|
```ruby
|
16
|
-
gem 'squake', git: 'git@github.com:squake-earth/squake-ruby'
|
18
|
+
gem 'squake', git: 'git@github.com:squake-earth/squake-ruby', branch: :main
|
17
19
|
```
|
18
20
|
|
19
21
|
## Auth
|
@@ -30,7 +32,9 @@ You need a different API Key for production and sandbox.
|
|
30
32
|
|
31
33
|
## Usage
|
32
34
|
|
33
|
-
Initialize the client
|
35
|
+
### Initialize the client
|
36
|
+
|
37
|
+
either explicitly anywhere in the app
|
34
38
|
|
35
39
|
```ruby
|
36
40
|
config = Squake::Config.new(
|
@@ -46,9 +50,34 @@ config = Squake::Config.new(
|
|
46
50
|
client = Squake::Client.new(config: config)
|
47
51
|
```
|
48
52
|
|
49
|
-
|
53
|
+
or once globally in e.g. an initializer
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
Squake.configure do |config|
|
57
|
+
config.api_key = ENV.fetch('SQUAKE_API_KEY') # optional if ENV var `SQUAKE_API_KEY` is set
|
58
|
+
config.sandbox_mode = true # set to `false` for production
|
59
|
+
config.keep_alive_timeout = 30 # optional, default: 30
|
60
|
+
config.logger = Logger.new($stdout) # Set this to `Rails.logger` when using Rails
|
61
|
+
config.enforced_api_base = nil # for testing to e.g. point to a local server
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
If you have the API Key in an ENV var named `SQUAKE_API_KEY`, you don't need any further config.
|
66
|
+
|
67
|
+
### Fetch available products
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
context = Squake::Products.get
|
71
|
+
products = context.result # [Squake::Model::Product]
|
72
|
+
```
|
73
|
+
|
74
|
+
### Define items you want emissions to be computed
|
75
|
+
|
76
|
+
Find all available item types and methodologies in the [docs](https://docs-v2.squake.earth/group/endpoint-calculations).
|
50
77
|
|
51
78
|
```ruby
|
79
|
+
# NOTE: some items are also available as typed objects, found at `lib/squake/model/items/**/*.rb`
|
80
|
+
# where `.../items/private_jet/squake` is the item for type "private_jet" and methodology "squake".
|
52
81
|
items = [
|
53
82
|
{
|
54
83
|
type: 'flight',
|
@@ -59,69 +88,81 @@ items = [
|
|
59
88
|
number_of_travellers: 2,
|
60
89
|
aircraft_type: '350',
|
61
90
|
external_reference: 'booking-id',
|
62
|
-
}
|
91
|
+
},
|
92
|
+
Squake::Model::Items::PrivateJet::Squake.new(
|
93
|
+
origin: 'SIN',
|
94
|
+
destination: 'HND',
|
95
|
+
external_reference: 'my-booking-id',
|
96
|
+
identifier: 'P180',
|
97
|
+
identifier_kind: 'ICAO',
|
98
|
+
),
|
63
99
|
]
|
100
|
+
```
|
101
|
+
|
102
|
+
### Calculate emissions (without price quote)
|
64
103
|
|
65
|
-
|
66
|
-
|
67
|
-
client: client, # required
|
104
|
+
```ruby
|
105
|
+
context = Squake::Calculation.create(
|
68
106
|
items: items, # required
|
69
107
|
carbon_unit: 'gram', # optional, default: 'gram', other options: 'kilogram', 'tonne'
|
70
108
|
expand: [], # optional, default: [], allowed values: 'items' to enrich the response
|
109
|
+
client: client, # optional
|
71
110
|
)
|
111
|
+
|
112
|
+
# alias: context.failed?
|
113
|
+
if context.success?
|
114
|
+
context.result # Squake::Model::Carbon
|
115
|
+
else
|
116
|
+
context.errors # [Squake::Errors::APIErrorResult]
|
117
|
+
end
|
72
118
|
```
|
73
119
|
|
74
|
-
Calculate emissions and include a price quote
|
120
|
+
### Calculate emissions and include a price quote
|
75
121
|
|
76
122
|
```ruby
|
77
|
-
|
78
|
-
# https://docs-v2.squake.earth/group/endpoint-calculations
|
79
|
-
items = [
|
80
|
-
{
|
81
|
-
type: 'flight',
|
82
|
-
methodology: 'ICAO',
|
83
|
-
origin: 'BER',
|
84
|
-
destination: 'SIN',
|
85
|
-
booking_class: 'economy',
|
86
|
-
number_of_travellers: 2,
|
87
|
-
aircraft_type: '350',
|
88
|
-
external_reference: 'booking-id',
|
89
|
-
}
|
90
|
-
]
|
91
|
-
|
92
|
-
# returns Squake::Model::Pricing
|
93
|
-
pricing = Squake::CalculationWithPricing.quote(
|
94
|
-
client: client, # required
|
123
|
+
context = Squake::CalculationWithPricing.quote(
|
95
124
|
items: items, # required
|
96
125
|
product: 'product-id', # required
|
97
126
|
currency: 'EUR', # optional, default: 'EUR'
|
98
127
|
carbon_unit: 'gram', # optional, default: 'gram', other options: 'kilogram', 'tonne'
|
99
128
|
expand: [], # optional, default: [], allowed values: 'items', 'product', 'price' to enrich the response
|
129
|
+
client: client, # optional
|
100
130
|
)
|
131
|
+
|
132
|
+
if context.success?
|
133
|
+
context.result # Squake::Model::Pricing
|
134
|
+
else
|
135
|
+
context.errors # [Squake::Errors::APIErrorResult]
|
136
|
+
end
|
101
137
|
```
|
102
138
|
|
103
|
-
Place a purchase order
|
139
|
+
### Place a purchase order
|
140
|
+
|
141
|
+
by default the library injects a `SecureRandom.uuid` as `external_reference` to ensure idempotency, i.e. you can safely retry the request if it fails.
|
104
142
|
|
105
143
|
```ruby
|
106
144
|
uuid = SecureRandom.uuid
|
107
145
|
|
108
146
|
# returns Squake::Model::Purchase
|
109
|
-
|
110
|
-
client: client, # required
|
147
|
+
context = Squake::Purchase.create(
|
111
148
|
pricing: pricing.id, # required, from above
|
112
149
|
external_reference: uuid, # optional, default: SecureRandom.uuid, used for idempotency, if given, MUST be unique
|
150
|
+
client: client, # optional
|
113
151
|
)
|
114
152
|
|
153
|
+
context.result # Squake::Model::Purchase
|
154
|
+
context.errors # [Squake::Errors::APIErrorResult]
|
155
|
+
|
115
156
|
# retrieve the purchase later
|
116
157
|
Squake::Purchase.retrieve(
|
117
|
-
client: client, # required
|
118
158
|
id: purchase.id, # required
|
159
|
+
client: client, # optional
|
119
160
|
)
|
120
161
|
|
121
162
|
# within 14 days, you can cancel the purchase worry-free
|
122
163
|
Squake::Purchase.cancel(
|
123
|
-
client: client, # required
|
124
164
|
id: purchase.id, # required
|
165
|
+
client: client, # optional
|
125
166
|
)
|
126
167
|
```
|
127
168
|
|
@@ -133,22 +174,10 @@ Please commit small and focused PRs with descriptive commit messages. If you are
|
|
133
174
|
|
134
175
|
## Publishing a new version
|
135
176
|
|
136
|
-
|
137
|
-
|
138
|
-
```shell
|
139
|
-
---
|
140
|
-
:rubygems_squake: rubygems_xxx
|
141
|
-
```
|
142
|
-
|
143
|
-
Then run:
|
177
|
+
run
|
144
178
|
|
145
179
|
```shell
|
146
180
|
bin/release
|
147
181
|
```
|
148
182
|
|
149
|
-
which will guide you through the release process
|
150
|
-
|
151
|
-
* create a new git tag from current main
|
152
|
-
* create a GitHub release from the tag
|
153
|
-
* build the gem locally
|
154
|
-
* publish the gem to RubyGems
|
183
|
+
which will guide you through the release process.
|
data/lib/squake/calculation.rb
CHANGED
@@ -10,13 +10,13 @@ module Squake
|
|
10
10
|
|
11
11
|
sig do
|
12
12
|
params(
|
13
|
-
client: Squake::Client,
|
14
13
|
items: T::Array[T.any(Squake::Model::Items::BaseType, T::Hash[T.any(String, Symbol), T.untyped])],
|
15
14
|
carbon_unit: String,
|
16
15
|
expand: T::Array[String],
|
17
|
-
|
16
|
+
client: Squake::Client,
|
17
|
+
).returns(Squake::Return[Squake::Model::Carbon])
|
18
18
|
end
|
19
|
-
def self.create(
|
19
|
+
def self.create(items:, carbon_unit: 'gram', expand: [], client: Squake::Client.new)
|
20
20
|
# @TODO: add typed objects for all possible items. Until then, we allow either a Hash or a T::Struct
|
21
21
|
items = items.map do |item|
|
22
22
|
item.is_a?(T::Struct) ? item.serialize : item
|
@@ -26,16 +26,24 @@ module Squake
|
|
26
26
|
path: ENDPOINT,
|
27
27
|
method: :post,
|
28
28
|
params: {
|
29
|
-
items: items
|
29
|
+
items: items,
|
30
30
|
carbon_unit: carbon_unit,
|
31
31
|
expand: expand,
|
32
32
|
},
|
33
33
|
)
|
34
|
-
raise Squake::APIError.new(response: result) unless result.success?
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
if result.success?
|
36
|
+
carbon = Squake::Model::Carbon.from_api_response(
|
37
|
+
T.cast(result.body, T::Hash[Symbol, T.untyped]),
|
38
|
+
)
|
39
|
+
Return.new(result: carbon)
|
40
|
+
else
|
41
|
+
error = Squake::Errors::APIErrorResult.new(
|
42
|
+
code: :"api_error_#{result.code}",
|
43
|
+
detail: result.error_message,
|
44
|
+
)
|
45
|
+
Return.new(errors: [error])
|
46
|
+
end
|
39
47
|
end
|
40
48
|
end
|
41
49
|
end
|
@@ -10,15 +10,15 @@ module Squake
|
|
10
10
|
|
11
11
|
sig do
|
12
12
|
params(
|
13
|
-
client: Squake::Client,
|
14
13
|
items: T::Array[T.any(Squake::Model::Items::BaseType, T::Hash[T.any(String, Symbol), T.untyped])],
|
15
14
|
product: String,
|
16
15
|
currency: String,
|
17
16
|
carbon_unit: String,
|
18
17
|
expand: T::Array[String],
|
19
|
-
|
18
|
+
client: Squake::Client,
|
19
|
+
).returns(Squake::Return[Squake::Model::Pricing])
|
20
20
|
end
|
21
|
-
def self.quote(
|
21
|
+
def self.quote(items:, product:, currency: 'EUR', carbon_unit: 'gram', expand: [], client: Squake::Client.new)
|
22
22
|
# @TODO: add typed objects for all possible items. Until then, we allow either a Hash or a T::Struct
|
23
23
|
items = items.map do |item|
|
24
24
|
item.is_a?(T::Struct) ? item.serialize : item
|
@@ -35,11 +35,19 @@ module Squake
|
|
35
35
|
expand: expand,
|
36
36
|
},
|
37
37
|
)
|
38
|
-
raise Squake::APIError.new(response: result) unless result.success?
|
39
38
|
|
40
|
-
|
41
|
-
|
42
|
-
|
39
|
+
if result.success?
|
40
|
+
pricing = Squake::Model::Pricing.from_api_response(
|
41
|
+
T.cast(result.body, T::Hash[Symbol, T.untyped]),
|
42
|
+
)
|
43
|
+
Return.new(result: pricing)
|
44
|
+
else
|
45
|
+
error = Squake::Errors::APIErrorResult.new(
|
46
|
+
code: :"api_error_#{result.code}",
|
47
|
+
detail: result.error_message,
|
48
|
+
)
|
49
|
+
Return.new(errors: [error])
|
50
|
+
end
|
43
51
|
end
|
44
52
|
end
|
45
53
|
end
|
data/lib/squake/client.rb
CHANGED
@@ -14,9 +14,9 @@ module Squake
|
|
14
14
|
)
|
15
15
|
end
|
16
16
|
|
17
|
-
sig { params(config: Squake::Config).void }
|
18
|
-
def initialize(config:)
|
19
|
-
@config = config
|
17
|
+
sig { params(config: T.nilable(Squake::Config)).void }
|
18
|
+
def initialize(config: nil)
|
19
|
+
@config = T.let(config || T.must(Squake.configuration), Squake::Config)
|
20
20
|
end
|
21
21
|
|
22
22
|
sig do
|
@@ -53,7 +53,7 @@ module Squake
|
|
53
53
|
end
|
54
54
|
private def execute_request(method:, path:, headers: {}, params: nil, api_base: nil, api_key: nil)
|
55
55
|
api_base ||= @config.api_base
|
56
|
-
api_key ||= @config.api_key
|
56
|
+
api_key ||= T.must(@config.api_key)
|
57
57
|
|
58
58
|
body_params = nil
|
59
59
|
query_params = nil
|
data/lib/squake/config.rb
CHANGED
@@ -10,11 +10,11 @@ module Squake
|
|
10
10
|
DEFAULT_BASE_URL = T.let('https://api.squake.earth', String)
|
11
11
|
SANDBOX_BASE_URL = T.let('https://api.sandbox.squake.earth', String)
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
prop :api_key, T.nilable(String)
|
14
|
+
prop :keep_alive_timeout, Integer, default: 30
|
15
|
+
prop :logger, ::Logger, factory: -> { ::Logger.new($stdout) }
|
16
|
+
prop :sandbox_mode, T::Boolean, default: true
|
17
|
+
prop :enforced_api_base, T.nilable(String)
|
18
18
|
|
19
19
|
sig { returns(String) }
|
20
20
|
def api_base
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative 'api_error_source'
|
5
|
+
|
6
|
+
module Squake
|
7
|
+
module Errors
|
8
|
+
#
|
9
|
+
# https://jsonapi.org/format/#errors
|
10
|
+
#
|
11
|
+
# > Error objects MUST be returned as an array keyed by errors in the top level of a JSON:API document.
|
12
|
+
#
|
13
|
+
class APIErrorResult < T::Struct
|
14
|
+
const :code, Symbol # An application-specific error code
|
15
|
+
|
16
|
+
# A human-readable explanation specific to this occurrence of the problem.
|
17
|
+
# Like title, this field's value can be localized.
|
18
|
+
const :detail, T.nilable(String)
|
19
|
+
const :source, T.nilable(APIErrorSource)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Squake
|
5
|
+
module Errors
|
6
|
+
class APIErrorSource < T::Struct
|
7
|
+
const :attribute, T.any(Symbol, String)
|
8
|
+
const :model, T.nilable(String), default: nil
|
9
|
+
const :id, T.nilable(String), default: nil # external reference if given
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/squake/model/carbon.rb
CHANGED
@@ -15,8 +15,8 @@ module Squake
|
|
15
15
|
sig { params(response_body: T::Hash[Symbol, T.untyped]).returns(Squake::Model::Carbon) }
|
16
16
|
def self.from_api_response(response_body)
|
17
17
|
Squake::Model::Carbon.new(
|
18
|
-
quantity: BigDecimal(String(response_body.fetch(:
|
19
|
-
unit: CarbonUnit.deserialize(response_body.fetch(:
|
18
|
+
quantity: BigDecimal(String(response_body.fetch(:carbon_quantity))),
|
19
|
+
unit: CarbonUnit.deserialize(response_body.fetch(:carbon_unit)),
|
20
20
|
items: response_body.fetch(:items, nil),
|
21
21
|
)
|
22
22
|
end
|
data/lib/squake/model/price.rb
CHANGED
@@ -9,21 +9,19 @@ module Squake
|
|
9
9
|
const :id, String
|
10
10
|
const :product, String
|
11
11
|
const :unit_amount, BigDecimal
|
12
|
-
const :valid_from,
|
12
|
+
const :valid_from, Date
|
13
13
|
const :carbon_unit, String
|
14
14
|
const :currency, String
|
15
15
|
|
16
16
|
sig { params(response_body: T::Hash[Symbol, T.untyped]).returns(Squake::Model::Price) }
|
17
17
|
def self.from_api_response(response_body)
|
18
|
-
product = response_body.fetch(:price, {})
|
19
|
-
|
20
18
|
Squake::Model::Price.new(
|
21
|
-
id:
|
22
|
-
product:
|
23
|
-
unit_amount:
|
24
|
-
valid_from: Date.parse(
|
25
|
-
carbon_unit:
|
26
|
-
currency:
|
19
|
+
id: response_body.fetch(:id),
|
20
|
+
product: response_body.fetch(:product),
|
21
|
+
unit_amount: BigDecimal(response_body.fetch(:unit_amount).to_s),
|
22
|
+
valid_from: Date.parse(response_body.fetch(:valid_from)),
|
23
|
+
carbon_unit: response_body.fetch(:carbon_unit),
|
24
|
+
currency: response_body.fetch(:currency),
|
27
25
|
)
|
28
26
|
end
|
29
27
|
end
|
data/lib/squake/model/pricing.rb
CHANGED
@@ -24,8 +24,8 @@ module Squake
|
|
24
24
|
const :carbon_quantity, BigDecimal
|
25
25
|
const :carbon_unit, String
|
26
26
|
const :payment_link, T.nilable(String)
|
27
|
-
const :price, Squake::Model::Price
|
28
|
-
const :product, Squake::Model::Product
|
27
|
+
const :price, T.any(Squake::Model::Price, String)
|
28
|
+
const :product, T.any(Squake::Model::Product, String)
|
29
29
|
const :valid_until, Date
|
30
30
|
const :currency, String, default: 'EUR'
|
31
31
|
const :total, Integer
|
@@ -33,10 +33,13 @@ module Squake
|
|
33
33
|
|
34
34
|
sig { params(response_body: T::Hash[Symbol, T.untyped]).returns(Squake::Model::Pricing) }
|
35
35
|
def self.from_api_response(response_body)
|
36
|
-
|
37
|
-
|
36
|
+
price_or_id = T.let(response_body.fetch(:price), T.any(String, T::Hash[Symbol, T.untyped]))
|
37
|
+
price = price_or_id.is_a?(Hash) ? Squake::Model::Price.from_api_response(price_or_id) : price_or_id
|
38
38
|
|
39
|
-
|
39
|
+
product_or_id = T.let(response_body.fetch(:product), T.any(String, T::Hash[Symbol, T.untyped]))
|
40
|
+
product = product_or_id.is_a?(Hash) ? Squake::Model::Product.from_api_response(product_or_id) : product_or_id
|
41
|
+
|
42
|
+
items = response_body.fetch(:items, []) || []
|
40
43
|
items.map! do |item|
|
41
44
|
item[:carbon_quantity] = item.fetch(:carbon_quantity).to_d
|
42
45
|
item[:distance] = item.fetch(:distance).to_d
|
data/lib/squake/model/product.rb
CHANGED
@@ -8,14 +8,14 @@ module Squake
|
|
8
8
|
|
9
9
|
const :id, String
|
10
10
|
const :title, String
|
11
|
+
const :certifications, T::Array[String], default: []
|
11
12
|
|
12
13
|
sig { params(response_body: T::Hash[Symbol, T.untyped]).returns(Squake::Model::Product) }
|
13
14
|
def self.from_api_response(response_body)
|
14
|
-
product = response_body.fetch(:product, {})
|
15
|
-
|
16
15
|
Squake::Model::Product.new(
|
17
|
-
id:
|
18
|
-
title:
|
16
|
+
id: response_body.fetch(:id),
|
17
|
+
title: response_body.fetch(:title),
|
18
|
+
certifications: response_body.fetch(:certifications, []),
|
19
19
|
)
|
20
20
|
end
|
21
21
|
end
|
data/lib/squake/products.rb
CHANGED
@@ -11,12 +11,12 @@ module Squake
|
|
11
11
|
|
12
12
|
sig do
|
13
13
|
params(
|
14
|
-
client: Squake::Client,
|
15
|
-
locale: String,
|
16
14
|
product_id: T.nilable(String),
|
17
|
-
|
15
|
+
locale: String,
|
16
|
+
client: Squake::Client,
|
17
|
+
).returns(Squake::Return[T::Array[Squake::Model::Product]])
|
18
18
|
end
|
19
|
-
def self.get(
|
19
|
+
def self.get(product_id: nil, locale: DEFAULT_LOCALE, client: Squake::Client.new)
|
20
20
|
path = product_id.nil? ? ENDPOINT : "#{ENDPOINT}/#{product_id}"
|
21
21
|
|
22
22
|
result = client.call(
|
@@ -26,10 +26,18 @@ module Squake
|
|
26
26
|
locale: locale,
|
27
27
|
},
|
28
28
|
)
|
29
|
-
raise Squake::APIError.new(response: result) unless result.success?
|
30
29
|
|
31
|
-
|
32
|
-
|
30
|
+
if result.success?
|
31
|
+
products = T.cast(Array(result.body), T::Array[T::Hash[Symbol, T.untyped]]).map do |product_data|
|
32
|
+
Squake::Model::Product.from_api_response(product_data)
|
33
|
+
end
|
34
|
+
Return.new(result: products)
|
35
|
+
else
|
36
|
+
error = Squake::Errors::APIErrorResult.new(
|
37
|
+
code: :"api_error_#{result.code}",
|
38
|
+
detail: result.error_message,
|
39
|
+
)
|
40
|
+
Return.new(errors: [error])
|
33
41
|
end
|
34
42
|
end
|
35
43
|
end
|
data/lib/squake/purchase.rb
CHANGED
@@ -11,16 +11,16 @@ module Squake
|
|
11
11
|
# rubocop:disable Metrics/ParameterLists, Layout/LineLength
|
12
12
|
sig do
|
13
13
|
params(
|
14
|
-
client: Squake::Client,
|
15
14
|
pricing: String,
|
16
15
|
confirmation_document: T.nilable(T::Hash[Symbol, T.untyped]),
|
17
16
|
certificate_document: T.nilable(T::Hash[Symbol, T.untyped]),
|
18
17
|
metadata: T.nilable(T::Hash[Symbol, T.untyped]),
|
19
18
|
external_reference: String, # used for idempotency, if given, MUST be unique
|
20
19
|
expand: T::Array[String],
|
21
|
-
|
20
|
+
client: Squake::Client,
|
21
|
+
).returns(Squake::Return[Squake::Model::Purchase])
|
22
22
|
end
|
23
|
-
def self.create(
|
23
|
+
def self.create(pricing:, confirmation_document: nil, certificate_document: nil, metadata: nil, external_reference: SecureRandom.uuid, expand: [], client: Squake::Client.new)
|
24
24
|
result = client.call(
|
25
25
|
path: ENDPOINT,
|
26
26
|
method: :post,
|
@@ -33,48 +33,73 @@ module Squake
|
|
33
33
|
expand: expand,
|
34
34
|
},
|
35
35
|
)
|
36
|
-
raise Squake::APIError.new(response: result) unless result.success?
|
37
36
|
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
if result.success?
|
38
|
+
purchase = Squake::Model::Purchase.from_api_response(
|
39
|
+
T.cast(result.body, T::Hash[Symbol, T.untyped]),
|
40
|
+
)
|
41
|
+
Return.new(result: purchase)
|
42
|
+
else
|
43
|
+
error = Squake::Errors::APIErrorResult.new(
|
44
|
+
code: :"api_error_#{result.code}",
|
45
|
+
detail: result.error_message,
|
46
|
+
)
|
47
|
+
Return.new(errors: [error])
|
48
|
+
end
|
41
49
|
end
|
42
50
|
# rubocop:enable Metrics/ParameterLists, Layout/LineLength
|
43
51
|
|
44
52
|
sig do
|
45
53
|
params(
|
46
|
-
client: Squake::Client,
|
47
54
|
id: String,
|
48
|
-
|
55
|
+
client: Squake::Client,
|
56
|
+
).returns(T.nilable(Squake::Return[Squake::Model::Purchase]))
|
49
57
|
end
|
50
|
-
def self.retrieve(
|
58
|
+
def self.retrieve(id:, client: Squake::Client.new)
|
51
59
|
result = client.call(
|
52
60
|
path: "#{ENDPOINT}/#{id}",
|
53
61
|
)
|
54
62
|
return nil if result.code == 404
|
55
|
-
raise Squake::APIError.new(response: result) unless result.success?
|
56
63
|
|
57
|
-
|
58
|
-
|
59
|
-
|
64
|
+
if result.success?
|
65
|
+
purchase = Squake::Model::Purchase.from_api_response(
|
66
|
+
T.cast(result.body, T::Hash[Symbol, T.untyped]),
|
67
|
+
)
|
68
|
+
Return.new(result: purchase)
|
69
|
+
else
|
70
|
+
error = Squake::Errors::APIErrorResult.new(
|
71
|
+
code: :"api_error_#{result.code}",
|
72
|
+
detail: result.error_message,
|
73
|
+
)
|
74
|
+
Return.new(errors: [error])
|
75
|
+
end
|
60
76
|
end
|
61
77
|
|
62
78
|
sig do
|
63
79
|
params(
|
64
|
-
client: Squake::Client,
|
65
80
|
id: String,
|
66
|
-
|
81
|
+
client: Squake::Client,
|
82
|
+
).returns(T.nilable(Squake::Return[Squake::Model::Purchase]))
|
67
83
|
end
|
68
|
-
def self.cancel(
|
84
|
+
def self.cancel(id:, client: Squake::Client.new)
|
69
85
|
result = client.call(
|
70
86
|
path: "#{ENDPOINT}/#{id}/cancel",
|
71
87
|
method: :post,
|
72
88
|
)
|
73
|
-
|
89
|
+
return nil if result.code == 404
|
74
90
|
|
75
|
-
|
76
|
-
|
77
|
-
|
91
|
+
if result.success?
|
92
|
+
purchase = Squake::Model::Purchase.from_api_response(
|
93
|
+
T.cast(result.body, T::Hash[Symbol, T.untyped]),
|
94
|
+
)
|
95
|
+
Return.new(result: purchase)
|
96
|
+
else
|
97
|
+
error = Squake::Errors::APIErrorResult.new(
|
98
|
+
code: :"api_error_#{result.code}",
|
99
|
+
detail: result.error_message,
|
100
|
+
)
|
101
|
+
Return.new(errors: [error])
|
102
|
+
end
|
78
103
|
end
|
79
104
|
end
|
80
105
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative 'errors/api_error_result'
|
5
|
+
|
6
|
+
module Squake
|
7
|
+
class Return
|
8
|
+
class Failure < StandardError; end
|
9
|
+
|
10
|
+
FAILURE_MSG_NO_ARGS = 'Must provide either a result or errors'
|
11
|
+
FAILURE_MSG_BOTH_ARGS = 'Cannot provide both a result and errors'
|
12
|
+
FAILURE_ERRORS_CALLED_WHEN_SUCCESS = 'Cannot call errors when result is present'
|
13
|
+
|
14
|
+
extend T::Sig
|
15
|
+
extend T::Generic
|
16
|
+
|
17
|
+
Error = type_member { { fixed: Errors::APIErrorResult } }
|
18
|
+
Result = type_member { { upper: Object } }
|
19
|
+
|
20
|
+
sig { params(result: T.nilable(Result), errors: T.nilable(T::Array[Error])).void }
|
21
|
+
def initialize(result: nil, errors: nil)
|
22
|
+
raise Failure, FAILURE_MSG_NO_ARGS if result.nil? && errors.nil?
|
23
|
+
raise Failure, FAILURE_MSG_BOTH_ARGS if present?(result) && present?(errors)
|
24
|
+
|
25
|
+
@result = result
|
26
|
+
@errors = errors
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { returns(T::Boolean) }
|
30
|
+
def success?
|
31
|
+
present?(@result)
|
32
|
+
end
|
33
|
+
|
34
|
+
sig { returns(T::Boolean) }
|
35
|
+
def failed?
|
36
|
+
!success?
|
37
|
+
end
|
38
|
+
|
39
|
+
sig { returns(T::Array[Error]) }
|
40
|
+
def errors
|
41
|
+
raise Failure, FAILURE_ERRORS_CALLED_WHEN_SUCCESS if success?
|
42
|
+
|
43
|
+
T.must(@errors)
|
44
|
+
end
|
45
|
+
|
46
|
+
sig { returns(Result) }
|
47
|
+
def result
|
48
|
+
raise Failure, @errors&.map(&:serialize) if failed?
|
49
|
+
|
50
|
+
T.must(@result)
|
51
|
+
end
|
52
|
+
|
53
|
+
# courtesy to Rails, see: https://api.rubyonrails.org/v7.0.5/classes/Object.html
|
54
|
+
sig { params(object: T.untyped).returns(T::Boolean) }
|
55
|
+
private def blank?(object)
|
56
|
+
object.respond_to?(:empty?) ? !!object.empty? : !object
|
57
|
+
end
|
58
|
+
|
59
|
+
sig { params(object: T.untyped).returns(T::Boolean) }
|
60
|
+
private def present?(object)
|
61
|
+
!blank?(object)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/squake/version.rb
CHANGED
data/lib/squake.rb
CHANGED
@@ -5,11 +5,36 @@ require 'sorbet-runtime'
|
|
5
5
|
require 'oj'
|
6
6
|
require 'net/http'
|
7
7
|
|
8
|
-
Dir[File.join(__dir__, './**/*', '*.rb')].each { require(_1) }
|
9
|
-
|
10
|
-
module Squake; end
|
11
|
-
|
12
8
|
Oj.default_options = {
|
13
9
|
mode: :compat, # required to dump hashes with symbol-keys
|
14
10
|
symbol_keys: true,
|
15
11
|
}
|
12
|
+
|
13
|
+
Dir[File.join(__dir__, './**/*', '*.rb')].each { require(_1) }
|
14
|
+
|
15
|
+
module Squake
|
16
|
+
class << self
|
17
|
+
extend T::Sig
|
18
|
+
|
19
|
+
sig { returns(T.nilable(Squake::Config)) }
|
20
|
+
attr_accessor :configuration
|
21
|
+
|
22
|
+
sig do
|
23
|
+
params(
|
24
|
+
_: T.proc.params(configuration: Squake::Config).void,
|
25
|
+
).void
|
26
|
+
end
|
27
|
+
def configure(&_)
|
28
|
+
self.configuration ||= Squake::Config.new(
|
29
|
+
api_key: ENV.fetch('SQUAKE_API_KEY', nil),
|
30
|
+
keep_alive_timeout: ENV.fetch('SQUAKE_KEEP_ALIVE_TIMEOUT', 30).to_i,
|
31
|
+
sandbox_mode: ENV.fetch('SQUAKE_SANDBOX_MODE', 'true').casecmp?('true'),
|
32
|
+
enforced_api_base: ENV.fetch('SQUAKE_API_BASE', nil),
|
33
|
+
)
|
34
|
+
|
35
|
+
yield(T.must(configuration))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Squake.configure(&:itself) unless ENV['SQUAKE_API_KEY'].nil?
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: squake
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- SQUAKE
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-06-
|
11
|
+
date: 2023-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-http
|
@@ -138,6 +138,8 @@ files:
|
|
138
138
|
- lib/squake/calculation_with_pricing.rb
|
139
139
|
- lib/squake/client.rb
|
140
140
|
- lib/squake/config.rb
|
141
|
+
- lib/squake/errors/api_error_result.rb
|
142
|
+
- lib/squake/errors/api_error_source.rb
|
141
143
|
- lib/squake/model/carbon.rb
|
142
144
|
- lib/squake/model/carbon_unit.rb
|
143
145
|
- lib/squake/model/items/base_type.rb
|
@@ -149,6 +151,7 @@ files:
|
|
149
151
|
- lib/squake/products.rb
|
150
152
|
- lib/squake/purchase.rb
|
151
153
|
- lib/squake/response.rb
|
154
|
+
- lib/squake/return.rb
|
152
155
|
- lib/squake/util.rb
|
153
156
|
- lib/squake/version.rb
|
154
157
|
- sorbet/rbi/dsl/active_support/callbacks.rbi
|