squake 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7860b4b9cfbf3110355582ab95ce9faee0055fc6ea852b00093703fbcf8f7c8d
4
+ data.tar.gz: 6f05154a58469f7f52f9730bf808a3005961b7ba29e7f6ce8fe47e47b27cc651
5
+ SHA512:
6
+ metadata.gz: d2065bbbe6bbbffa5067acf0810dbfe2c3ef32b50b5bce14b949f170a138f29ec16d2897093d9305af615e768ebcaa6da24873660572c24141e09f11b896cf08
7
+ data.tar.gz: 53f6a57201c32df605f6d30932baa4408bc3eafd7c02fb6f2dff6f55d0313c1e0b0f501980aa93e17bcea5ecd3bc11491f81639d138aa4c60005e62237689e92
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # SQUAKE
2
+
3
+ Find the documentation here: [docs](https://docs.squake.earth/).
4
+
5
+ ## Installation
6
+
7
+ Via git:
8
+
9
+ ```ruby
10
+ # Gemfile
11
+
12
+ gem 'squake', git: 'git@github.com:squake-earth/squake-ruby'
13
+ ```
14
+
15
+ Via rubygems:
16
+
17
+ ```ruby
18
+ # Gemfile
19
+
20
+ gem 'squake', '~> 0.1.0'
21
+ ```
22
+
23
+ ## Auth
24
+
25
+ An API Key can be generated on the [dashboard](https://dashboard.squake.earth/.)
26
+ You need a different API Key for production and sandbox.
27
+
28
+ ## Functionality
29
+
30
+ * compute emission values
31
+ * request a price quote (with a product id)
32
+ * place a purchase order (with a price quote id)
33
+ * get a list of all available products (includes price information)
34
+
35
+ ## Usage
36
+
37
+ Calculate emissions and include a price quote:
38
+
39
+ ```ruby
40
+ config = Squake::Config.new(
41
+ api_key: 'Your API Key', # required
42
+ sandbox_mode: true, # optional, default: true
43
+ keep_alive_timeout: 30, # optional, default: 30
44
+ logger: Logger.new($stdout), # optional, default: Logger.new($stdout)
45
+ enforced_api_base: nil, # optional, default: nil. If given, overrides the API base URL.
46
+ )
47
+
48
+ # the SQUAKE client emits canonical log lines, e.g.
49
+ # Request started http_method=get http_path=/api/v2/calculations
50
+ client = Squake::Client.new(config: config)
51
+
52
+ # Find all available item types and methodologies here:
53
+ # https://docs-v2.squake.earth/group/endpoint-calculations
54
+ items = [
55
+ {
56
+ type: 'flight',
57
+ methodology: 'ICAO',
58
+ origin: 'BER',
59
+ destination: 'SIN',
60
+ booking_class: 'economy',
61
+ number_of_travellers: 2,
62
+ aircraft_type: '350',
63
+ external_reference: 'booking-id',
64
+ }
65
+ ]
66
+
67
+ # returns Squake::Model::Pricing
68
+ pricing = Squake::CalculationWithPricing.quote(
69
+ client: client, # required
70
+ items: items, # required
71
+ product: 'product-id', # required
72
+ currency: 'EUR', # optional, default: 'EUR'
73
+ carbon_unit: 'gram', # optional, default: 'gram', other options: 'kilogram', 'tonne'
74
+ )
75
+ ```
76
+
77
+ Place a purchase order; 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.
78
+
79
+ ```ruby
80
+ uuid = SecureRandom.uuid
81
+
82
+ # returns Squake::Model::Purchase
83
+ purchase = Squake::Purchase.create(
84
+ client: client, # required
85
+ pricing: pricing.id, # required, from above
86
+ external_reference: uuid, # optional, default: SecureRandom.uuid, used for idempotency, if given, MUST be unique
87
+ )
88
+
89
+ # retrieve the purchase later
90
+ Squake::Purchase.retrieve(
91
+ client: client, # required
92
+ id: purchase.id, # required
93
+ )
94
+
95
+ # within 14 days, you can cancel the purchase worry-free
96
+ Squake::Purchase.cancel(
97
+ client: client, # required
98
+ id: purchase.id, # required
99
+ )
100
+ ```
@@ -0,0 +1,17 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Squake
5
+ class APIError < StandardError
6
+ extend T::Sig
7
+
8
+ sig { returns(Squake::Response) }
9
+ attr_reader :response
10
+
11
+ sig { params(response: Squake::Response).void }
12
+ def initialize(response:)
13
+ @response = response
14
+ super(T.must(response.error_message))
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,40 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # https://docs-v2.squake.earth/group/endpoint-combined-calculation-pricing
5
+ module Squake
6
+ class CalculationWithPricing
7
+ extend T::Sig
8
+
9
+ ENDPOINT = T.let('/v2/calculations-with-pricing', String)
10
+
11
+ sig do
12
+ params(
13
+ client: Squake::Client,
14
+ items: T::Array[Squake::Model::Items::BaseType],
15
+ product: String,
16
+ currency: String,
17
+ carbon_unit: String,
18
+ expand: T::Array[String],
19
+ ).returns(Squake::Model::Pricing)
20
+ end
21
+ def self.quote(client:, items:, product:, currency: 'EUR', carbon_unit: 'gram', expand: [])
22
+ result = client.call(
23
+ path: ENDPOINT,
24
+ method: :post,
25
+ params: {
26
+ items: items.map(&:serialize),
27
+ product: product,
28
+ currency: currency,
29
+ carbon_unit: carbon_unit,
30
+ expand: expand,
31
+ },
32
+ )
33
+ raise Squake::APIError.new(response: result) unless result.success?
34
+
35
+ Squake::Model::Pricing.from_api_response(
36
+ T.cast(result.body, T::Hash[Symbol, T.untyped]),
37
+ )
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,132 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'oj'
5
+
6
+ module Squake
7
+ class Client
8
+ extend T::Sig
9
+
10
+ JsonResponseBody = T.type_alias do
11
+ T.any(
12
+ T::Array[T::Hash[Symbol, T.untyped]],
13
+ T::Hash[Symbol, T.untyped],
14
+ )
15
+ end
16
+
17
+ sig { params(config: Squake::Config).void }
18
+ def initialize(config:)
19
+ @config = config
20
+ end
21
+
22
+ sig do
23
+ params(
24
+ path: String,
25
+ headers: T::Hash[String, String],
26
+ method: Symbol,
27
+ params: T.nilable(T::Hash[Symbol, String]),
28
+ api_base: T.nilable(String),
29
+ api_key: T.nilable(String),
30
+ ).returns(Squake::Response)
31
+ end
32
+ def call(path:, headers: {}, method: :get, params: nil, api_base: nil, api_key: nil)
33
+ execute_request(
34
+ method: method,
35
+ path: path,
36
+ headers: headers,
37
+ params: params,
38
+ api_base: api_base,
39
+ api_key: api_key,
40
+ )
41
+ end
42
+
43
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
44
+ sig do
45
+ params(
46
+ method: Symbol,
47
+ path: String,
48
+ headers: T::Hash[String, String],
49
+ params: T.nilable(T::Hash[Symbol, String]),
50
+ api_base: T.nilable(String),
51
+ api_key: T.nilable(String),
52
+ ).returns(Squake::Response)
53
+ end
54
+ private def execute_request(method:, path:, headers: {}, params: nil, api_base: nil, api_key: nil)
55
+ api_base ||= @config.api_base
56
+ api_key ||= @config.api_key
57
+
58
+ body_params = nil
59
+ query_params = nil
60
+ case method
61
+ when :get, :head, :delete
62
+ query_params = params
63
+ when :post
64
+ body_params = params
65
+ else
66
+ raise "Unrecognized HTTP method: #{method}. Expected one of: get, head, delete, post"
67
+ end
68
+
69
+ headers = request_headers(api_key).update(headers)
70
+ query = query_params ? Util.encode_parameters(query_params) : nil
71
+ body = body_params ? ::Oj.dump(body_params) : nil
72
+ sanitized_path = path[0] == '/' ? path : "/#{path}"
73
+ uri = URI.parse(api_base + sanitized_path)
74
+
75
+ connection = connection(uri)
76
+
77
+ method_name = method.to_s.upcase
78
+ has_response_body = method_name != 'HEAD'
79
+ request = Net::HTTPGenericRequest.new(
80
+ method_name,
81
+ (body_params ? true : false),
82
+ has_response_body,
83
+ (query ? [uri.path, query].join('?') : uri.path),
84
+ headers,
85
+ )
86
+
87
+ # https://stripe.com/blog/canonical-log-lines
88
+ canonical_request_line = <<~TXT
89
+ Request started
90
+ http_method=#{request.method}
91
+ http_path=#{request.path}
92
+ http_headers=#{request.to_hash}
93
+ TXT
94
+ canonical_request_line.gsub!(api_key, 'API_KEY_REDACTED')
95
+ @config.logger.info(canonical_request_line)
96
+
97
+ result = connection.request(request, body)
98
+
99
+ Squake::Response.new(
100
+ original_request: request,
101
+ code: Integer(result.code),
102
+ body: try_parse_json(result.body),
103
+ headers: result.to_hash,
104
+ )
105
+ end
106
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
107
+
108
+ sig { params(api_key: String).returns(T::Hash[String, String]) }
109
+ private def request_headers(api_key)
110
+ {
111
+ 'Authorization' => "Bearer #{api_key}",
112
+ 'Content-Type' => 'application/json',
113
+ }
114
+ end
115
+
116
+ sig { params(result_body: T.untyped).returns(JsonResponseBody) }
117
+ private def try_parse_json(result_body)
118
+ ::Oj.load(result_body, symbol_keys: true)
119
+ rescue ::Oj::ParseError, TypeError, JSON::ParserError, EncodingError => e
120
+ # in case of an error, Squake's response body is HTML not JSON
121
+ { 'error' => { 'message' => e.message, 'body' => result_body } }
122
+ end
123
+
124
+ sig { params(uri: URI::Generic).returns(Net::HTTP) }
125
+ private def connection(uri)
126
+ connection = Net::HTTP.new(uri.host, uri.port)
127
+ connection.keep_alive_timeout = @config.keep_alive_timeout
128
+ connection.use_ssl = uri.scheme == 'https'
129
+ connection
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,24 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Squake
5
+ class Config < T::Struct
6
+ extend T::Sig
7
+
8
+ DEFAULT_BASE_URL = T.let('https://api.squake.earth', String)
9
+ SANDBOX_BASE_URL = T.let('https://api.sandbox.squake.earth', String)
10
+
11
+ const :api_key, String
12
+ const :keep_alive_timeout, Integer, default: 30
13
+ const :logger, Logger, factory: -> { Logger.new($stdout) }
14
+ const :sandbox_mode, T::Boolean, default: true
15
+ const :enforced_api_base, T.nilable(String)
16
+
17
+ sig { returns(String) }
18
+ def api_base
19
+ return T.must(enforced_api_base) unless enforced_api_base.nil?
20
+
21
+ sandbox_mode ? SANDBOX_BASE_URL : DEFAULT_BASE_URL
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ # typed: strict
2
+
3
+ module Squake
4
+ module Model
5
+ module Items
6
+ extend T::Helpers
7
+
8
+ BaseType = T.type_alias do
9
+ Squake::Model::Items::PrivateJet::Squake
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ # typed: strict
2
+
3
+ module Squake
4
+ module Model
5
+ module Items
6
+ module PrivateJet
7
+ class Squake < T::Struct
8
+ extend T::Sig
9
+
10
+ const :type, String, default: 'private_jet'
11
+ const :methodology, String, default: 'SQUAKE'
12
+ const :origin, String
13
+ const :destination, String
14
+ const :identifier, String
15
+ const :identifier_kind, String, default: 'ICAO'
16
+ const :external_reference, T.nilable(String)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,31 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Squake
5
+ module Model
6
+ class Price < T::Struct
7
+ extend T::Sig
8
+
9
+ const :id, String
10
+ const :product, String
11
+ const :unit_amount, BigDecimal
12
+ const :valid_from, T.nilable(Date)
13
+ const :carbon_unit, String
14
+ const :currency, String
15
+
16
+ sig { params(response_body: T::Hash[Symbol, T.untyped]).returns(Squake::Model::Price) }
17
+ def self.from_api_response(response_body)
18
+ product = response_body.fetch(:price, {})
19
+
20
+ Squake::Model::Price.new(
21
+ id: product.fetch(:id),
22
+ product: product.fetch(:product),
23
+ unit_amount: product.fetch(:unit_amount).to_d,
24
+ valid_from: Date.parse(product.fetch(:valid_from, nil)),
25
+ carbon_unit: product.fetch(:carbon_unit),
26
+ currency: product.fetch(:currency),
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,61 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'price'
5
+ require_relative 'product'
6
+
7
+ module Squake
8
+ module Model
9
+ class Pricing < T::Struct
10
+ extend T::Sig
11
+
12
+ class Item < T::Struct
13
+ extend T::Sig
14
+ const :carbon_quantity, BigDecimal
15
+ const :carbon_unit, String
16
+ const :distance, BigDecimal
17
+ const :distance_unit, String
18
+ const :external_reference, T.nilable(String)
19
+ const :type, String
20
+ const :methodology, String
21
+ end
22
+
23
+ const :id, String
24
+ const :carbon_quantity, BigDecimal
25
+ const :carbon_unit, String
26
+ const :payment_link, T.nilable(String)
27
+ const :price, Squake::Model::Price
28
+ const :product, Squake::Model::Product
29
+ const :valid_until, Date
30
+ const :currency, String, default: 'EUR'
31
+ const :total, Integer
32
+ const :items, T::Array[Pricing::Item] # when constructing this, construct nested items first explicitly
33
+
34
+ sig { params(response_body: T::Hash[Symbol, T.untyped]).returns(Squake::Model::Pricing) }
35
+ def self.from_api_response(response_body)
36
+ price = Squake::Model::Price.from_api_response(response_body)
37
+ product = Squake::Model::Product.from_api_response(response_body)
38
+
39
+ items = response_body.fetch(:items, [])
40
+ items.map! do |item|
41
+ item[:carbon_quantity] = item.fetch(:carbon_quantity).to_d
42
+ item[:distance] = item.fetch(:distance).to_d
43
+ Item.new(item)
44
+ end
45
+
46
+ Squake::Model::Pricing.new(
47
+ id: response_body.fetch(:id),
48
+ items: items,
49
+ carbon_quantity: response_body.fetch(:carbon_quantity).to_d,
50
+ carbon_unit: response_body.fetch(:carbon_unit),
51
+ payment_link: response_body.fetch(:payment_link, nil),
52
+ price: price,
53
+ product: product,
54
+ valid_until: Date.parse(response_body.fetch(:valid_until, nil)),
55
+ currency: response_body.fetch(:currency),
56
+ total: response_body.fetch(:total),
57
+ )
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,23 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Squake
5
+ module Model
6
+ class Product < T::Struct
7
+ extend T::Sig
8
+
9
+ const :id, String
10
+ const :title, String
11
+
12
+ sig { params(response_body: T::Hash[Symbol, T.untyped]).returns(Squake::Model::Product) }
13
+ def self.from_api_response(response_body)
14
+ product = response_body.fetch(:product, {})
15
+
16
+ Squake::Model::Product.new(
17
+ id: product.fetch(:id),
18
+ title: product.fetch(:title),
19
+ )
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,39 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Squake
5
+ module Model
6
+ class Purchase < T::Struct
7
+ extend T::Sig
8
+
9
+ const :id, String
10
+ const :files, T::Array[String] # TODO: optional object, typed?
11
+ const :payment_method, String
12
+ const :state, String
13
+ const :metadata, T::Hash[Symbol, T.untyped], default: {}
14
+ const :checkout_page, T.nilable(T::Hash[Symbol, T.untyped])
15
+ const :carbon_quantity, BigDecimal
16
+ const :carbon_unit, String
17
+ const :total, Integer
18
+ const :currency, String
19
+ const :external_reference, T.nilable(String)
20
+
21
+ sig { params(response_body: T::Hash[Symbol, T.untyped]).returns(Squake::Model::Purchase) }
22
+ def self.from_api_response(response_body)
23
+ Squake::Model::Purchase.new(
24
+ id: response_body.fetch(:id),
25
+ files: response_body.fetch(:files),
26
+ payment_method: response_body.fetch(:payment_method),
27
+ state: response_body.fetch(:state),
28
+ metadata: response_body.fetch(:metadata),
29
+ checkout_page: response_body.fetch(:checkout_page, nil),
30
+ carbon_quantity: BigDecimal(response_body.fetch(:carbon_quantity).to_s),
31
+ carbon_unit: response_body.fetch(:carbon_unit),
32
+ total: response_body.fetch(:total),
33
+ currency: response_body.fetch(:currency),
34
+ external_reference: response_body.fetch(:external_reference, nil),
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,36 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # https://docs-v2.squake.earth/operation/operation-get-products
5
+ module Squake
6
+ class Products
7
+ extend T::Sig
8
+
9
+ ENDPOINT = T.let('/v2/products', String)
10
+ DEFAULT_LOCALE = T.let('en', String)
11
+
12
+ sig do
13
+ params(
14
+ client: Squake::Client,
15
+ locale: String,
16
+ product_id: T.nilable(String),
17
+ ).returns(T::Array[Squake::Model::Product])
18
+ end
19
+ def self.get(client:, locale: DEFAULT_LOCALE, product_id: nil)
20
+ path = product_id.nil? ? ENDPOINT : "#{ENDPOINT}/#{product_id}"
21
+
22
+ result = client.call(
23
+ path: path,
24
+ method: :get,
25
+ params: {
26
+ locale: locale,
27
+ },
28
+ )
29
+ raise Squake::APIError.new(response: result) unless result.success?
30
+
31
+ Array(result.body).map do |product_data|
32
+ Squake::Model::Product.from_api_response({ product: product_data })
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,80 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # https://docs-v2.squake.earth/operation/operation-post-purchases
5
+ module Squake
6
+ class Purchase
7
+ extend T::Sig
8
+
9
+ ENDPOINT = T.let('/v2/purchases', String)
10
+
11
+ # rubocop:disable Metrics/ParameterLists, Layout/LineLength
12
+ sig do
13
+ params(
14
+ client: Squake::Client,
15
+ pricing: String,
16
+ confirmation_document: T.nilable(T::Hash[Symbol, T.untyped]),
17
+ certificate_document: T.nilable(T::Hash[Symbol, T.untyped]),
18
+ metadata: T.nilable(T::Hash[Symbol, T.untyped]),
19
+ external_reference: String, # used for idempotency, if given, MUST be unique
20
+ expand: T::Array[String],
21
+ ).returns(Squake::Model::Purchase)
22
+ end
23
+ def self.create(client:, pricing:, confirmation_document: nil, certificate_document: nil, metadata: nil, external_reference: SecureRandom.uuid, expand: [])
24
+ result = client.call(
25
+ path: ENDPOINT,
26
+ method: :post,
27
+ params: {
28
+ pricing: pricing,
29
+ confirmation_document: confirmation_document,
30
+ certificate_document: certificate_document,
31
+ metadata: metadata,
32
+ external_reference: external_reference,
33
+ expand: expand,
34
+ },
35
+ )
36
+ raise Squake::APIError.new(response: result) unless result.success?
37
+
38
+ Squake::Model::Purchase.from_api_response(
39
+ T.cast(result.body, T::Hash[Symbol, T.untyped]),
40
+ )
41
+ end
42
+ # rubocop:enable Metrics/ParameterLists, Layout/LineLength
43
+
44
+ sig do
45
+ params(
46
+ client: Squake::Client,
47
+ id: String,
48
+ ).returns(T.nilable(Squake::Model::Purchase))
49
+ end
50
+ def self.retrieve(client:, id:)
51
+ result = client.call(
52
+ path: "#{ENDPOINT}/#{id}",
53
+ )
54
+ return nil if result.code == 404
55
+ raise Squake::APIError.new(response: result) unless result.success?
56
+
57
+ Squake::Model::Purchase.from_api_response(
58
+ T.cast(result.body, T::Hash[Symbol, T.untyped]),
59
+ )
60
+ end
61
+
62
+ sig do
63
+ params(
64
+ client: Squake::Client,
65
+ id: String,
66
+ ).returns(T.nilable(Squake::Model::Purchase))
67
+ end
68
+ def self.cancel(client:, id:)
69
+ result = client.call(
70
+ path: "#{ENDPOINT}/#{id}/cancel",
71
+ method: :post,
72
+ )
73
+ raise Squake::APIError.new(response: result) unless result.success?
74
+
75
+ Squake::Model::Purchase.from_api_response(
76
+ T.cast(result.body, T::Hash[Symbol, T.untyped]),
77
+ )
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,45 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'oj'
5
+
6
+ module Squake
7
+ class Response < T::Struct
8
+ extend T::Sig
9
+
10
+ const :original_request, Net::HTTPGenericRequest
11
+ const :code, Integer
12
+ const :body, Squake::Client::JsonResponseBody
13
+ const :headers, T::Hash[String, T.untyped]
14
+
15
+ sig { returns(T::Boolean) }
16
+ def success?
17
+ code >= 200 && code < 300
18
+ end
19
+
20
+ sig { returns(T::Boolean) }
21
+ def failure?
22
+ !success?
23
+ end
24
+
25
+ sig { returns(T.nilable(Squake::APIError)) }
26
+ def error
27
+ return if success?
28
+
29
+ Squake::APIError.new(response: self)
30
+ end
31
+
32
+ sig { returns(T.nilable(String)) }
33
+ def error_message
34
+ return if success?
35
+
36
+ <<~TXT
37
+ Failed Request
38
+ http_code=#{code}
39
+ body=#{::Oj.dump(body)}
40
+ original_request_http_method=#{original_request.method}
41
+ original_request_http_path=#{original_request.path}
42
+ TXT
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,29 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Squake
5
+ class Util
6
+ extend T::Sig
7
+
8
+ # Encodes a hash of parameters in a way that's suitable for use as query
9
+ # parameters in a URI or as form parameters in a request body. This mainly
10
+ # involves escaping special characters from parameter keys and values (e.g.
11
+ # `&`).
12
+ sig { params(params: T::Hash[Symbol, String]).returns(String) }
13
+ def self.encode_parameters(params)
14
+ params.map { |k, v| "#{url_encode(k.to_s)}=#{url_encode(v)}" }.join('&')
15
+ end
16
+
17
+ # Encodes a string in a way that makes it suitable for use in a set of
18
+ # query parameters in a URI or in a set of form parameters in a request
19
+ # body.
20
+ sig { params(key: String).returns(String) }
21
+ def self.url_encode(key)
22
+ CGI.escape(key.to_s).
23
+ # Don't use strict form encoding by changing the square bracket control
24
+ # characters back to their literals. This is fine by the server, and
25
+ # makes these parameter strings easier to read.
26
+ gsub('%5B', '[').gsub('%5D', ']')
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,6 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Squake
5
+ VERSION = '0.1.0'
6
+ end
data/lib/squake.rb ADDED
@@ -0,0 +1,7 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'sorbet-runtime'
5
+ require 'oj'
6
+
7
+ require_relative 'squake/version'
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: squake
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - 'Team SQUAKE :rocket:'
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-06-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: oj
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sorbet
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sorbet-runtime
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop-dbl
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: spoom
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: tapioca
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: This gem provides an interface for the SQUAKE API to calculate and compensate
98
+ carbon emissions.
99
+ email: oss@squake.earth
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - README.md
105
+ - lib/squake.rb
106
+ - lib/squake/api_error.rb
107
+ - lib/squake/calculation_with_pricing.rb
108
+ - lib/squake/client.rb
109
+ - lib/squake/config.rb
110
+ - lib/squake/model/items/base_type.rb
111
+ - lib/squake/model/items/private_jet/squake.rb
112
+ - lib/squake/model/price.rb
113
+ - lib/squake/model/pricing.rb
114
+ - lib/squake/model/product.rb
115
+ - lib/squake/model/purchase.rb
116
+ - lib/squake/products.rb
117
+ - lib/squake/purchase.rb
118
+ - lib/squake/response.rb
119
+ - lib/squake/util.rb
120
+ - lib/squake/version.rb
121
+ homepage: https://github.com/squake-earth/squake-ruby
122
+ licenses:
123
+ - MIT
124
+ metadata:
125
+ rubygems_mfa_required: 'true'
126
+ post_install_message:
127
+ rdoc_options: []
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '2.7'
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ requirements: []
141
+ rubygems_version: 3.4.13
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: The industry solution for sustainable travel and logistics.
145
+ test_files: []