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,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Contractful
4
+ # A class that can be instantiated for access to a RESTful resource,
5
+ # including authentication.
6
+ #
7
+ # Example:
8
+ #
9
+ # resource = Contractful::Resource.new('http://some/resource')
10
+ # jpg = resource.get(:accept => 'image/jpg')
11
+ #
12
+ # With HTTP basic authentication:
13
+ #
14
+ # resource = Contractful::Resource.new('http://protected/resource', :user => 'user', :password => 'password')
15
+ # resource.delete
16
+ #
17
+ # With a timeout (seconds):
18
+ #
19
+ # Contractful::Resource.new('http://slow', :read_timeout => 10)
20
+ #
21
+ # With an open timeout (seconds):
22
+ #
23
+ # Contractful::Resource.new('http://behindfirewall', :open_timeout => 10)
24
+ #
25
+ # You can also use resources to share common headers. For headers keys,
26
+ # symbols are converted to strings. Example:
27
+ #
28
+ # resource = Contractful::Resource.new('http://some/resource', :headers => { :client_version => 1 })
29
+ #
30
+ # This header will be transported as Client-Version (symbol keys are split
31
+ # on underscores and capitalized; no X- prefix is added)
32
+ #
33
+ # Use the [] syntax to allocate subresources:
34
+ #
35
+ # site = Contractful::Resource.new('http://example.com', :user => 'adam', :password => 'mypasswd')
36
+ # site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
37
+ #
38
+ class Resource
39
+ attr_reader :url, :options, :block
40
+
41
+ def initialize(url, options = {}, backwards_compatibility = nil, &block)
42
+ @url = url
43
+ @block = block
44
+ # compatibility with previous versions
45
+ @options = if options.instance_of?(Hash)
46
+ options
47
+ else
48
+ { user: options, password: backwards_compatibility }
49
+ end
50
+ end
51
+
52
+ def get(additional_headers = {}, &block)
53
+ headers = (options[:headers] || {}).merge(additional_headers)
54
+ Request.execute(options.merge(
55
+ method: :get,
56
+ url: url,
57
+ headers: headers,
58
+ log: log
59
+ ), &block || @block)
60
+ end
61
+
62
+ def head(additional_headers = {}, &block)
63
+ headers = (options[:headers] || {}).merge(additional_headers)
64
+ Request.execute(options.merge(
65
+ method: :head,
66
+ url: url,
67
+ headers: headers,
68
+ log: log
69
+ ), &block || @block)
70
+ end
71
+
72
+ def post(payload, additional_headers = {}, &block)
73
+ headers = (options[:headers] || {}).merge(additional_headers)
74
+ Request.execute(options.merge(
75
+ method: :post,
76
+ url: url,
77
+ payload: payload,
78
+ headers: headers,
79
+ log: log
80
+ ), &block || @block)
81
+ end
82
+
83
+ def put(payload, additional_headers = {}, &block)
84
+ headers = (options[:headers] || {}).merge(additional_headers)
85
+ Request.execute(options.merge(
86
+ method: :put,
87
+ url: url,
88
+ payload: payload,
89
+ headers: headers,
90
+ log: log
91
+ ), &block || @block)
92
+ end
93
+
94
+ def patch(payload, additional_headers = {}, &block)
95
+ headers = (options[:headers] || {}).merge(additional_headers)
96
+ Request.execute(options.merge(
97
+ method: :patch,
98
+ url: url,
99
+ payload: payload,
100
+ headers: headers,
101
+ log: log
102
+ ), &block || @block)
103
+ end
104
+
105
+ def delete(additional_headers = {}, &block)
106
+ headers = (options[:headers] || {}).merge(additional_headers)
107
+ Request.execute(options.merge(
108
+ method: :delete,
109
+ url: url,
110
+ headers: headers,
111
+ log: log
112
+ ), &block || @block)
113
+ end
114
+
115
+ def to_s
116
+ url
117
+ end
118
+
119
+ def user
120
+ options[:user]
121
+ end
122
+
123
+ def password
124
+ options[:password]
125
+ end
126
+
127
+ def headers
128
+ options[:headers] || {}
129
+ end
130
+
131
+ def read_timeout
132
+ options[:read_timeout]
133
+ end
134
+
135
+ def open_timeout
136
+ options[:open_timeout]
137
+ end
138
+
139
+ def log
140
+ options[:log] || Contractful.log
141
+ end
142
+
143
+ # Construct a subresource, preserving authentication.
144
+ #
145
+ # Example:
146
+ #
147
+ # site = Contractful::Resource.new('http://example.com', 'adam', 'mypasswd')
148
+ # site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
149
+ #
150
+ # This is especially useful if you wish to define your site in one place and
151
+ # call it in multiple locations:
152
+ #
153
+ # def orders
154
+ # Contractful::Resource.new('http://example.com/orders', 'admin', 'mypasswd')
155
+ # end
156
+ #
157
+ # orders.get # GET http://example.com/orders
158
+ # orders['1'].get # GET http://example.com/orders/1
159
+ # orders['1/items'].delete # DELETE http://example.com/orders/1/items
160
+ #
161
+ # Nest resources as far as you want:
162
+ #
163
+ # site = Contractful::Resource.new('http://example.com')
164
+ # posts = site['posts']
165
+ # first_post = posts['1']
166
+ # comments = first_post['comments']
167
+ # comments.post 'Hello', :content_type => 'text/plain'
168
+ #
169
+ def [](suburl, &new_block)
170
+ if block_given?
171
+ self.class.new(concat_urls(url, suburl), options, &new_block)
172
+ elsif block
173
+ self.class.new(concat_urls(url, suburl), options, &block)
174
+ else
175
+ self.class.new(concat_urls(url, suburl), options)
176
+ end
177
+ end
178
+
179
+ def concat_urls(url, suburl) # :nodoc:
180
+ url = url.to_s
181
+ suburl = suburl.to_s
182
+ if url.slice(-1, 1) == '/' or suburl.slice(0, 1) == '/'
183
+ url + suburl
184
+ else
185
+ "#{url}/#{suburl}"
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Contractful
4
+ # A Response from Contractful, you can access the response body, the code or the headers.
5
+ #
6
+ class Response < String
7
+ include AbstractResponse
8
+
9
+ # Return the HTTP response body.
10
+ #
11
+ # Future versions of Contractful will deprecate treating response objects
12
+ # directly as strings, so it will be necessary to call `.body`.
13
+ #
14
+ # @return [String]
15
+ #
16
+ def body
17
+ # Benchmarking suggests that "#{self}" is fastest, and that caching the
18
+ # body string in an instance variable doesn't make it enough faster to be
19
+ # worth the extra memory storage.
20
+ String.new(self)
21
+ end
22
+
23
+ # Convert the HTTP response body to a pure String object.
24
+ #
25
+ # @return [String]
26
+ def to_s
27
+ body
28
+ end
29
+
30
+ # Convert the HTTP response body to a pure String object.
31
+ #
32
+ # @return [String]
33
+ def to_str
34
+ body
35
+ end
36
+
37
+ # Parse the response body as JSON and return the result.
38
+ #
39
+ # This is a convenience wrapper around `JSON.parse(body)`. Any keyword
40
+ # arguments are forwarded to `JSON.parse`, so for example:
41
+ #
42
+ # response.json # => {"a" => 1}
43
+ # response.json(symbolize_names: true) # => {a: 1}
44
+ #
45
+ # Raises `JSON::ParserError` if the body is not valid JSON. Note that the
46
+ # return value is a plain parsed data structure, not a Response object.
47
+ #
48
+ # @param kwargs [Hash] keyword arguments forwarded to JSON.parse
49
+ #
50
+ # @return [Object] the parsed JSON data structure (typically Hash or Array)
51
+ #
52
+ def json(**kwargs)
53
+ JSON.parse(to_s, **kwargs)
54
+ end
55
+
56
+ def inspect
57
+ "<Contractful::Response #{code.inspect} #{body_truncated(10).inspect}>"
58
+ end
59
+
60
+ # Initialize a Response object. Because Contractful::Response is
61
+ # (unfortunately) a subclass of String for historical reasons,
62
+ # Response.create is the preferred initializer.
63
+ #
64
+ # @param [String, nil] body The response body from the Net::HTTPResponse
65
+ # @param [Net::HTTPResponse] net_http_res
66
+ # @param [Contractful::Request] request
67
+ # @param [Time] start_time
68
+ def self.create(body, net_http_res, request, start_time = nil)
69
+ result = new(body || '')
70
+
71
+ result.response_set_vars(net_http_res, request, start_time)
72
+ fix_encoding(result)
73
+
74
+ result
75
+ end
76
+
77
+ # Set the String encoding according to the 'Content-Type: charset' header,
78
+ # if possible.
79
+ def self.fix_encoding(response)
80
+ charset = Contractful::Utils.get_encoding_from_headers(response.headers)
81
+ encoding = nil
82
+
83
+ begin
84
+ encoding = Encoding.find(charset) if charset
85
+ rescue ArgumentError
86
+ response.log << "No such encoding: #{charset.inspect}" if response.log
87
+ end
88
+
89
+ return unless encoding
90
+
91
+ response.force_encoding(encoding)
92
+
93
+ response
94
+ end
95
+
96
+ private
97
+
98
+ def body_truncated(length)
99
+ b = body
100
+ if b.length > length
101
+ # b[0..length] is inclusive, returning length+1 chars before the
102
+ # ellipsis — use b[0, length] for an exact cut.
103
+ "#{b[0, length]}..."
104
+ else
105
+ b
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'http/accept'
4
+
5
+ module Contractful
6
+ # Various utility methods
7
+ module Utils
8
+ # Return encoding from an HTTP header hash.
9
+ #
10
+ # We use the RFC 7231 specification and do not impose a default encoding on
11
+ # text. This differs from the older RFC 2616 behavior, which specifies
12
+ # using ISO-8859-1 for text/* content types without a charset.
13
+ #
14
+ # Strings will use the default encoding when this method returns nil. This
15
+ # default is likely to be UTF-8 for Ruby >= 2.0
16
+ #
17
+ # @param headers [Hash<Symbol,String>]
18
+ #
19
+ # @return [String, nil] Return the string encoding or nil if no header is
20
+ # found.
21
+ #
22
+ # @example
23
+ # >> get_encoding_from_headers({:content_type => 'text/plain; charset=UTF-8'})
24
+ # => "UTF-8"
25
+ #
26
+ def self.get_encoding_from_headers(headers)
27
+ type_header = headers[:content_type]
28
+ return nil unless type_header
29
+
30
+ begin
31
+ _content_type, params = cgi_parse_header(type_header)
32
+ rescue HTTP::Accept::ParseError
33
+ nil
34
+ else
35
+ params['charset']
36
+ end
37
+ end
38
+
39
+ # Parse a Content-Type like header.
40
+ #
41
+ # Return the main content-type and a hash of params.
42
+ #
43
+ # @param [String] line
44
+ # @return [Array(String, Hash)]
45
+ #
46
+ def self.cgi_parse_header(line)
47
+ types = HTTP::Accept::MediaTypes.parse(line)
48
+
49
+ raise HTTP::Accept::ParseError.new('Found no types in header line') if types.empty?
50
+
51
+ [types.first.mime_type, types.first.parameters]
52
+ end
53
+
54
+ # Serialize a ruby object into HTTP query string parameters.
55
+ #
56
+ # There is no standard for doing this, so we choose our own slightly
57
+ # idiosyncratic format. The output closely matches the format understood by
58
+ # Rails, Rack, and PHP.
59
+ #
60
+ # If you don't want handling of complex objects and only want to handle
61
+ # simple flat hashes, you may want to use `URI.encode_www_form` instead,
62
+ # which implements HTML5-compliant URL encoded form data.
63
+ #
64
+ # @param [Hash,ParamsArray] object The object to serialize
65
+ #
66
+ # @return [String] A string appropriate for use as an HTTP query string
67
+ #
68
+ # @see flatten_params
69
+ #
70
+ # @see URI.encode_www_form
71
+ #
72
+ # @see See also Object#to_query in ActiveSupport
73
+ # @see http://php.net/manual/en/function.http-build-query.php
74
+ # http_build_query in PHP
75
+ # @see See also Rack::Utils.build_nested_query in Rack
76
+ #
77
+ # Notable differences from the ActiveSupport implementation:
78
+ #
79
+ # - Empty hash and empty array are treated the same as nil instead of being
80
+ # omitted entirely from the output. Rather than disappearing, they will
81
+ # appear to be nil instead.
82
+ #
83
+ # It's most common to pass a Hash as the object to serialize, but you can
84
+ # also use a ParamsArray if you want to be able to pass the same key with
85
+ # multiple values and not use the rack/rails array convention.
86
+ #
87
+ # @since 2.0.0
88
+ #
89
+ # @example Simple hashes
90
+ # >> encode_query_string({foo: 123, bar: 456})
91
+ # => 'foo=123&bar=456'
92
+ #
93
+ # @example Simple arrays
94
+ # >> encode_query_string({foo: [1,2,3]})
95
+ # => 'foo[]=1&foo[]=2&foo[]=3'
96
+ #
97
+ # @example Nested hashes
98
+ # >> encode_query_string({outer: {foo: 123, bar: 456}})
99
+ # => 'outer[foo]=123&outer[bar]=456'
100
+ #
101
+ # @example Deeply nesting
102
+ # >> encode_query_string({coords: [{x: 1, y: 0}, {x: 2}, {x: 3}]})
103
+ # => 'coords[][x]=1&coords[][y]=0&coords[][x]=2&coords[][x]=3'
104
+ #
105
+ # @example Null and empty values
106
+ # >> encode_query_string({string: '', empty: nil, list: [], hash: {}})
107
+ # => 'string=&empty&list&hash'
108
+ #
109
+ # @example Nested nulls
110
+ # >> encode_query_string({foo: {string: '', empty: nil}})
111
+ # => 'foo[string]=&foo[empty]'
112
+ #
113
+ # @example Multiple fields with the same name using ParamsArray
114
+ # >> encode_query_string(Contractful::ParamsArray.new([[:foo, 1], [:foo, 2], [:foo, 3]]))
115
+ # => 'foo=1&foo=2&foo=3'
116
+ #
117
+ # @example Nested ParamsArray
118
+ # >> encode_query_string({foo: Contractful::ParamsArray.new([[:a, 1], [:a, 2]])})
119
+ # => 'foo[a]=1&foo[a]=2'
120
+ #
121
+ # >> encode_query_string(Contractful::ParamsArray.new([[:foo, {a: 1}], [:foo, {a: 2}]]))
122
+ # => 'foo[a]=1&foo[a]=2'
123
+ #
124
+ def self.encode_query_string(object)
125
+ flatten_params(object, true).map { |k, v| v.nil? ? k : "#{k}=#{v}" }.join('&')
126
+ end
127
+
128
+ # Transform deeply nested param containers into a flat array of [key,
129
+ # value] pairs.
130
+ #
131
+ # @example
132
+ # >> flatten_params({key1: {key2: 123}})
133
+ # => [["key1[key2]", 123]]
134
+ #
135
+ # @example
136
+ # >> flatten_params({key1: {key2: 123, arr: [1,2,3]}})
137
+ # => [["key1[key2]", 123], ["key1[arr][]", 1], ["key1[arr][]", 2], ["key1[arr][]", 3]]
138
+ #
139
+ # @param object [Hash, ParamsArray] The container to flatten
140
+ # @param uri_escape [Boolean] Whether to URI escape keys and values
141
+ # @param parent_key [String] Should not be passed (used for recursion)
142
+ # @param depth [Integer] Should not be passed (used for recursion).
143
+ # Hard cap at MAX_FLATTEN_DEPTH to prevent SystemStackError on
144
+ # cyclically-referenced or adversarially-nested input.
145
+ #
146
+ MAX_FLATTEN_DEPTH = 100
147
+
148
+ def self.flatten_params(object, uri_escape = false, parent_key = nil, depth = 0)
149
+ unless object.is_a?(Hash) || object.is_a?(ParamsArray) ||
150
+ (parent_key && object.is_a?(Array))
151
+ raise ArgumentError.new('expected Hash or ParamsArray, got: ' + object.inspect)
152
+ end
153
+
154
+ if depth > MAX_FLATTEN_DEPTH
155
+ raise ArgumentError,
156
+ "flatten_params nested deeper than #{MAX_FLATTEN_DEPTH} levels " \
157
+ '(reject input, or raise MAX_FLATTEN_DEPTH if you have a ' \
158
+ 'legitimate use case)'
159
+ end
160
+
161
+ # transform empty collections into nil, where possible
162
+ return [[parent_key, nil]] if object.empty? && parent_key
163
+
164
+ # This is essentially .map(), but we need to do += for nested containers
165
+ object.reduce([]) do |result, item|
166
+ if object.is_a?(Array)
167
+ # item is already the value
168
+ k = nil
169
+ v = item
170
+ else
171
+ # item is a key, value pair
172
+ k, v = item
173
+ k = escape(k.to_s) if uri_escape
174
+ end
175
+
176
+ processed_key = parent_key ? "#{parent_key}[#{k}]" : k
177
+
178
+ case v
179
+ when Array, Hash, ParamsArray
180
+ result.concat flatten_params(v, uri_escape, processed_key, depth + 1)
181
+ else
182
+ v = escape(v.to_s) if uri_escape && v
183
+ result << [processed_key, v]
184
+ end
185
+ end
186
+ end
187
+
188
+ # Encode string for safe transport by URI or form encoding. This uses a CGI
189
+ # style escape, which transforms ` ` into `+` and various special
190
+ # characters into percent encoded forms.
191
+ #
192
+ # This calls URI.encode_www_form_component for the implementation. The only
193
+ # difference between this and CGI.escape is that it does not escape `*`.
194
+ # http://stackoverflow.com/questions/25085992/
195
+ #
196
+ # @see URI.encode_www_form_component
197
+ #
198
+ def self.escape(string)
199
+ URI.encode_www_form_component(string)
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Gem version constants for contractful.
4
+ module Contractful
5
+ VERSION_INFO = [0, 1, 0].freeze
6
+ VERSION = VERSION_INFO.map(&:to_s).join('.').freeze
7
+
8
+ def self.version
9
+ VERSION
10
+ end
11
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'ffi'
5
+
6
+ # Adapted from Puppet, Copyright (c) Puppet Labs Inc,
7
+ # licensed under the Apache License, Version 2.0.
8
+ #
9
+ # https://github.com/puppetlabs/puppet/blob/bbe30e0a/lib/puppet/util/windows/root_certs.rb
10
+
11
+ # Represents a collection of trusted root certificates.
12
+ #
13
+ # @api public
14
+ class Contractful::Windows::RootCerts
15
+ include Enumerable
16
+ extend FFI::Library
17
+
18
+ typedef :ulong, :dword
19
+ typedef :uintptr_t, :handle
20
+
21
+ def initialize(roots)
22
+ @roots = roots
23
+ end
24
+
25
+ # Enumerates each root certificate.
26
+ # @yieldparam cert [OpenSSL::X509::Certificate] each root certificate
27
+ # @api public
28
+ def each(&block)
29
+ @roots.each(&block)
30
+ end
31
+
32
+ # Returns a new instance.
33
+ # @return [Contractful::Windows::RootCerts] object constructed from current root certificates
34
+ def self.instance
35
+ new(load_certs)
36
+ end
37
+
38
+ # Returns an array of root certificates.
39
+ #
40
+ # @return [Array<[OpenSSL::X509::Certificate]>] an array of root certificates
41
+ # @api private
42
+ def self.load_certs
43
+ certs = []
44
+
45
+ # This is based on a patch submitted to openssl:
46
+ # http://www.mail-archive.com/openssl-dev@openssl.org/msg26958.html
47
+ ptr = FFI::Pointer::NULL
48
+ store = CertOpenSystemStoreA(nil, 'ROOT')
49
+ begin
50
+ while (ptr = CertEnumCertificatesInStore(store, ptr)) and !ptr.null?
51
+ context = CertContext.new(ptr)
52
+ cert_buf = context[:pbCertEncoded].read_bytes(context[:cbCertEncoded])
53
+ begin
54
+ certs << OpenSSL::X509::Certificate.new(cert_buf)
55
+ rescue StandardError => e
56
+ warn("Failed to import root certificate: #{e.inspect}")
57
+ end
58
+ end
59
+ ensure
60
+ CertCloseStore(store, 0)
61
+ end
62
+
63
+ certs
64
+ end
65
+
66
+ # typedef ULONG_PTR HCRYPTPROV_LEGACY;
67
+ # typedef void *HCERTSTORE;
68
+
69
+ # FFI mapping of the Windows CERT_CONTEXT structure returned by
70
+ # CertEnumCertificatesInStore.
71
+ class CertContext < FFI::Struct
72
+ layout(
73
+ :dwCertEncodingType, :dword,
74
+ :pbCertEncoded, :pointer,
75
+ :cbCertEncoded, :dword,
76
+ :pCertInfo, :pointer,
77
+ :hCertStore, :handle
78
+ )
79
+ end
80
+
81
+ # HCERTSTORE
82
+ # WINAPI
83
+ # CertOpenSystemStoreA(
84
+ # __in_opt HCRYPTPROV_LEGACY hProv,
85
+ # __in LPCSTR szSubsystemProtocol
86
+ # );
87
+ ffi_lib :crypt32
88
+ attach_function :CertOpenSystemStoreA, %i[pointer string], :handle
89
+
90
+ # PCCERT_CONTEXT
91
+ # WINAPI
92
+ # CertEnumCertificatesInStore(
93
+ # __in HCERTSTORE hCertStore,
94
+ # __in_opt PCCERT_CONTEXT pPrevCertContext
95
+ # );
96
+ ffi_lib :crypt32
97
+ attach_function :CertEnumCertificatesInStore, %i[handle pointer], :pointer
98
+
99
+ # BOOL
100
+ # WINAPI
101
+ # CertCloseStore(
102
+ # __in_opt HCERTSTORE hCertStore,
103
+ # __in DWORD dwFlags
104
+ # );
105
+ ffi_lib :crypt32
106
+ attach_function :CertCloseStore, %i[handle dword], :bool
107
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Contractful
4
+ # Windows-specific support code (root certificate store loading).
5
+ module Windows
6
+ end
7
+ end
8
+
9
+ require_relative 'windows/root_certs' if Contractful::Platform.windows?