kickplan-sdk 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b954c469ddb3a36bc7675a6a980e0d20f3002a5a3fa2e054e1e4309774c3d03e
4
+ data.tar.gz: d9a084c0d19165577fc11f8fe7a3574cc86020feb6a343b0f89d52c0f0589f51
5
+ SHA512:
6
+ metadata.gz: 465ad8b79cec8ddd036b0c104ad1872283b83094fd0a60157bf2038e457459da84b1043ba4f04c70e3d7bbcb9a7314c95cfc90bb9c7a0292bc354415ebcd9e20
7
+ data.tar.gz: 7578e16403fba734039bb1e9680349b5ceb38e04b9f31d25921d2a7ab72dfdf8d753d58e3e46038cf663997079f78f2e01c11dea67bfe00d328b5d484f6b358f
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) [2023] [Kickplan Inc.]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
File without changes
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kickplan
4
+ require_relative "concurrency"
5
+ require_relative "errors"
6
+
7
+ class Adapter
8
+ include Concurrency
9
+
10
+ def self.for(config)
11
+ adapter = Adapters[config.adapter] ||
12
+ fail(Errors::Configuration, "unknown adapter `#{config.adapter}`")
13
+
14
+ adapter.new(config)
15
+ end
16
+
17
+ attr_reader :config
18
+
19
+ def initialize(config = {})
20
+ @config = config
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kickplan
4
+ module Adapters
5
+ class HTTP < Adapter
6
+ extend Forwardable
7
+
8
+ delegate %i(get post put delete) => :connection
9
+
10
+ # @todo Implement adapter interface method.
11
+ def configure_account(params)
12
+ false
13
+ end
14
+
15
+ # @todo Implement adapter interface method.
16
+ def configure_feature(params)
17
+ false
18
+ end
19
+
20
+ def resolve_feature(key, params)
21
+ post("features/#{key}", params.to_h).body
22
+ end
23
+
24
+ def resolve_features(params)
25
+ post("features", params.to_h).body
26
+ end
27
+
28
+ def update_metric(params)
29
+ path = ["metrics", params.key, params.action].join("/")
30
+
31
+ post(path, params.to_h.slice(:value, :context)).body
32
+ end
33
+
34
+ # @api private
35
+ def connection
36
+ memoize do
37
+ Faraday::Connection.new(
38
+ url: config.endpoint,
39
+ proxy: config.proxy,
40
+ builder: config.middleware,
41
+ headers: {
42
+ user_agent: config.user_agent
43
+ }
44
+ )
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ Adapters.register(:http, Adapters::HTTP)
51
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kickplan
4
+ module Adapters
5
+ class Memory < Adapter
6
+ def configure_account(params)
7
+ accounts.merge_pair(params.key, params) do |existing|
8
+ existing.merge(params)
9
+ end
10
+ true
11
+ end
12
+
13
+ def configure_feature(params)
14
+ features.put(params.key, params)
15
+ true
16
+ end
17
+
18
+ def resolve_feature(key, params)
19
+ feature = features.get(key) ||
20
+ fail(ClientError, "Feature \"#{key}\" was not found")
21
+
22
+ account = accounts.get(params.context&.account_key)
23
+ variant = resolve_variant(feature, account)
24
+
25
+ { key: key, value: feature.variants[variant] }.tap do |response|
26
+ break response unless params.detailed
27
+
28
+ response.merge!(
29
+ metadata: { "name" => feature.name },
30
+ variant: variant
31
+ )
32
+ end
33
+ end
34
+
35
+ def resolve_features(params)
36
+ features.keys.map do |key|
37
+ resolve_feature(key, params)
38
+ end
39
+ end
40
+
41
+ def update_metric(params)
42
+ lookup_key = [params.key, params.context.account_key].join(".")
43
+ current_value = metrics[lookup_key] || 0
44
+
45
+ new_value =
46
+ case params.action
47
+ when "set"
48
+ params.value
49
+ when "increment"
50
+ current_value + params.value
51
+ when "decrement"
52
+ current_value - params.value
53
+ end
54
+
55
+ metrics[lookup_key] = new_value
56
+ end
57
+
58
+ # @api private
59
+ def accounts
60
+ memoize { Concurrent::Map.new }
61
+ end
62
+
63
+ # @api private
64
+ def features
65
+ memoize { Concurrent::Map.new }
66
+ end
67
+
68
+ # @api private
69
+ def metrics
70
+ memoize { Concurrent::Map.new }
71
+ end
72
+
73
+ private
74
+
75
+ def resolve_variant(feature, account = nil)
76
+ (account && account.overrides[feature.key]) || feature.default
77
+ end
78
+ end
79
+ end
80
+
81
+ Adapters.register(:memory, Adapters::Memory)
82
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kickplan
4
+ require_relative "adapter"
5
+
6
+ module Adapters
7
+ @_registry = Concurrent::Map.new
8
+
9
+ class << self
10
+ def [](name)
11
+ registry.get(name.to_s)
12
+ end
13
+
14
+ def register(name, klass)
15
+ registry.put(name.to_s, klass)
16
+ end
17
+
18
+ def registry
19
+ @_registry
20
+ end
21
+ end
22
+ end
23
+
24
+ require_relative "adapters/http"
25
+ require_relative "adapters/memory"
26
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kickplan
4
+ require_relative "adapters"
5
+ require_relative "concurrency"
6
+ require_relative "configuration"
7
+ require_relative "resource"
8
+
9
+ class Client < Module
10
+ include Concurrency
11
+ include Configuration
12
+
13
+ def initialize
14
+ # Use global configuration as client defaults
15
+ config.update(Kickplan.config.values)
16
+ end
17
+
18
+ def adapter
19
+ memoize { Adapter.for(config) }
20
+ end
21
+
22
+ def reset
23
+ unset(:adapter)
24
+ end
25
+
26
+ private
27
+
28
+ def const_missing(name)
29
+ synchronize do
30
+ Resources.const_get(name).new(self).tap do |resource|
31
+ self.const_set(name, resource)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kickplan
4
+ module Concurrency
5
+ def self.included(klass)
6
+ klass.prepend Initializer
7
+ end
8
+
9
+ def memoize(name = caller_locations(1,1)[0].label, &block)
10
+ memo = memoization_variable(name)
11
+
12
+ synchronize do
13
+ if instance_variable_defined?(memo)
14
+ instance_variable_get(memo)
15
+ else
16
+ instance_variable_set(memo, block.call)
17
+ end
18
+ end
19
+ end
20
+
21
+ def synchronize(&block)
22
+ @_semaphore.synchronize(&block)
23
+ end
24
+
25
+ def unset(name)
26
+ memo = memoization_variable(name)
27
+
28
+ if instance_variable_defined?(memo)
29
+ remove_instance_variable(memo)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def memoization_variable(name)
36
+ ["@", name.to_s].join
37
+ end
38
+
39
+ module Initializer
40
+ def initialize(...)
41
+ @_semaphore = Mutex.new
42
+ super
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+
5
+ module Kickplan
6
+ require_relative "default"
7
+
8
+ module Configuration
9
+ def self.included(klass)
10
+ klass.prepend Initializer
11
+ end
12
+
13
+ def self.extended(klass)
14
+ klass.class_eval do
15
+ extend Dry::Configurable
16
+
17
+ setting :access_token, default: Default.access_token
18
+ setting :adapter, default: Default.adapter
19
+ setting :endpoint, default: Default.endpoint
20
+ setting :middleware, default: Default.middleware
21
+ setting :proxy, default: Default.proxy
22
+ setting :user_agent, default: Default.user_agent
23
+ end
24
+ end
25
+
26
+ module Initializer
27
+ def initialize(...)
28
+ extend Configuration
29
+ super
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+
5
+ # Explicit requires for Faraday < 2.0
6
+ require "faraday/request/json"
7
+ require "faraday/response/json"
8
+
9
+ module Kickplan
10
+ require_relative "middleware/raise_error"
11
+ require_relative "version"
12
+
13
+ module Default
14
+ ADAPTER = :memory
15
+
16
+ ENDPOINT = "https://api.kickplan.io"
17
+
18
+ MIDDLEWARE = Faraday::RackBuilder.new do |builder|
19
+ builder.use Faraday::Request::Json
20
+ builder.use Faraday::Response::Json
21
+ builder.use Middleware::RaiseError
22
+ builder.adapter Faraday.default_adapter
23
+ end
24
+
25
+ USER_AGENT = "Kickplan SDK v#{VERSION}"
26
+
27
+ class << self
28
+ def access_token
29
+ ENV.fetch("KICKPLAN_ACCESS_TOKEN", nil)
30
+ end
31
+
32
+ def adapter
33
+ ENV.fetch("KICKPLAN_ADAPTER", ADAPTER)
34
+ end
35
+
36
+ def endpoint
37
+ ENV.fetch("KICKPLAN_ENDPOINT", ENDPOINT)
38
+ end
39
+
40
+ def middleware
41
+ MIDDLEWARE
42
+ end
43
+
44
+ def proxy
45
+ ENV.fetch("KICKPLAN_PROXY", nil)
46
+ end
47
+
48
+ def user_agent
49
+ ENV.fetch("KICKPLAN_USER_AGENT", USER_AGENT)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kickplan
4
+ Error = Class.new(StandardError)
5
+
6
+ # Generic errors
7
+ ClientError = Class.new(Error)
8
+ HttpError = Class.new(Error)
9
+ ServiceError = Class.new(HttpError)
10
+
11
+ module Errors
12
+ # Client errors
13
+ Configuration = Class.new(ClientError)
14
+
15
+ # Service errors
16
+ BadRequest = Class.new(ServiceError)
17
+ NotAuthorized = Class.new(ServiceError)
18
+ NotFound = Class.new(ServiceError)
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+
5
+ module Kickplan
6
+ module Middleware
7
+ # In Faraday 2.x, Faraday::Response::Middleware was removed
8
+ Base = defined?(Faraday::Response::Middleware) ? Faraday::Response::Middleware : Faraday::Middleware
9
+ end
10
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kickplan
4
+ require_relative "../errors"
5
+
6
+ module Middleware
7
+ require_relative "base"
8
+
9
+ class RaiseError < Base
10
+ def on_complete(env)
11
+ response = env.response
12
+
13
+ case response.status
14
+ when 400
15
+ fail Errors::BadRequest, response.body
16
+ when 403
17
+ fail Errors::NotAuthorized, response.body
18
+ when 404
19
+ fail Errors::NotFound, response.body
20
+ when 500
21
+ fail ServerError, response.body
22
+ when (400..499)
23
+ fail ServiceError, response.body
24
+ when (500..599)
25
+ fail ServerError, response.body
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-struct"
4
+
5
+ module Kickplan
6
+ require_relative "types"
7
+
8
+ class Request < Dry::Struct
9
+ transform_keys(&:to_sym)
10
+ schema schema.strict
11
+ end
12
+
13
+ require_relative "requests/configure_account"
14
+ require_relative "requests/configure_feature"
15
+ require_relative "requests/resolve_feature"
16
+ require_relative "requests/update_metric"
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kickplan
4
+ module Requests
5
+ class ConfigureAccount < Request
6
+ attribute :key, Types::String
7
+ attribute :overrides, Types::Hash
8
+
9
+ def merge(other)
10
+ new(
11
+ key: key,
12
+ overrides: overrides.merge(other.overrides)
13
+ )
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kickplan
4
+ module Requests
5
+ class ConfigureFeature < Request
6
+ FeatureTypes = Types::String.default("boolean").
7
+ enum("boolean", "integer", "object", "string")
8
+
9
+ DefaultVariants = { "true" => true, "false" => false }.freeze
10
+
11
+ attribute :key, Types::String
12
+ attribute? :name, Types::String.optional
13
+ attribute? :type, FeatureTypes
14
+ attribute? :default, Types::String.default("false")
15
+ attribute? :variants, Types::Hash.default(DefaultVariants)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kickplan
4
+ module Requests
5
+ class ResolveFeature < Request
6
+ attribute? :detailed, Types::Bool.default(false)
7
+ attribute? :context do
8
+ attribute? :account_key, Types::String.optional
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kickplan
4
+ module Requests
5
+ class UpdateMetric < Request
6
+ attribute :action, Types::String.enum("decrement", "increment", "set")
7
+ attribute :key, Types::String
8
+ attribute :value, Types::Any
9
+
10
+ attribute :context do
11
+ attribute :account_key, Types::String
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kickplan
4
+ require_relative "adapter"
5
+ require_relative "request"
6
+ require_relative "response"
7
+
8
+ class Resource < Module
9
+ extend Forwardable
10
+
11
+ delegate %i(adapter) => :client
12
+
13
+ attr_reader :client
14
+
15
+ def initialize(client)
16
+ @client = client
17
+ end
18
+ end
19
+
20
+ require_relative "resources/accounts"
21
+ require_relative "resources/features"
22
+ require_relative "resources/metrics"
23
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kickplan
4
+ module Resources
5
+ class Accounts < Resource
6
+ def configure(key, feature_key, feature_variant)
7
+ params = Requests::ConfigureAccount.new(
8
+ key: key,
9
+ overrides: { feature_key => feature_variant }
10
+ )
11
+
12
+ adapter.configure_account(params)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kickplan
4
+ module Resources
5
+ class Features < Resource
6
+ def configure(key, options = {})
7
+ params = Requests::ConfigureFeature.new(options.merge(key: key))
8
+
9
+ adapter.configure_feature(params)
10
+ end
11
+
12
+ def resolve(key = nil, options = {})
13
+ if key.nil? || key.is_a?(Hash)
14
+ key, options = nil, key
15
+ end
16
+
17
+ params = Requests::ResolveFeature.new(options)
18
+
19
+ response =
20
+ if key.nil?
21
+ adapter.resolve_features(params)
22
+ else
23
+ adapter.resolve_feature(key, params)
24
+ end
25
+
26
+ Responses::Resolution.wrap(response)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kickplan
4
+ module Resources
5
+ class Metrics < Resource
6
+ def decrement(key, value, context = nil)
7
+ update("decrement", key, value, context)
8
+ end
9
+
10
+ def increment(key, value, context = nil)
11
+ update("increment", key, value, context)
12
+ end
13
+
14
+ def set(key, value, context = nil)
15
+ update("set", key, value, context)
16
+ end
17
+
18
+ def update(action, key, value, context = nil)
19
+ if value.is_a?(Hash) && context.nil?
20
+ value, context = 1, value
21
+ end
22
+
23
+ params = Requests::UpdateMetric.new(
24
+ action: action,
25
+ context: context,
26
+ key: key,
27
+ value: value
28
+ )
29
+
30
+ adapter.update_metric(params)
31
+ true
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-struct"
4
+
5
+ module Kickplan
6
+ require_relative "types"
7
+
8
+ class Response < Dry::Struct
9
+ transform_keys(&:to_sym)
10
+
11
+ def self.wrap(attributes = {})
12
+ if attributes.is_a? Hash
13
+ new(attributes)
14
+ else
15
+ Array(attributes).map &method(:new)
16
+ end
17
+ end
18
+ end
19
+
20
+ require_relative "responses/resolution"
21
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kickplan
4
+ module Responses
5
+ class Resolution < Response
6
+ attribute :key, Types::String
7
+ attribute :value, Types::Any
8
+
9
+ # Detailed resolution
10
+ attribute? :error_code, Types::String.optional
11
+ attribute? :error_message, Types::String.optional
12
+ attribute? :metadata, Types::Hash.optional
13
+ attribute? :reason, Types::String.optional
14
+ attribute? :variant, Types::String.optional
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-types"
4
+
5
+ module Kickplan
6
+ module Types
7
+ include Dry.Types()
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kickplan
4
+ VERSION = "0.1.0"
5
+ end
data/lib/kickplan.rb ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ # Loading specific modules was added in 1.2
5
+ require "concurrent/map"
6
+ rescue LoadError
7
+ require "concurrent"
8
+ end
9
+
10
+ require "forwardable"
11
+
12
+ module Kickplan
13
+ require_relative "kickplan/client"
14
+ require_relative "kickplan/configuration"
15
+ require_relative "kickplan/version"
16
+
17
+ extend Configuration
18
+
19
+ @_clients = Concurrent::Map.new
20
+
21
+ class << self
22
+ def client(name = :default)
23
+ clients.fetch_or_store(name) { Client.new }
24
+ end
25
+ alias_method :[], :client
26
+
27
+ def clients
28
+ @_clients
29
+ end
30
+
31
+ private
32
+
33
+ def const_missing(name)
34
+ client.const_get(name)
35
+ end
36
+ end
37
+ end
metadata ADDED
@@ -0,0 +1,240 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kickplan-sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Will Clark
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-12-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: concurrent-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-configurable
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-struct
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: dry-types
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '1.7'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '1.7'
69
+ - !ruby/object:Gem::Dependency
70
+ name: faraday
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '1.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '1.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: dotenv
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.14'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.14'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rake
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '13.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '13.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: webmock
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '3.5'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '3.5'
167
+ - !ruby/object:Gem::Dependency
168
+ name: vcr
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '6.0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '6.0'
181
+ description: Kickplan lets you monetize your SaaS app by providing billing, feature
182
+ access and authorization infrastructure.
183
+ email:
184
+ - will@kickplan.com
185
+ executables: []
186
+ extensions: []
187
+ extra_rdoc_files: []
188
+ files:
189
+ - MIT-LICENSE
190
+ - README.md
191
+ - Rakefile
192
+ - lib/kickplan.rb
193
+ - lib/kickplan/adapter.rb
194
+ - lib/kickplan/adapters.rb
195
+ - lib/kickplan/adapters/http.rb
196
+ - lib/kickplan/adapters/memory.rb
197
+ - lib/kickplan/client.rb
198
+ - lib/kickplan/concurrency.rb
199
+ - lib/kickplan/configuration.rb
200
+ - lib/kickplan/default.rb
201
+ - lib/kickplan/errors.rb
202
+ - lib/kickplan/middleware/base.rb
203
+ - lib/kickplan/middleware/raise_error.rb
204
+ - lib/kickplan/request.rb
205
+ - lib/kickplan/requests/configure_account.rb
206
+ - lib/kickplan/requests/configure_feature.rb
207
+ - lib/kickplan/requests/resolve_feature.rb
208
+ - lib/kickplan/requests/update_metric.rb
209
+ - lib/kickplan/resource.rb
210
+ - lib/kickplan/resources/accounts.rb
211
+ - lib/kickplan/resources/features.rb
212
+ - lib/kickplan/resources/metrics.rb
213
+ - lib/kickplan/response.rb
214
+ - lib/kickplan/responses/resolution.rb
215
+ - lib/kickplan/types.rb
216
+ - lib/kickplan/version.rb
217
+ homepage: https://github.com/kickplan/sdk-ruby
218
+ licenses:
219
+ - MIT
220
+ metadata: {}
221
+ post_install_message:
222
+ rdoc_options: []
223
+ require_paths:
224
+ - lib
225
+ required_ruby_version: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ">="
228
+ - !ruby/object:Gem::Version
229
+ version: 2.7.0
230
+ required_rubygems_version: !ruby/object:Gem::Requirement
231
+ requirements:
232
+ - - ">="
233
+ - !ruby/object:Gem::Version
234
+ version: '0'
235
+ requirements: []
236
+ rubygems_version: 3.1.6
237
+ signing_key:
238
+ specification_version: 4
239
+ summary: SDK for Kickplan, a SaaS monetization infrastructure provider.
240
+ test_files: []