trino-client 1.0.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 (43) hide show
  1. checksums.yaml +7 -0
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +18 -0
  4. data/.github/workflows/ruby.yml +30 -0
  5. data/.gitignore +4 -0
  6. data/ChangeLog.md +168 -0
  7. data/Gemfile +7 -0
  8. data/LICENSE +202 -0
  9. data/README.md +131 -0
  10. data/Rakefile +45 -0
  11. data/lib/trino-client.rb +1 -0
  12. data/lib/trino/client.rb +23 -0
  13. data/lib/trino/client/client.rb +78 -0
  14. data/lib/trino/client/errors.rb +46 -0
  15. data/lib/trino/client/faraday_client.rb +242 -0
  16. data/lib/trino/client/model_versions/0.149.rb +1683 -0
  17. data/lib/trino/client/model_versions/0.153.rb +1719 -0
  18. data/lib/trino/client/model_versions/0.173.rb +1685 -0
  19. data/lib/trino/client/model_versions/0.178.rb +1964 -0
  20. data/lib/trino/client/model_versions/0.205.rb +2169 -0
  21. data/lib/trino/client/model_versions/303.rb +2574 -0
  22. data/lib/trino/client/model_versions/316.rb +2595 -0
  23. data/lib/trino/client/model_versions/351.rb +2726 -0
  24. data/lib/trino/client/models.rb +38 -0
  25. data/lib/trino/client/query.rb +144 -0
  26. data/lib/trino/client/statement_client.rb +279 -0
  27. data/lib/trino/client/version.rb +20 -0
  28. data/modelgen/model_versions.rb +280 -0
  29. data/modelgen/modelgen.rb +119 -0
  30. data/modelgen/models.rb +31 -0
  31. data/modelgen/trino_models.rb +270 -0
  32. data/release.rb +56 -0
  33. data/spec/basic_query_spec.rb +82 -0
  34. data/spec/client_spec.rb +75 -0
  35. data/spec/gzip_spec.rb +40 -0
  36. data/spec/model_spec.rb +35 -0
  37. data/spec/spec_helper.rb +42 -0
  38. data/spec/statement_client_spec.rb +637 -0
  39. data/spec/tpch/q01.sql +21 -0
  40. data/spec/tpch/q02.sql +43 -0
  41. data/spec/tpch_query_spec.rb +41 -0
  42. data/trino-client.gemspec +31 -0
  43. metadata +211 -0
