contractful 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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/API.md +142 -0
  3. data/ARCHITECTURE.md +134 -0
  4. data/AUTHORS +106 -0
  5. data/CODE_OF_CONDUCT.md +128 -0
  6. data/LICENSE +21 -0
  7. data/README.md +1290 -0
  8. data/SECURITY.md +67 -0
  9. data/bin/contractful +128 -0
  10. data/history.md +801 -0
  11. data/lib/contractful/abstract_response.rb +301 -0
  12. data/lib/contractful/circuit_breaker.rb +221 -0
  13. data/lib/contractful/connection_pool.rb +166 -0
  14. data/lib/contractful/contract/auth.rb +257 -0
  15. data/lib/contractful/contract/config.rb +93 -0
  16. data/lib/contractful/contract/definition.rb +295 -0
  17. data/lib/contractful/contract/dsl.rb +501 -0
  18. data/lib/contractful/contract/executor.rb +492 -0
  19. data/lib/contractful/contract/openapi_import.rb +1135 -0
  20. data/lib/contractful/contract/pagination.rb +218 -0
  21. data/lib/contractful/contract/param.rb +505 -0
  22. data/lib/contractful/contract/postman_import/auth.rb +39 -0
  23. data/lib/contractful/contract/postman_import/body.rb +87 -0
  24. data/lib/contractful/contract/postman_import/operation.rb +107 -0
  25. data/lib/contractful/contract/postman_import/scope.rb +37 -0
  26. data/lib/contractful/contract/postman_import/url.rb +80 -0
  27. data/lib/contractful/contract/postman_import.rb +164 -0
  28. data/lib/contractful/contract/response_field.rb +167 -0
  29. data/lib/contractful/contract/sequence.rb +432 -0
  30. data/lib/contractful/contract/swagger2/components.rb +84 -0
  31. data/lib/contractful/contract/swagger2/operations.rb +81 -0
  32. data/lib/contractful/contract/swagger2/parameters.rb +93 -0
  33. data/lib/contractful/contract/swagger2/references.rb +55 -0
  34. data/lib/contractful/contract/swagger2.rb +55 -0
  35. data/lib/contractful/contract.rb +85 -0
  36. data/lib/contractful/exceptions.rb +328 -0
  37. data/lib/contractful/notifications/active_support.rb +40 -0
  38. data/lib/contractful/notifications.rb +138 -0
  39. data/lib/contractful/pacer.rb +161 -0
  40. data/lib/contractful/params_array.rb +85 -0
  41. data/lib/contractful/payload.rb +340 -0
  42. data/lib/contractful/platform.rb +52 -0
  43. data/lib/contractful/rate_limit.rb +133 -0
  44. data/lib/contractful/raw_response.rb +70 -0
  45. data/lib/contractful/request.rb +1483 -0
  46. data/lib/contractful/resource.rb +189 -0
  47. data/lib/contractful/response.rb +109 -0
  48. data/lib/contractful/utils.rb +202 -0
  49. data/lib/contractful/version.rb +11 -0
  50. data/lib/contractful/windows/root_certs.rb +107 -0
  51. data/lib/contractful/windows.rb +9 -0
  52. data/lib/contractful.rb +233 -0
  53. metadata +244 -0
