smithy-client 1.0.0.pre0 → 1.0.0.pre1

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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -0
  3. data/VERSION +1 -1
  4. data/lib/smithy-client/anonymous_provider.rb +12 -0
  5. data/lib/smithy-client/auth_option.rb +23 -0
  6. data/lib/smithy-client/auth_scheme.rb +25 -0
  7. data/lib/smithy-client/auth_schemes/anonymous.rb +18 -0
  8. data/lib/smithy-client/auth_schemes/http_api_key.rb +18 -0
  9. data/lib/smithy-client/auth_schemes/http_basic.rb +18 -0
  10. data/lib/smithy-client/auth_schemes/http_bearer.rb +18 -0
  11. data/lib/smithy-client/auth_schemes/http_digest.rb +18 -0
  12. data/lib/smithy-client/base.rb +200 -0
  13. data/lib/smithy-client/block_io.rb +36 -0
  14. data/lib/smithy-client/configuration.rb +222 -0
  15. data/lib/smithy-client/default_params.rb +91 -0
  16. data/lib/smithy-client/dynamic_errors.rb +82 -0
  17. data/lib/smithy-client/endpoint_rules.rb +186 -0
  18. data/lib/smithy-client/handler.rb +29 -0
  19. data/lib/smithy-client/handler_builder.rb +33 -0
  20. data/lib/smithy-client/handler_context.rb +67 -0
  21. data/lib/smithy-client/handler_list.rb +197 -0
  22. data/lib/smithy-client/handler_list_entry.rb +102 -0
  23. data/lib/smithy-client/http/error_inspector.rb +87 -0
  24. data/lib/smithy-client/http/headers.rb +122 -0
  25. data/lib/smithy-client/http/request.rb +57 -0
  26. data/lib/smithy-client/http/response.rb +178 -0
  27. data/lib/smithy-client/http_api_key_provider.rb +18 -0
  28. data/lib/smithy-client/http_bearer_provider.rb +18 -0
  29. data/lib/smithy-client/http_login_provider.rb +19 -0
  30. data/lib/smithy-client/identities/anonymous.rb +10 -0
  31. data/lib/smithy-client/identities/http_api_key.rb +18 -0
  32. data/lib/smithy-client/identities/http_bearer.rb +18 -0
  33. data/lib/smithy-client/identities/http_login.rb +22 -0
  34. data/lib/smithy-client/identity.rb +15 -0
  35. data/lib/smithy-client/log_formatter.rb +215 -0
  36. data/lib/smithy-client/log_param_filter.rb +88 -0
  37. data/lib/smithy-client/log_param_formatter.rb +65 -0
  38. data/lib/smithy-client/managed_file.rb +14 -0
  39. data/lib/smithy-client/net_http/connection_pool.rb +297 -0
  40. data/lib/smithy-client/net_http/handler.rb +160 -0
  41. data/lib/smithy-client/net_http/patches.rb +28 -0
  42. data/lib/smithy-client/networking_error.rb +16 -0
  43. data/lib/smithy-client/pageable_response.rb +138 -0
  44. data/lib/smithy-client/param_converter.rb +243 -0
  45. data/lib/smithy-client/param_validator.rb +213 -0
  46. data/lib/smithy-client/plugin.rb +144 -0
  47. data/lib/smithy-client/plugin_list.rb +141 -0
  48. data/lib/smithy-client/plugins/anonymous_auth.rb +23 -0
  49. data/lib/smithy-client/plugins/checksum_required.rb +51 -0
  50. data/lib/smithy-client/plugins/content_length.rb +26 -0
  51. data/lib/smithy-client/plugins/default_params.rb +22 -0
  52. data/lib/smithy-client/plugins/host_prefix.rb +69 -0
  53. data/lib/smithy-client/plugins/http_api_key_auth.rb +37 -0
  54. data/lib/smithy-client/plugins/http_basic_auth.rb +47 -0
  55. data/lib/smithy-client/plugins/http_bearer_auth.rb +37 -0
  56. data/lib/smithy-client/plugins/http_digest_auth.rb +60 -0
  57. data/lib/smithy-client/plugins/idempotency_token.rb +34 -0
  58. data/lib/smithy-client/plugins/logging.rb +56 -0
  59. data/lib/smithy-client/plugins/net_http.rb +163 -0
  60. data/lib/smithy-client/plugins/pageable_response.rb +37 -0
  61. data/lib/smithy-client/plugins/param_converter.rb +32 -0
  62. data/lib/smithy-client/plugins/param_validator.rb +30 -0
  63. data/lib/smithy-client/plugins/protocol.rb +66 -0
  64. data/lib/smithy-client/plugins/raise_response_errors.rb +33 -0
  65. data/lib/smithy-client/plugins/request_compression.rb +200 -0
  66. data/lib/smithy-client/plugins/response_target.rb +71 -0
  67. data/lib/smithy-client/plugins/retry_errors.rb +125 -0
  68. data/lib/smithy-client/plugins/sign_requests.rb +24 -0
  69. data/lib/smithy-client/plugins/stub_responses.rb +102 -0
  70. data/lib/smithy-client/protocol_spec_matcher.rb +60 -0
  71. data/lib/smithy-client/refreshing_identity_provider.rb +65 -0
  72. data/lib/smithy-client/request.rb +76 -0
  73. data/lib/smithy-client/response.rb +48 -0
  74. data/lib/smithy-client/retry/adaptive.rb +66 -0
  75. data/lib/smithy-client/retry/client_rate_limiter.rb +142 -0
  76. data/lib/smithy-client/retry/quota.rb +58 -0
  77. data/lib/smithy-client/retry/standard.rb +52 -0
  78. data/lib/smithy-client/retry.rb +36 -0
  79. data/lib/smithy-client/rpc_v2_cbor/protocol.rb +38 -0
  80. data/lib/smithy-client/rpc_v2_cbor/request_builder.rb +76 -0
  81. data/lib/smithy-client/rpc_v2_cbor/response_parser.rb +86 -0
  82. data/lib/smithy-client/rpc_v2_cbor/response_stubber.rb +34 -0
  83. data/lib/smithy-client/service_error.rb +57 -0
  84. data/lib/smithy-client/signer.rb +16 -0
  85. data/lib/smithy-client/signers/anonymous.rb +13 -0
  86. data/lib/smithy-client/signers/http_api_key.rb +52 -0
  87. data/lib/smithy-client/signers/http_basic.rb +23 -0
  88. data/lib/smithy-client/signers/http_bearer.rb +19 -0
  89. data/lib/smithy-client/signers/http_digest.rb +21 -0
  90. data/lib/smithy-client/stubbing/data_applicator.rb +61 -0
  91. data/lib/smithy-client/stubbing/empty_stub.rb +69 -0
  92. data/lib/smithy-client/stubbing/endpoint_provider.rb +22 -0
  93. data/lib/smithy-client/stubbing/protocol.rb +29 -0
  94. data/lib/smithy-client/stubbing/stub_data.rb +25 -0
  95. data/lib/smithy-client/stubbing.rb +14 -0
  96. data/lib/smithy-client/stubs.rb +212 -0
  97. data/lib/smithy-client/util.rb +15 -0
  98. data/lib/smithy-client/waiters/poller.rb +93 -0
  99. data/lib/smithy-client/waiters/waiter.rb +113 -0
  100. data/lib/smithy-client.rb +66 -1
  101. metadata +163 -9
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smithy
4
+ module Client
5
+ module NetHTTP
6
+ # The default HTTP handler for Smithy::Client. This is based on Ruby's `Net::HTTP`.
7
+ # @api private
8
+ class Handler < Client::Handler
9
+ # @api private
10
+ class TruncatedBodyError < IOError
11
+ def initialize(bytes_expected, bytes_received)
12
+ msg = "http response body truncated, expected #{bytes_expected} " \
13
+ "bytes, received #{bytes_received} bytes"
14
+ super(msg)
15
+ end
16
+ end
17
+
18
+ # @param [HandlerContext] context
19
+ # @return [Output]
20
+ def call(context)
21
+ transmit(context.config, context.http_request, context.http_response)
22
+ Response.new(context: context)
23
+ end
24
+
25
+ private
26
+
27
+ # @param [Configuration] config
28
+ # @param [HTTP::Request] req
29
+ # @param [HTTP::Response] resp
30
+ # @return [void]
31
+ def transmit(config, req, resp)
32
+ # Monkey patch default content-type set by Net::HTTP
33
+ Thread.current[:net_http_skip_default_content_type] = true
34
+ session(config, req) do |http|
35
+ http.request(build_net_request(req)) do |net_resp|
36
+ bytes_received = signal_response(net_resp, resp)
37
+ complete_response(req, resp, bytes_received)
38
+ end
39
+ end
40
+ rescue ArgumentError => e
41
+ # Invalid verb, ArgumentError is a StandardError
42
+ # Not retryable.
43
+ resp.signal_error(e)
44
+ rescue StandardError => e
45
+ error = NetworkingError.new(e)
46
+ resp.signal_error(error)
47
+ ensure
48
+ # ensure we turn off monkey patch in case of error
49
+ Thread.current[:net_http_skip_default_content_type] = nil
50
+ end
51
+
52
+ def signal_response(net_resp, resp)
53
+ status_code = net_resp.code.to_i
54
+ headers = extract_headers(net_resp)
55
+
56
+ bytes_received = 0
57
+ resp.signal_headers(status_code, headers)
58
+ net_resp.read_body do |chunk|
59
+ bytes_received += chunk.bytesize
60
+ resp.signal_data(chunk)
61
+ end
62
+ bytes_received
63
+ end
64
+
65
+ def complete_response(req, resp, bytes_received)
66
+ if should_verify_bytes?(req, resp)
67
+ verify_bytes_received(resp, bytes_received)
68
+ else
69
+ resp.signal_done
70
+ end
71
+ end
72
+
73
+ def should_verify_bytes?(req, resp)
74
+ req.http_method != 'HEAD' && resp.headers['content-length']
75
+ end
76
+
77
+ def verify_bytes_received(resp, bytes_received)
78
+ bytes_expected = resp.headers['content-length'].to_i
79
+ if bytes_expected == bytes_received
80
+ resp.signal_done
81
+ else
82
+ error = TruncatedBodyError.new(bytes_expected, bytes_received)
83
+ resp.signal_error(NetworkingError.new(error))
84
+ end
85
+ end
86
+
87
+ # @param [Configuration] config
88
+ # @param [HTTP::Request] req
89
+ # @yieldparam [Net::HTTP] http
90
+ def session(config, req, &)
91
+ pool_for(config).session_for(req.endpoint, &)
92
+ end
93
+
94
+ # @param [Configuration] config
95
+ # @return [ConnectionPool]
96
+ def pool_for(config)
97
+ ConnectionPool.for(pool_options(config))
98
+ end
99
+
100
+ # Extracts the {ConnectionPool} configuration options.
101
+ # @param [Configuration] config
102
+ # @return [Hash]
103
+ def pool_options(config)
104
+ ConnectionPool::OPTIONS.keys.each_with_object({}) do |opt, opts|
105
+ opts[opt] = config.send(opt)
106
+ end
107
+ end
108
+
109
+ # Constructs and returns a Net::HTTP::Request object from a {HTTP::Request}.
110
+ # @param [HTTP::Request] request
111
+ # @return [Net::HTTP::Request]
112
+ def build_net_request(request)
113
+ request_class = net_http_request_class(request)
114
+ req = request_class.new(request.endpoint.request_uri, net_headers_for(request))
115
+
116
+ # Net::HTTP adds a default Content-Type when a body is present.
117
+ # Set the body stream when it has an unknown size or when it is > 0.
118
+ if !request.body.respond_to?(:size) ||
119
+ (request.body.respond_to?(:size) && request.body.size.positive?)
120
+ req.body_stream = request.body
121
+ end
122
+ req
123
+ end
124
+
125
+ # @param [HTTP::Request] request
126
+ # @raise [ArgumentError]
127
+ # @return Returns a base `Net::HTTP::Request` class, e.g.,
128
+ # `Net::HTTP::Get`, `Net::HTTP::Post`, etc.
129
+ def net_http_request_class(request)
130
+ Net::HTTP.const_get(request.http_method.capitalize)
131
+ rescue NameError
132
+ msg = "`#{request.http_method}` is not a valid http verb"
133
+ raise ArgumentError, msg
134
+ end
135
+
136
+ # Validate that fields are not trailers and return a hash of headers.
137
+ # @param [HTTP::Request] request
138
+ # @return [Hash<String, String>]
139
+ def net_headers_for(request)
140
+ # Net::HTTP adds a default header for accept-encoding (2.0.0+).
141
+ # Setting a default empty value defeats this.
142
+ # Removing this is necessary for most services to not break request
143
+ # signatures as well as dynamodb crc32 checks (these fail if the
144
+ # response is gzipped).
145
+ headers = { 'accept-encoding' => '' }
146
+ request.headers.each_pair do |key, value|
147
+ headers[key] = value
148
+ end
149
+ headers
150
+ end
151
+
152
+ # @param [Net::HTTP::Response] response
153
+ # @return [Hash<String, String>]
154
+ def extract_headers(response)
155
+ response.to_hash.transform_values(&:first)
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smithy
4
+ module Client
5
+ module NetHTTP
6
+ # @api private
7
+ module Patches
8
+ def self.apply!
9
+ Net::HTTPGenericRequest.prepend(PatchDefaultContentType)
10
+ end
11
+
12
+ # For requests with bodies, Net::HTTP sets a default content type of:
13
+ # 'application/x-www-form-urlencoded'
14
+ # There are cases where we should not send content type at all.
15
+ # Even when no body is supplied, Net::HTTP uses a default empty body
16
+ # and sets it anyway. This patch disables the behavior when a Thread
17
+ # local variable is set.
18
+ module PatchDefaultContentType
19
+ def supply_default_content_type
20
+ return if Thread.current[:net_http_skip_default_content_type]
21
+
22
+ super
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smithy
4
+ module Client
5
+ # Raised when a networking error occurs.
6
+ class NetworkingError < StandardError
7
+ # @param [StandardError] error
8
+ def initialize(error)
9
+ super(error.message)
10
+ @original_error = error
11
+ end
12
+
13
+ attr_reader :original_error
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Smithy
4
+ module Client
5
+ # Raised when calling {PageableResponse#next_page} on a paginator that
6
+ # is on the last page of results. You can call {PageableResponse#last_page?}
7
+ # or {PageableResponse#next_page?} to know if there are more pages.
8
+ class LastPageError < RuntimeError
9
+ # @param [Response] response
10
+ def initialize(response)
11
+ @response = response
12
+ super('unable to fetch next page, end of results reached')
13
+ end
14
+
15
+ # @return [Response]
16
+ attr_reader :response
17
+ end
18
+
19
+ # Decorates a {Smithy::Client::Response} with paging convenience methods.
20
+ # Most API calls provide paged responses to limit the amount of data returned
21
+ # with each response. To optimize for latency, some APIs may return an
22
+ # inconsistent number of responses per page. You should rely on the values of
23
+ # the `next_page?` method or using enumerable methods such as `each_page` rather
24
+ # than the number of items returned to iterate through results. See below for
25
+ # examples.
26
+ #
27
+ # # Enumerator Methods
28
+ # The simplest way to handle paged response data is to use the built-in
29
+ # `each_page` enumerator on the response object:
30
+ #
31
+ # weather = Weather::Client.new
32
+ # weather.list_cities.each_page do |page|
33
+ # puts page.items.map(&:name)
34
+ # end
35
+ #
36
+ # This yields one response object per API call made. The SDK retrieves additional
37
+ # pages of data to complete the request.
38
+ #
39
+ # If the operation allows for it, a selected item can be enumerated using
40
+ # `each_item`:
41
+ #
42
+ # weather = Weather::Client.new
43
+ # weather.list_cities.each_item do |item|
44
+ # puts item.name
45
+ # end
46
+ #
47
+ # # Handling Paged Responses Manually
48
+ # To handle paging yourself, use the Response's `next_page?` method to verify
49
+ # there are more pages to retrieve, or use the `last_page?` method to verify
50
+ # there are no more pages to retrieve.
51
+ #
52
+ # If there are more pages, use the `next_page` method to retrieve the
53
+ # next page of results, as shown in the following example.
54
+ #
55
+ # weather = Weather::Client.new
56
+ #
57
+ # # Get the first page of data
58
+ # response = weather.list_cities
59
+ #
60
+ # # Get additional pages
61
+ # while response.next_page?
62
+ # response = response.next_page
63
+ # # Use the response data here...
64
+ # puts response.items.map(&:name)
65
+ # end
66
+ #
67
+ module PageableResponse
68
+ # @api private
69
+ attr_accessor :paginator
70
+
71
+ # Returns `true` if there are no more results. Calling {#next_page}
72
+ # when this method returns `false` will raise an error.
73
+ # @return [Boolean]
74
+ def last_page?
75
+ return @last_page if @last_page
76
+
77
+ @last_page = !truncated?
78
+ end
79
+
80
+ # Returns `true` if there are more results. Calling {#next_page} will
81
+ # return the next response.
82
+ # @return [Boolean]
83
+ def next_page?
84
+ return @next_page if @next_page
85
+
86
+ @next_page = truncated?
87
+ end
88
+
89
+ # @param [Hash] params A hash of additional request params.
90
+ # @return [Response] Returns the next page of results.
91
+ def next_page(params = {})
92
+ raise LastPageError, self if last_page?
93
+
94
+ params = next_page_params(params)
95
+ context.client.send(context.operation_name, params)
96
+ end
97
+
98
+ # Yields the current and each following response to the given block.
99
+ # @yieldparam [Response] response
100
+ # @return [Enumerable, nil] Returns a new Enumerable if no block is given.
101
+ def each_page(&)
102
+ response = self
103
+ yield(response)
104
+ until response.last_page?
105
+ response = response.next_page
106
+ yield(response)
107
+ end
108
+ end
109
+
110
+ # Yields the current and each following item to the given block.
111
+ # @yieldparam [Object] item
112
+ # @return [Enumerable, nil] Returns a new Enumerable if no block is given.
113
+ def each_item(&)
114
+ response = self
115
+ @paginator.items(response.data).each(&)
116
+ until response.last_page?
117
+ response = response.next_page
118
+ @paginator.items(response.data).each(&)
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ def truncated?
125
+ next_t = @paginator.next_tokens(data)
126
+ !(next_t.empty? || next_t == @paginator.prev_tokens(context.params))
127
+ end
128
+
129
+ def next_page_params(params)
130
+ prev_tokens = @paginator.prev_tokens(context.params)
131
+ # Remove all previous tokens from original params
132
+ # Sometimes a token can be nil and merge would not include it.
133
+ new_params = context.params.except(*prev_tokens)
134
+ new_params.merge!(@paginator.next_tokens(data).merge(params))
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,243 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bigdecimal'
4
+ require 'stringio'
5
+ require 'date'
6
+ require 'time'
7
+ require 'tempfile'
8
+
9
+ module Smithy
10
+ module Client
11
+ # @api private
12
+ class ParamConverter
13
+ include Schema::Shapes
14
+
15
+ @mutex = Mutex.new
16
+ @converters = Hash.new { |h, k| h[k] = {} }
17
+
18
+ def initialize(ref)
19
+ @ref = ref
20
+ @opened_files = []
21
+ end
22
+
23
+ attr_reader :opened_files
24
+
25
+ # @param [Hash] params
26
+ # @return [Hash]
27
+ def convert(params)
28
+ structure(@ref, params)
29
+ end
30
+
31
+ def close_opened_files
32
+ @opened_files.each(&:close)
33
+ @opened_files = []
34
+ end
35
+
36
+ private
37
+
38
+ def c(ref, value)
39
+ self.class.c(ref.shape.class, value, self)
40
+ end
41
+
42
+ def shape(ref, value)
43
+ case ref.shape
44
+ when ListShape then list(ref, value)
45
+ when MapShape then map(ref, value)
46
+ when StructureShape then structure(ref, value)
47
+ when UnionShape then union(ref, value)
48
+ else c(ref, value)
49
+ end
50
+ end
51
+
52
+ def list(ref, values)
53
+ values = c(ref, values)
54
+ return values unless values.is_a?(Array)
55
+
56
+ values.collect { |v| shape(ref.shape.member, v) }
57
+ end
58
+
59
+ def map(ref, values)
60
+ values = c(ref, values)
61
+ return values unless values.is_a?(Hash)
62
+
63
+ values.each.with_object({}) do |(key, value), hash|
64
+ hash[shape(ref.shape.key, key)] = shape(ref.shape.value, value)
65
+ end
66
+ end
67
+
68
+ def structure(ref, values)
69
+ values = c(ref, values)
70
+ return values unless values.respond_to?(:each_pair)
71
+
72
+ values.each_pair do |k, v|
73
+ next if v.nil?
74
+ next unless ref.shape.member?(k)
75
+
76
+ values[k] = shape(ref.shape.member(k), v)
77
+ end
78
+ values
79
+ end
80
+
81
+ def union(ref, values)
82
+ values = c(ref, values)
83
+
84
+ if values.is_a?(Schema::Union)
85
+ _name, member_ref = ref.shape.member_by_type(values.class)
86
+ values = shape(member_ref, values)
87
+ else
88
+ key, value = values.first
89
+ values[key] = shape(ref.shape.member(key), value)
90
+ end
91
+ values
92
+ end
93
+
94
+ class << self
95
+ # Registers a new value converter. Converters run in the context
96
+ # of a shape and value class.
97
+ #
98
+ # # add a converter that stringifies integers
99
+ # shape_class = Shapes::StringShape
100
+ # ParamConverter.add(shape_class, Integer) { |i| i.to_s }
101
+ #
102
+ # @param [Class<Shapes::Shape>] shape_class
103
+ # @param [Class] value_class
104
+ # @param [#call] block An object that responds to `#call`
105
+ # accepting a single argument. This function should perform
106
+ # the value conversion if possible, returning the result.
107
+ # If the conversion is not possible, the original value should
108
+ # be returned.
109
+ # @return [void]
110
+ def add(shape_class, value_class, &block)
111
+ @converters[shape_class][value_class] = block
112
+ end
113
+
114
+ def ensure_open(file, converter)
115
+ if file.closed?
116
+ new_file = File.open(file.path, 'rb')
117
+ converter.opened_files << new_file
118
+ new_file
119
+ else
120
+ file
121
+ end
122
+ end
123
+
124
+ def c(shape, value, instance = nil)
125
+ if (converter = converter_for(shape, value))
126
+ converter.call(value, instance)
127
+ else
128
+ value
129
+ end
130
+ end
131
+
132
+ private
133
+
134
+ def converter_for(shape_class, value)
135
+ unless @converters[shape_class].key?(value.class)
136
+ @mutex.synchronize do
137
+ unless @converters[shape_class].key?(value.class)
138
+ @converters[shape_class][value.class] = find(shape_class, value)
139
+ end
140
+ end
141
+ end
142
+ @converters[shape_class][value.class]
143
+ end
144
+
145
+ def find(shape_class, value)
146
+ converter = nil
147
+ each_base_class(shape_class) do |klass|
148
+ @converters[klass].each do |value_class, block|
149
+ if value.is_a?(value_class)
150
+ converter = block
151
+ break
152
+ end
153
+ end
154
+ break if converter
155
+ end
156
+ converter
157
+ end
158
+
159
+ def each_base_class(shape_class, &)
160
+ shape_class.ancestors.each do |ancestor|
161
+ yield(ancestor) if @converters.key?(ancestor)
162
+ end
163
+ end
164
+ end
165
+
166
+ add(BigDecimalShape, BigDecimal)
167
+ add(BigDecimalShape, Integer) { |i| BigDecimal(i) }
168
+ add(BigDecimalShape, Float) { |f| BigDecimal(f.to_s) }
169
+ add(BigDecimalShape, String) do |str|
170
+ BigDecimal(str)
171
+ rescue ArgumentError
172
+ str
173
+ end
174
+
175
+ add(BlobShape, IO)
176
+ add(BlobShape, File) { |file, converter| ensure_open(file, converter) }
177
+ add(BlobShape, Tempfile) { |tmpfile, converter| ensure_open(tmpfile, converter) }
178
+ add(BlobShape, StringIO)
179
+ add(BlobShape, String)
180
+
181
+ add(BooleanShape, TrueClass)
182
+ add(BooleanShape, FalseClass)
183
+ add(BooleanShape, String) do |str|
184
+ { 'true' => true, 'false' => false }[str]
185
+ end
186
+
187
+ add(EnumShape, String)
188
+ add(EnumShape, Symbol) { |sym, _| sym.to_s }
189
+
190
+ add(IntegerShape, Integer)
191
+ add(IntegerShape, Float) { |f, _| f.to_i }
192
+ add(IntegerShape, String) do |str|
193
+ Integer(str)
194
+ rescue ArgumentError
195
+ str
196
+ end
197
+
198
+ add(IntEnumShape, Integer)
199
+ add(IntEnumShape, Float) { |f, _| f.to_i }
200
+ add(IntEnumShape, String) do |str|
201
+ Integer(str)
202
+ rescue ArgumentError
203
+ str
204
+ end
205
+
206
+ add(FloatShape, Float)
207
+ add(FloatShape, Integer) { |i, _| i.to_f }
208
+ add(FloatShape, String) do |str|
209
+ Float(str)
210
+ rescue ArgumentError
211
+ str
212
+ end
213
+
214
+ add(ListShape, Array) { |a, _| a.dup }
215
+ add(ListShape, Enumerable) { |v, _| v.to_a }
216
+
217
+ add(MapShape, Hash) { |h, _| h.dup }
218
+ add(MapShape, ::Struct) do |s|
219
+ s.members.each.with_object({}) { |k, h| h[k] = s[k] }
220
+ end
221
+
222
+ add(StringShape, String)
223
+ add(StringShape, Symbol) { |sym, _| sym.to_s }
224
+
225
+ add(StructureShape, Hash) { |h, _| h.dup }
226
+ add(StructureShape, ::Struct)
227
+
228
+ add(TimestampShape, Time)
229
+ add(TimestampShape, Date) { |d, _| d.to_time }
230
+ add(TimestampShape, DateTime) { |dt, _| dt.to_time }
231
+ add(TimestampShape, Integer) { |i| Time.at(i) }
232
+ add(TimestampShape, Float) { |f| Time.at(f) }
233
+ add(TimestampShape, String) do |str|
234
+ Time.parse(str)
235
+ rescue ArgumentError
236
+ str
237
+ end
238
+
239
+ add(UnionShape, Hash) { |h, _| h.dup }
240
+ add(UnionShape, Schema::Union)
241
+ end
242
+ end
243
+ end