data/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # Trino client library for Ruby
2
+
3
+ [![Build Status](https://travis-ci.org/treasure-data/trino-client-ruby.svg?branch=master)](https://travis-ci.org/treasure-data/trino-client-ruby) [![Gem](https://img.shields.io/gem/v/trino-client)](https://rubygems.org/gems/trino-client) [![Gem](https://img.shields.io/gem/dt/trino-client)](https://rubygems.org/gems/trino-client) [![GitHub](https://img.shields.io/github/license/treasure-data/trino-client-ruby)]()
4
+
5
+ Trino is a distributed SQL query engine for big data:
6
+ https://trino.io/
7
+
8
+ This is a client library for Ruby to run queries on Trino.
9
+
10
+ ## Example
11
+
12
+ ```ruby
13
+ require 'trino-client'
14
+
15
+ # create a client object:
16
+ client = Trino::Client.new(
17
+ server: "localhost:8880", # required option
18
+ ssl: {verify: false},
19
+ catalog: "native",
20
+ schema: "default",
21
+ user: "frsyuki",
22
+ password: "********",
23
+ time_zone: "US/Pacific",
24
+ language: "English",
25
+ properties: {
26
+ "hive.force_local_scheduling": true,
27
+ "raptor.reader_stream_buffer_size": "32MB"
28
+ },
29
+ http_proxy: "proxy.example.com:8080",
30
+ http_debug: true
31
+ )
32
+
33
+ # run a query and get results as an array of arrays:
34
+ columns, rows = client.run("select * from sys.node")
35
+ rows.each {|row|
36
+ p row # row is an array
37
+ }
38
+
39
+ # run a query and get results as an array of hashes:
40
+ results = client.run_with_names("select alpha, 1 AS beta from tablename")
41
+ results.each {|row|
42
+ p row['alpha'] # access by name
43
+ p row['beta']
44
+ p row.values[0] # access by index
45
+ p row.values[1]
46
+ }
47
+
48
+ # run a query and fetch results streamingly:
49
+ client.query("select * from sys.node") do |q|
50
+ # get columns:
51
+ q.columns.each {|column|
52
+ puts "column: #{column.name}.#{column.type}"
53
+ }
54
+
55
+ # get query results. it feeds more rows until
56
+ # query execution finishes:
57
+ q.each_row {|row|
58
+ p row # row is an array
59
+ }
60
+ end
61
+
62
+ # killing a query
63
+ query = client.query("select * from sys.node")
64
+ query_id = query.query_info.query_id
65
+ query.each_row {|row| ... } # when a thread is processing the query,
66
+ client.kill(query_id) # another thread / process can kill the query.
67
+ ```
68
+
69
+ ## Build models
70
+
71
+ ```
72
+ $ bundle exec rake modelgen:latest
73
+ ```
74
+
75
+ ## Options
76
+
77
+ * **server** sets address (and port) of a Trino coordinator server.
78
+ * **ssl** enables https.
79
+ * Setting `true` enables SSL and verifies server certificate using system's built-in certificates.
80
+ * Setting `{verify: false}` enables SSL but doesn't verify server certificate.
81
+ * Setting a Hash object enables SSL and verify server certificate with options:
82
+ * **ca_file**: path of a CA certification file in PEM format
83
+ * **ca_path**: path of a CA certification directory containing certifications in PEM format
84
+ * **cert_store**: a `OpenSSL::X509::Store` object used for verification
85
+ * **client_cert**: a `OpenSSL::X509::Certificate` object as client certificate
86
+ * **client_key**: a `OpenSSL::PKey::RSA` or `OpenSSL::PKey::DSA` object used for client certificate
87
+ * **catalog** sets catalog (connector) name of Trino such as `hive-cdh4`, `hive-hadoop1`, etc.
88
+ * **schema** sets default schema name of Trino. You need to use qualified name like `FROM myschema.table1` to use non-default schemas.
89
+ * **source** sets source name to connect to a Trino. This name is shown on Trino web interface.
90
+ * **client_info** sets client info to queries. It can be a string to pass a raw string, or an object that can be encoded to JSON.
91
+ * **client_tags** sets client tags to queries. It needs to be an array of strings. The tags are shown on web interface.
92
+ * **user** sets user name to connect to a Trino.
93
+ * **password** sets a password to connect to Trino using basic auth.
94
+ * **time_zone** sets time zone of queries. Time zone affects some functions such as `format_datetime`.
95
+ * **language** sets language of queries. Language affects some functions such as `format_datetime`.
96
+ * **properties** set session properties. Session properties affect internal behavior such as `hive.force_local_scheduling: true`, `raptor.reader_stream_buffer_size: "32MB"`, etc.
97
+ * **query_timeout** sets timeout in seconds for the entire query execution (from the first API call until there're no more output data). If timeout happens, client raises TrinoQueryTimeoutError. Default is nil (disabled).
98
+ * **plan_timeout** sets timeout in seconds for query planning execution (from the first API call until result columns become available). If timeout happens, client raises TrinoQueryTimeoutError. Default is nil (disabled).
99
+ * **http_headers** sets custom HTTP headers. It must be a Hash of string to string.
100
+ * **http_proxy** sets host:port of a HTTP proxy server.
101
+ * **http_debug** enables debug message to STDOUT for each HTTP requests.
102
+ * **http_open_timeout** sets timeout in seconds to open new HTTP connection.
103
+ * **http_timeout** sets timeout in seconds to read data from a server.
104
+ * **gzip** enables gzip compression.
105
+ * **follow_redirect** enables HTTP redirection support.
106
+ * **model_version** set the Trino version to which a job is submitted. Supported versions are 351, 316, 303, 0.205, 0.178, 0.173, 0.153 and 0.149. Default is 351.
107
+
108
+ See [RDoc](http://www.rubydoc.info/gems/presto-client/) for the full documentation.
109
+
110
+ ## Development
111
+
112
+ ### Releasing a new version
113
+
114
+ 1. First update `lib/trino/client/version.rb` to the next version.
115
+ 2. Run the following command which will update `ChangeLog.md` file automatically.
116
+ ```
117
+ $ ruby release.rb
118
+ ```
119
+
120
+ 3. Create tag
121
+ ```
122
+ $ git commit -am "vX.Y.Z"
123
+ $ git tag "vX.Y.Z"
124
+ % git push --tags
125
+ ```
126
+
127
+ 4. Push package
128
+ ```
129
+ $ gem build trino-client.gemspec
130
+ $ gem push trino-client-X.Y.Z.gem
131
+ ```
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+
4
+ require 'rake/testtask'
5
+ require 'rake/clean'
6
+
7
+ require 'rspec/core/rake_task'
8
+
9
+ RSpec::Core::RakeTask.new(:spec)
10
+
11
+ task :default => [:spec, :build]
12
+
13
+ GEN_MODEL_VERSIONS = %w[
14
+ 351
15
+ ]
16
+
17
+ namespace "modelgen" do
18
+ task :latest => :all do
19
+ require 'erb'
20
+ erb = ERB.new(File.read("modelgen/models.rb"))
21
+ @versions = GEN_MODEL_VERSIONS
22
+ @latest_version = GEN_MODEL_VERSIONS.last
23
+ data = erb.result
24
+ File.write("lib/trino/client/models.rb", data)
25
+ end
26
+
27
+ task :all => GEN_MODEL_VERSIONS
28
+
29
+ GEN_MODEL_VERSIONS.each do |ver|
30
+ file "build/trino-#{ver}.tar.gz" do
31
+ mkdir_p "build"
32
+ sh "curl -L -o build/trino-#{ver}.tar.gz https://github.com/trinodb/trino/archive/#{ver}.tar.gz"
33
+ end
34
+
35
+ file "lib/trino/client/model_versions/#{ver}.rb" => "build/trino-#{ver}.tar.gz" do
36
+ sh "tar zxf build/trino-#{ver}.tar.gz -C build"
37
+ mkdir_p "lib/trino/client/model_versions"
38
+ sh "#{RbConfig.ruby} modelgen/modelgen.rb #{ver} build/trino-#{ver} modelgen/model_versions.rb lib/trino/client/model_versions/#{ver}.rb"
39
+ puts "Generated lib/trino/client/model_versions/#{ver}.rb."
40
+ end
41
+
42
+ task ver => "lib/trino/client/model_versions/#{ver}.rb"
43
+ end
44
+ end
45
+
@@ -0,0 +1 @@
1
+ require 'trino/client'
@@ -0,0 +1,23 @@
1
+ #
2
+ # Trino client for Ruby
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ module Trino
17
+ module Client
18
+
19
+ require 'trino/client/version'
20
+ require 'trino/client/client'
21
+
22
+ end
23
+ end
@@ -0,0 +1,78 @@
1
+ #
2
+ # Trino client for Ruby
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ module Trino::Client
17
+
18
+ require 'trino/client/models'
19
+ require 'trino/client/query'
20
+
21
+ class Client
22
+ def initialize(options)
23
+ @options = options
24
+ end
25
+
26
+ def query(query, &block)
27
+ q = Query.start(query, @options)
28
+ if block
29
+ begin
30
+ yield q
31
+ ensure
32
+ q.close
33
+ end
34
+ else
35
+ return q
36
+ end
37
+ end
38
+
39
+ def resume_query(next_uri)
40
+ return Query.resume(next_uri, @options)
41
+ end
42
+
43
+ def kill(query_id)
44
+ return Query.kill(query_id, @options)
45
+ end
46
+
47
+ def run(query)
48
+ q = Query.start(query, @options)
49
+ begin
50
+ columns = q.columns
51
+ if columns.empty?
52
+ return [], []
53
+ end
54
+ return columns, q.rows
55
+ ensure
56
+ q.close
57
+ end
58
+ end
59
+
60
+ # Accepts the raw response from the Trino Client and returns an
61
+ # array of hashes where you can access the data in each row using the
62
+ # output name specified in the query with AS:
63
+ # SELECT expression AS output_name
64
+ def run_with_names(query)
65
+ columns, rows = run(query)
66
+
67
+ column_names = columns.map(&:name)
68
+
69
+ rows.map do |row|
70
+ Hash[column_names.zip(row)]
71
+ end
72
+ end
73
+ end
74
+
75
+ def self.new(*args)
76
+ Client.new(*args)
77
+ end
78
+ end
@@ -0,0 +1,46 @@
1
+ #
2
+ # Trino client for Ruby
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ module Trino::Client
17
+ class TrinoError < StandardError
18
+ end
19
+
20
+ class TrinoHttpError < TrinoError
21
+ def initialize(status, message)
22
+ super(message)
23
+ @status = status
24
+ end
25
+
26
+ attr_reader :status
27
+ end
28
+
29
+ class TrinoClientError < TrinoError
30
+ end
31
+
32
+ class TrinoQueryError < TrinoError
33
+ def initialize(message, query_id, error_code, error_name, failure_info)
34
+ super(message)
35
+ @query_id = query_id
36
+ @error_code = error_code
37
+ @error_name = error_name
38
+ @failure_info = failure_info
39
+ end
40
+
41
+ attr_reader :error_code, :error_name, :failure_info
42
+ end
43
+
44
+ class TrinoQueryTimeoutError < TrinoError
45
+ end
46
+ end
@@ -0,0 +1,242 @@
1
+ #
2
+ # Trino client for Ruby
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ module Trino::Client
17
+
18
+ require 'cgi'
19
+
20
+ module TrinoHeaders
21
+ TRINO_USER = "X-Trino-User"
22
+ TRINO_SOURCE = "X-Trino-Source"
23
+ TRINO_CATALOG = "X-Trino-Catalog"
24
+ TRINO_SCHEMA = "X-Trino-Schema"
25
+ TRINO_TIME_ZONE = "X-Trino-Time-Zone"
26
+ TRINO_LANGUAGE = "X-Trino-Language"
27
+ TRINO_SESSION = "X-Trino-Session"
28
+ TRINO_CLIENT_INFO = "X-Trino-Client-Info";
29
+ TRINO_CLIENT_TAGS = "X-Trino-Client-Tags";
30
+
31
+ TRINO_CURRENT_STATE = "X-Trino-Current-State"
32
+ TRINO_MAX_WAIT = "X-Trino-Max-Wait"
33
+ TRINO_MAX_SIZE = "X-Trino-Max-Size"
34
+ TRINO_PAGE_SEQUENCE_ID = "X-Trino-Page-Sequence-Id"
35
+ end
36
+
37
+ module PrestoHeaders
38
+ PRESTO_USER = "X-Presto-User"
39
+ PRESTO_SOURCE = "X-Presto-Source"
40
+ PRESTO_CATALOG = "X-Presto-Catalog"
41
+ PRESTO_SCHEMA = "X-Presto-Schema"
42
+ PRESTO_TIME_ZONE = "X-Presto-Time-Zone"
43
+ PRESTO_LANGUAGE = "X-Presto-Language"
44
+ PRESTO_SESSION = "X-Presto-Session"
45
+ PRESTO_CLIENT_INFO = "X-Presto-Client-Info";
46
+ PRESTO_CLIENT_TAGS = "X-Presto-Client-Tags";
47
+
48
+ PRESTO_CURRENT_STATE = "X-Presto-Current-State"
49
+ PRESTO_MAX_WAIT = "X-Presto-Max-Wait"
50
+ PRESTO_MAX_SIZE = "X-Presto-Max-Size"
51
+ PRESTO_PAGE_SEQUENCE_ID = "X-Presto-Page-Sequence-Id"
52
+ end
53
+
54
+ HEADERS = {
55
+ "User-Agent" => "trino-ruby/#{VERSION}",
56
+ }
57
+
58
+ def self.faraday_client(options)
59
+ server = options[:server]
60
+ unless server
61
+ raise ArgumentError, ":server option is required"
62
+ end
63
+
64
+ ssl = faraday_ssl_options(options)
65
+
66
+ if options[:password] && !ssl
67
+ raise ArgumentError, "Protocol must be https when passing a password"
68
+ end
69
+
70
+ url = "#{ssl ? "https" : "http"}://#{server}"
71
+ proxy = options[:http_proxy] || options[:proxy] # :proxy is obsoleted
72
+
73
+ faraday_options = {url: url, proxy: "#{proxy}"}
74
+ faraday_options[:ssl] = ssl if ssl
75
+
76
+ faraday = Faraday.new(faraday_options) do |faraday|
77
+ if options[:user] && options[:password]
78
+ faraday.basic_auth(options[:user], options[:password])
79
+ end
80
+ if options[:follow_redirect]
81
+ faraday.use FaradayMiddleware::FollowRedirects
82
+ end
83
+ if options[:gzip]
84
+ faraday.use FaradayMiddleware::Gzip
85
+ end
86
+ faraday.response :logger if options[:http_debug]
87
+ faraday.adapter Faraday.default_adapter
88
+ end
89
+
90
+ faraday.headers.merge!(HEADERS)
91
+ faraday.headers.merge!(optional_headers(options))
92
+
93
+ return faraday
94
+ end
95
+
96
+ def self.faraday_ssl_options(options)
97
+ ssl = options[:ssl]
98
+
99
+ case ssl
100
+ when true
101
+ ssl = {verify: true}
102
+
103
+ when Hash
104
+ verify = ssl.fetch(:verify, true)
105
+ case verify
106
+ when true
107
+ # detailed SSL options. pass through to faraday
108
+ when nil, false
109
+ ssl = {verify: false}
110
+ else
111
+ raise ArgumentError, "Can't convert #{verify.class} of :verify option of :ssl option to true or false"
112
+ end
113
+
114
+ when nil, false
115
+ ssl = false
116
+
117
+ else
118
+ raise ArgumentError, "Can't convert #{ssl.class} of :ssl option to true, false, or Hash"
119
+ end
120
+
121
+ return ssl
122
+ end
123
+
124
+ def self.optional_headers(options)
125
+ usePrestoHeader = false
126
+ if v = options[:model_version] && v < 351
127
+ usePrestoHeader = true
128
+ end
129
+
130
+ headers = {}
131
+ if v = options[:user]
132
+ if usePrestoHeader
133
+ headers[PrestoHeaders::PRESTO_USER] = v
134
+ else
135
+ headers[TrinoHeaders::TRINO_USER] = v
136
+ end
137
+ end
138
+ if v = options[:source]
139
+ if usePrestoHeader
140
+ headers[PrestoHeaders::PRESTO_SOURCE] = v
141
+ else
142
+ headers[TrinoHeaders::TRINO_SOURCE] = v
143
+ end
144
+ end
145
+ if v = options[:catalog]
146
+ if usePrestoHeader
147
+ headers[PrestoHeaders::PRESTO_CATALOG] = v
148
+ else
149
+ headers[TrinoHeaders::TRINO_CATALOG] = v
150
+ end
151
+ end
152
+ if v = options[:schema]
153
+ if usePrestoHeader
154
+ headers[PrestoHeaders::PRESTO_SCHEMA] = v
155
+ else
156
+ headers[TrinoHeaders::TRINO_SCHEMA] = v
157
+ end
158
+ end
159
+ if v = options[:time_zone]
160
+ if usePrestoHeader
161
+ headers[PrestoHeaders::PRESTO_TIME_ZONE] = v
162
+ else
163
+ headers[TrinoHeaders::TRINO_TIME_ZONE] = v
164
+ end
165
+ end
166
+ if v = options[:language]
167
+ if usePrestoHeader
168
+ headers[PrestoHeaders::PRESTO_LANGUAGE] = v
169
+ else
170
+ headers[TrinoHeaders::TRINO_LANGUAGE] = v
171
+ end
172
+ end
173
+ if v = options[:properties]
174
+ if usePrestoHeader
175
+ headers[PrestoHeaders::PRESTO_SESSION] = encode_properties(v)
176
+ else
177
+ headers[TrinoHeaders::TRINO_SESSION] = encode_properties(v)
178
+ end
179
+ end
180
+ if v = options[:client_info]
181
+ if usePrestoHeader
182
+ headers[PrestoHeaders::PRESTO_CLIENT_INFO] = encode_client_info(v)
183
+ else
184
+ headers[TrinoHeaders::TRINO_CLIENT_INFO] = encode_client_info(v)
185
+ end
186
+ end
187
+ if v = options[:client_tags]
188
+ if usePrestoHeader
189
+ headers[PrestoHeaders::PRESTO_CLIENT_TAGS] = encode_client_tags(v)
190
+ else
191
+ headers[TrinoHeaders::TRINO_CLIENT_TAGS] = encode_client_tags(v)
192
+ end
193
+ end
194
+ if options[:enable_x_msgpack]
195
+ # option name is enable_"x"_msgpack because "Accept: application/x-msgpack" header is
196
+ # not officially supported by Trino. We can use this option only if a proxy server
197
+ # decodes & encodes response body. Once this option is supported by Trino, option
198
+ # name should be enable_msgpack, which might be slightly different behavior.
199
+ headers['Accept'] = 'application/x-msgpack,application/json'
200
+ end
201
+ if v = options[:http_headers]
202
+ headers.merge!(v)
203
+ end
204
+ headers
205
+ end
206
+
207
+ HTTP11_SEPARATOR = ["(", ")", "<", ">", "@", ",", ";", ":", "\\", "<", ">", "/", "[", "]", "?", "=", "{", "}", " ", "\v"]
208
+ HTTP11_TOKEN_CHARSET = (32..126).map {|x| x.chr } - HTTP11_SEPARATOR
209
+ HTTP11_TOKEN_REGEXP = /^[#{Regexp.escape(HTTP11_TOKEN_CHARSET.join)}]+\z/
210
+ HTTP11_CTL_CHARSET = (0..31).map {|x| x.chr } + [127.chr]
211
+ HTTP11_CTL_CHARSET_REGEXP = /[#{Regexp.escape(HTTP11_CTL_CHARSET.join)}]/
212
+
213
+ def self.encode_properties(properties)
214
+ properties.map do |k, v|
215
+ token = k.to_s
216
+ field_value = v.to_s # TODO LWS encoding is not implemented
217
+ unless k =~ HTTP11_TOKEN_REGEXP
218
+ raise Faraday::ClientError, "Key of properties can't include HTTP/1.1 control characters or separators (#{HTTP11_SEPARATOR.map {|c| c =~ /\s/ ? c.dump : c }.join(' ')})"
219
+ end
220
+ if field_value =~ HTTP11_CTL_CHARSET_REGEXP
221
+ raise Faraday::ClientError, "Value of properties can't include HTTP/1.1 control characters"
222
+ end
223
+ field_value = CGI.escape(field_value)
224
+ "#{token}=#{field_value}"
225
+ end
226
+ end
227
+
228
+ def self.encode_client_info(info)
229
+ if info.is_a?(String)
230
+ info
231
+ else
232
+ JSON.dump(info)
233
+ end
234
+ end
235
+
236
+ def self.encode_client_tags(tags)
237
+ Array(tags).join(",")
238
+ end
239
+
240
+ private_class_method :faraday_ssl_options, :optional_headers, :encode_properties, :encode_client_info, :encode_client_tags
241
+
242
+ end