@@ -0,0 +1,233 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'openssl'
5
+ require 'stringio'
6
+ require 'uri'
7
+ require 'json'
8
+
9
+ require File.dirname(__FILE__) + '/contractful/version'
10
+ require File.dirname(__FILE__) + '/contractful/platform'
11
+ require File.dirname(__FILE__) + '/contractful/exceptions'
12
+ require File.dirname(__FILE__) + '/contractful/notifications'
13
+ require File.dirname(__FILE__) + '/contractful/circuit_breaker'
14
+ require File.dirname(__FILE__) + '/contractful/pacer'
15
+ require File.dirname(__FILE__) + '/contractful/utils'
16
+ require File.dirname(__FILE__) + '/contractful/request'
17
+ require File.dirname(__FILE__) + '/contractful/connection_pool'
18
+ require File.dirname(__FILE__) + '/contractful/abstract_response'
19
+ require File.dirname(__FILE__) + '/contractful/rate_limit'
20
+ require File.dirname(__FILE__) + '/contractful/response'
21
+ require File.dirname(__FILE__) + '/contractful/raw_response'
22
+ require File.dirname(__FILE__) + '/contractful/resource'
23
+ require File.dirname(__FILE__) + '/contractful/params_array'
24
+ require File.dirname(__FILE__) + '/contractful/payload'
25
+ require File.dirname(__FILE__) + '/contractful/contract'
26
+ require File.dirname(__FILE__) + '/contractful/windows'
27
+
28
+ # Contractful is a contract-first HTTP client for Ruby.
29
+ #
30
+ # Two layers share one request pipeline:
31
+ #
32
+ # * {Contractful} (this module) — the raw HTTP facade: one-liner verb
33
+ # methods ({Contractful.get}, {Contractful.post}, ...) returning a
34
+ # String-like {Contractful::Response}, with automatic retries, persistent
35
+ # connections, proxies, redirects, cookies and a retryable-vs-terminal
36
+ # error classification ({Contractful::Exceptions}).
37
+ # * {Contractful::Contract} — the contract-first layer: declare verb, path,
38
+ # typed parameters and expected responses, and client methods, path
39
+ # builders, validation and introspection are generated for you. See the
40
+ # "Contract-first API clients" section of the README.
41
+ #
42
+ # @example Raw HTTP
43
+ # Contractful.get('https://api.example.com/users', accept: 'application/json')
44
+ # Contractful.post('https://api.example.com/users', name: 'Ada') # form body
45
+ #
46
+ # @example Contract-first client
47
+ # class Client
48
+ # include Contractful::Contract
49
+ # base 'https://api.example.com'
50
+ # contract :list_users, 'GET /users' do
51
+ # param :page, Integer, default: 1
52
+ # responds 200, type: :array
53
+ # end
54
+ # end
55
+ #
56
+ # @see file:README.md Project overview and usage
57
+ # @see file:API.md Normative public API reference
58
+ # @see Contractful::Request The underlying request pipeline
59
+ #
60
+ # This module's static methods are the entry point for using the REST client.
61
+ #
62
+ # # GET
63
+ # xml = Contractful.get 'http://example.com/resource'
64
+ # jpg = Contractful.get 'http://example.com/resource', :accept => 'image/jpg'
65
+ #
66
+ # # authentication and SSL
67
+ # Contractful.get 'https://user:password@example.com/private/resource'
68
+ #
69
+ # # POST or PUT with a hash sends parameters as a urlencoded form body
70
+ # Contractful.post 'http://example.com/resource', :param1 => 'one'
71
+ #
72
+ # # nest hash parameters
73
+ # Contractful.post 'http://example.com/resource', :nested => { :param1 => 'one' }
74
+ #
75
+ # # POST and PUT with raw payloads
76
+ # Contractful.post 'http://example.com/resource', 'the post body', :content_type => 'text/plain'
77
+ # Contractful.post 'http://example.com/resource.xml', xml_doc
78
+ # Contractful.put 'http://example.com/resource.pdf', File.read('my.pdf'), :content_type => 'application/pdf'
79
+ #
80
+ # # DELETE
81
+ # Contractful.delete 'http://example.com/resource'
82
+ #
83
+ # # retrieve the response http code and headers
84
+ # res = Contractful.get 'http://example.com/some.jpg'
85
+ # res.code # => 200
86
+ # res.headers[:content_type] # => 'image/jpg'
87
+ #
88
+ # # HEAD
89
+ # Contractful.head('http://example.com').headers
90
+ #
91
+ # The module helpers accept a headers Hash only. Transport and resilience
92
+ # options (timeouts, retries, breaker:, pace:, persistent:, ...) belong to
93
+ # Contractful::Request.execute:
94
+ #
95
+ # Contractful::Request.execute(method: :get, url: 'http://example.com', max_retries: 3)
96
+ #
97
+ # To use with a proxy, just set Contractful.proxy to the proper http proxy:
98
+ #
99
+ # Contractful.proxy = "http://proxy.example.com/"
100
+ #
101
+ # Or inherit the proxy from the environment:
102
+ #
103
+ # Contractful.proxy = ENV['http_proxy']
104
+ #
105
+ # For live tests of Contractful, try using http://rest-test.heroku.com, which echoes back information about the rest call:
106
+ #
107
+ # >> Contractful.put 'http://rest-test.heroku.com/resource', :foo => 'baz'
108
+ # => "PUT http://rest-test.heroku.com/resource with a 7 byte payload, content type application/x-www-form-urlencoded {\"foo\"=>\"baz\"}"
109
+ #
110
+ module Contractful
111
+ def self.get(url, headers = {}, &block)
112
+ Request.execute(method: :get, url: url, headers: headers, &block)
113
+ end
114
+
115
+ def self.post(url, payload, headers = {}, &block)
116
+ Request.execute(method: :post, url: url, payload: payload, headers: headers, &block)
117
+ end
118
+
119
+ def self.patch(url, payload, headers = {}, &block)
120
+ Request.execute(method: :patch, url: url, payload: payload, headers: headers, &block)
121
+ end
122
+
123
+ def self.put(url, payload, headers = {}, &block)
124
+ Request.execute(method: :put, url: url, payload: payload, headers: headers, &block)
125
+ end
126
+
127
+ def self.delete(url, headers = {}, &block)
128
+ Request.execute(method: :delete, url: url, headers: headers, &block)
129
+ end
130
+
131
+ def self.head(url, headers = {}, &block)
132
+ Request.execute(method: :head, url: url, headers: headers, &block)
133
+ end
134
+
135
+ def self.options(url, headers = {}, &block)
136
+ Request.execute(method: :options, url: url, headers: headers, &block)
137
+ end
138
+
139
+ # A global proxy URL to use for all requests. This can be overridden on a
140
+ # per-request basis by passing `:proxy` to Contractful::Request.
141
+ def self.proxy
142
+ @proxy ||= nil
143
+ end
144
+
145
+ def self.proxy=(value)
146
+ @proxy = value
147
+ @proxy_set = true
148
+ end
149
+
150
+ # Return whether Contractful.proxy was set explicitly. We use this to
151
+ # differentiate between no value being set and a value explicitly set to nil.
152
+ #
153
+ # @return [Boolean]
154
+ #
155
+ def self.proxy_set?
156
+ @proxy_set ||= false
157
+ end
158
+
159
+ # Setup the log for Contractful calls.
160
+ # Value should be a logger but can can be stdout, stderr, or a filename.
161
+ # You can also configure logging by the environment variable CONTRACTFUL_LOG.
162
+ def self.log= log
163
+ @@log = create_log log
164
+ end
165
+
166
+ # Create a log that respond to << like a logger
167
+ # param can be 'stdout', 'stderr', a string (then we will log to that file) or a logger (then we return it)
168
+ def self.create_log param
169
+ return unless param
170
+
171
+ if param.is_a? String
172
+ if param == 'stdout'
173
+ stdout_logger = Class.new do
174
+ def << obj
175
+ # $stdout rather than the STDOUT constant so that logging
176
+ # follows a reassigned $stdout (matching the stderr logger's
177
+ # use of Kernel#warn, which writes to the current $stderr).
178
+ $stdout.puts obj
179
+ end
180
+ end
181
+ stdout_logger.new
182
+ elsif param == 'stderr'
183
+ stderr_logger = Class.new do
184
+ def << obj
185
+ warn obj
186
+ end
187
+ end
188
+ stderr_logger.new
189
+ else
190
+ file_logger = Class.new do
191
+ attr_writer :target_file
192
+
193
+ def << obj
194
+ File.open(@target_file, 'a') { |f| f.puts obj }
195
+ end
196
+ end
197
+ logger = file_logger.new
198
+ logger.target_file = param
199
+ logger
200
+ end
201
+ else
202
+ param
203
+ end
204
+
205
+ end
206
+
207
+ @@env_log = create_log ENV['CONTRACTFUL_LOG']
208
+
209
+ @@log = nil
210
+
211
+ def self.log # :nodoc:
212
+ @@env_log || @@log
213
+ end
214
+
215
+ @@before_execution_procs = []
216
+
217
+ # Add a Proc to be called before each request in executed.
218
+ # The proc parameters will be the http request and the request params.
219
+ def self.add_before_execution_proc &proc
220
+ raise ArgumentError.new('block is required') unless proc
221
+
222
+ @@before_execution_procs << proc
223
+ end
224
+
225
+ # Reset the procs to be called before each request is executed.
226
+ def self.reset_before_execution_procs
227
+ @@before_execution_procs = []
228
+ end
229
+
230
+ def self.before_execution_procs # :nodoc:
231
+ @@before_execution_procs
232
+ end
233
+ end
metadata ADDED
@@ -0,0 +1,244 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: contractful
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Shubham Taywade
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-11-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: webmock
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.19'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '5'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '3.19'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '5'
33
+ - !ruby/object:Gem::Dependency
34
+ name: rspec
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.13'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.13'
47
+ - !ruby/object:Gem::Dependency
48
+ name: pry
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.14'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.14'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rdoc
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '6.0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '6.0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: http-accept
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 1.7.0
82
+ - - "<"
83
+ - !ruby/object:Gem::Version
84
+ version: '2.0'
85
+ type: :runtime
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: 1.7.0
92
+ - - "<"
93
+ - !ruby/object:Gem::Version
94
+ version: '2.0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: http-cookie
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: 1.0.2
102
+ - - "<"
103
+ - !ruby/object:Gem::Version
104
+ version: '2.0'
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: 1.0.2
112
+ - - "<"
113
+ - !ruby/object:Gem::Version
114
+ version: '2.0'
115
+ - !ruby/object:Gem::Dependency
116
+ name: mime-types
117
+ requirement: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '1.16'
122
+ - - "<"
123
+ - !ruby/object:Gem::Version
124
+ version: '4.0'
125
+ type: :runtime
126
+ prerelease: false
127
+ version_requirements: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '1.16'
132
+ - - "<"
133
+ - !ruby/object:Gem::Version
134
+ version: '4.0'
135
+ - !ruby/object:Gem::Dependency
136
+ name: netrc
137
+ requirement: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - "~>"
140
+ - !ruby/object:Gem::Version
141
+ version: '0.8'
142
+ type: :runtime
143
+ prerelease: false
144
+ version_requirements: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - "~>"
147
+ - !ruby/object:Gem::Version
148
+ version: '0.8'
149
+ description: 'A contract-first HTTP client for Ruby: closed request schemas that reject
150
+ bad input before the network is touched, open response schemas that treat unknown
151
+ fields as data, and contract-level auth, idempotency, pagination, rate limits and
152
+ error classification. Clients are generated from contract definitions, with an OpenAPI
153
+ import helper and a recorded-sequence test helper.'
154
+ email:
155
+ - shubhamtaywade82@gmail.com
156
+ executables:
157
+ - contractful
158
+ extensions: []
159
+ extra_rdoc_files:
160
+ - README.md
161
+ - history.md
162
+ files:
163
+ - API.md
164
+ - ARCHITECTURE.md
165
+ - AUTHORS
166
+ - CODE_OF_CONDUCT.md
167
+ - LICENSE
168
+ - README.md
169
+ - SECURITY.md
170
+ - bin/contractful
171
+ - history.md
172
+ - lib/contractful.rb
173
+ - lib/contractful/abstract_response.rb
174
+ - lib/contractful/circuit_breaker.rb
175
+ - lib/contractful/connection_pool.rb
176
+ - lib/contractful/contract.rb
177
+ - lib/contractful/contract/auth.rb
178
+ - lib/contractful/contract/config.rb
179
+ - lib/contractful/contract/definition.rb
180
+ - lib/contractful/contract/dsl.rb
181
+ - lib/contractful/contract/executor.rb
182
+ - lib/contractful/contract/openapi_import.rb
183
+ - lib/contractful/contract/pagination.rb
184
+ - lib/contractful/contract/param.rb
185
+ - lib/contractful/contract/postman_import.rb
186
+ - lib/contractful/contract/postman_import/auth.rb
187
+ - lib/contractful/contract/postman_import/body.rb
188
+ - lib/contractful/contract/postman_import/operation.rb
189
+ - lib/contractful/contract/postman_import/scope.rb
190
+ - lib/contractful/contract/postman_import/url.rb
191
+ - lib/contractful/contract/response_field.rb
192
+ - lib/contractful/contract/sequence.rb
193
+ - lib/contractful/contract/swagger2.rb
194
+ - lib/contractful/contract/swagger2/components.rb
195
+ - lib/contractful/contract/swagger2/operations.rb
196
+ - lib/contractful/contract/swagger2/parameters.rb
197
+ - lib/contractful/contract/swagger2/references.rb
198
+ - lib/contractful/exceptions.rb
199
+ - lib/contractful/notifications.rb
200
+ - lib/contractful/notifications/active_support.rb
201
+ - lib/contractful/pacer.rb
202
+ - lib/contractful/params_array.rb
203
+ - lib/contractful/payload.rb
204
+ - lib/contractful/platform.rb
205
+ - lib/contractful/rate_limit.rb
206
+ - lib/contractful/raw_response.rb
207
+ - lib/contractful/request.rb
208
+ - lib/contractful/resource.rb
209
+ - lib/contractful/response.rb
210
+ - lib/contractful/utils.rb
211
+ - lib/contractful/version.rb
212
+ - lib/contractful/windows.rb
213
+ - lib/contractful/windows/root_certs.rb
214
+ homepage: https://github.com/shubhamtaywade82/contractful
215
+ licenses:
216
+ - MIT
217
+ metadata:
218
+ allowed_push_host: https://rubygems.org
219
+ bug_tracker_uri: https://github.com/shubhamtaywade82/contractful/issues
220
+ changelog_uri: https://github.com/shubhamtaywade82/contractful/blob/master/history.md
221
+ documentation_uri: https://rubydoc.info/gems/contractful/frames
222
+ source_code_uri: https://github.com/shubhamtaywade82/contractful
223
+ rubygems_mfa_required: 'true'
224
+ post_install_message:
225
+ rdoc_options: []
226
+ require_paths:
227
+ - lib
228
+ required_ruby_version: !ruby/object:Gem::Requirement
229
+ requirements:
230
+ - - ">="
231
+ - !ruby/object:Gem::Version
232
+ version: '3.2'
233
+ required_rubygems_version: !ruby/object:Gem::Requirement
234
+ requirements:
235
+ - - ">="
236
+ - !ruby/object:Gem::Version
237
+ version: '0'
238
+ requirements: []
239
+ rubygems_version: 3.5.22
240
+ signing_key:
241
+ specification_version: 4
242
+ summary: Contract-first HTTP client for Ruby with metaprogrammed clients, OpenAPI
243
+ import and recorded-sequence test helpers.
244
+ test_files: []