restify 1.15.2 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -2
  3. data/README.md +23 -31
  4. data/doc/file.README.html +192 -0
  5. data/lib/restify/adapter/base.rb +4 -0
  6. data/lib/restify/adapter/telemetry.rb +54 -0
  7. data/lib/restify/adapter/typhoeus.rb +37 -4
  8. data/lib/restify/context.rb +3 -3
  9. data/lib/restify/error.rb +2 -2
  10. data/lib/restify/link.rb +4 -4
  11. data/lib/restify/processors/base/parsing.rb +2 -21
  12. data/lib/restify/processors/base.rb +1 -1
  13. data/lib/restify/promise.rb +2 -2
  14. data/lib/restify/registry.rb +1 -1
  15. data/lib/restify/relation.rb +45 -17
  16. data/lib/restify/request.rb +6 -6
  17. data/lib/restify/timeout.rb +2 -2
  18. data/lib/restify/version.rb +3 -3
  19. data/lib/restify.rb +0 -1
  20. data/spec/restify/cache_spec.rb +16 -12
  21. data/spec/restify/context_spec.rb +8 -3
  22. data/spec/restify/error_spec.rb +13 -16
  23. data/spec/restify/features/head_requests_spec.rb +5 -4
  24. data/spec/restify/features/opentelemetry_spec.rb +46 -0
  25. data/spec/restify/features/request_bodies_spec.rb +8 -8
  26. data/spec/restify/features/request_errors_spec.rb +2 -2
  27. data/spec/restify/features/request_headers_spec.rb +3 -6
  28. data/spec/restify/features/response_errors_spec.rb +1 -1
  29. data/spec/restify/features/webmock_spec.rb +27 -0
  30. data/spec/restify/global_spec.rb +10 -10
  31. data/spec/restify/processors/base_spec.rb +6 -7
  32. data/spec/restify/processors/json_spec.rb +21 -62
  33. data/spec/restify/processors/msgpack_spec.rb +33 -70
  34. data/spec/restify/promise_spec.rb +31 -31
  35. data/spec/restify/registry_spec.rb +5 -7
  36. data/spec/restify/relation_spec.rb +185 -7
  37. data/spec/restify/resource_spec.rb +47 -53
  38. data/spec/restify/timeout_spec.rb +3 -3
  39. data/spec/restify_spec.rb +12 -73
  40. data/spec/spec_helper.rb +12 -15
  41. data/spec/support/opentelemetry.rb +29 -0
  42. data/spec/support/stub_server.rb +4 -0
  43. metadata +37 -64
  44. data/lib/restify/adapter/em.rb +0 -134
  45. data/lib/restify/adapter/pooled_em.rb +0 -269
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c29c95a9b1c0b18f2a73b51c99da7cdddf163e1cee33c43272810f5557b1375b
4
- data.tar.gz: 95f956684154058cc6f25b7d4ffebde6706864b00c1369e91c8f8b5fcc50f0d9
3
+ metadata.gz: 2b3748ffce3669fb7d0cf84c08ef2cb5732e18edd0a2c7f49c611c00a7d09058
4
+ data.tar.gz: 86918b318d74c3c1fa5ed03e68bd4fcc877a0697b37a324407508a1d2cb78477
5
5
  SHA512:
6
- metadata.gz: f420cc4d0c4ebbdade1bc1a780c24218b5eea707bd8e420c113d155d58067936464dc3d4807717de8f515ed586784c9f13476b46c66f68c8d1181501937db3a2
7
- data.tar.gz: 71da11cdf50d25abc968af02c6157c63f1aa5703c88f7ae28c3394aa028d9f038013d220283510532f7b540dffc69d0abad5fe4952c4e27df4910a4009475ae5
6
+ metadata.gz: 8fca1f764083979b98c66a55c2a3d49e3ad4c77bff142f2e5af8d40c4003fd2151bb8bce4076cb66b856b91054fba4cb814bb0635d2b3be3cf9d7e6b301f79f2
7
+ data.tar.gz: bdb52897e18ae8ba441128f9147a383ddde7e777f92ab59cce5353ac8e8feda23c9af072f9aaf2b4cb252085840bb7ef70927b289a42b66e6209a650d3d5a4c5
data/CHANGELOG.md CHANGED
@@ -5,8 +5,8 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7
7
 
8
-
9
8
  ## Unreleased
9
+
10
10
  ---
11
11
 
12
12
  ### New
@@ -17,6 +17,39 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
17
17
 
18
18
  ### Breaks
19
19
 
20
+ ## 2.0.1 - (2025-02-16)
21
+
22
+ ---
23
+
24
+ ### Fixes
25
+
26
+ - Restore compatibility with WebMocks Typhoeus instrumentation
27
+ - Catch WebMock exception and bubble them to the request promises
28
+
29
+ ## 2.0.0 - (2025-02-14)
30
+
31
+ ---
32
+
33
+ ### New
34
+
35
+ - Support for Ruby 3.4
36
+ - Experimental support for OpenTelemetry tracing
37
+
38
+ ### Changes
39
+
40
+ - Strict keyword handling for request methods
41
+
42
+ All request methods take parameters and headers as explicit keyword arguments, not as secondary arguments anymore. Change e.g. `get({}, {headers: ...})` into `get(headers: ...)`, and `head(params)` to `head(params:)`.
43
+
44
+ `#post`, `#put`, and `#patch` still take an optional first positional data/body argument. Change `post(body, {}, {headers: ...})` to `post(body, headers: ...)`.
45
+
46
+ `#get`, `#head`, and `#delete` accept a hash as the first positional argument, which is merged with `params:`. Therefore, passing parameters as data works too: `get({id: 1})`.
47
+
48
+ ### Breaks
49
+
50
+ - Remove indifferent access methods (Hashie) from responses
51
+ - Removed `em` and `em-pooled` adapters
52
+ - Require Ruby 3.1+
20
53
 
21
54
  ## 1.15.2 - (2021-12-23)
22
55
 
@@ -26,7 +59,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
26
59
 
27
60
  - ActiveSupport v7.0 issues with cache module
28
61
 
29
-
30
62
  ## 1.15.1 - (2021-07-15)
31
63
 
32
64
  ---
data/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # Restify
2
2
 
3
- [![Build Status](https://travis-ci.org/jgraichen/restify.svg?branch=master)](https://travis-ci.org/jgraichen/restify)
4
- [![Code Quality](https://codebeat.co/badges/18ffe6b7-8239-493a-b5b6-be329b9f275d)](https://codebeat.co/projects/github-com-jgraichen-restify-master)
3
+ [![Gem Version](https://img.shields.io/gem/v/restify?logo=ruby)](https://rubygems.org/gems/restify)
4
+ [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/jgraichen/restify/test.yml?logo=github)](https://github.com/jgraichen/restify/actions)
5
+ [![Code Quality](https://codebeat.co/badges/368f8033-bd76-48bc-9777-85f1d4befa94)](https://codebeat.co/projects/github-com-jgraichen-restify-main)
5
6
 
6
7
  Restify is an hypermedia REST client that does parallel, concurrent and keep-alive requests by default.
7
8
 
@@ -27,16 +28,19 @@ Links are extracted from
27
28
  * HTTP Link header
28
29
  * Github-style relations in payloads
29
30
 
30
- ### Planned features
31
+ ## Installation
31
32
 
32
- * HTTP cache
33
- * API versions via header
34
- * Content-Type and Language negotiation
35
- * Processors for JSON-HAL, etc.
33
+ Add it to your Gemfile:
36
34
 
37
- ## Installation
35
+ ```ruby
36
+ gem 'restify', '~> 2.0'
37
+ ```
38
38
 
39
- Add it to your Gemfile or install it manually: `$ gem install restify`
39
+ Or install it manually:
40
+
41
+ ```console
42
+ gem install restify
43
+ ```
40
44
 
41
45
  ## Usage
42
46
 
@@ -51,7 +55,7 @@ client = Restify.new('https://api.github.com').get.value
51
55
  # ...
52
56
  ```
53
57
 
54
- We are essentially requesting `'http://api.github.com'` via HTTP `get`. `get` is returning an `Promise`, similar to Java's `Future`. The `value` call resolves the returned `Promise` by blocking the thread until the resource is actually there. `value!` will additionally raise errors instead of returning `nil`. You can chain handlers using the `then` method. This allows you to be build a dependency chain that will be executed when the last promise is needed.
58
+ We are essentially requesting `'http://api.github.com'` via HTTP `get`. `get` is returning a `Promise`, similar to Java's `Future`. The `value` call resolves the returned `Promise` by blocking the thread until the resource is actually there. `value!` will additionally raise errors instead of returning `nil`. You can chain handlers using the `then` method. This allows you to be build a dependency chain that will be executed when the last promise is needed.
55
59
 
56
60
  As we can see GitHub returns us a field `repository_url` with a URI template. Restify automatically scans for `*_url` fields in the JSON response and exposes these as relations. It additionally scans the HTTP Header field `Link` for relations like pagination.
57
61
 
@@ -65,33 +69,21 @@ repositories = client.rel(:repository)
65
69
  This gets us the relation named `repository` that we can request now. The usual HTTP methods are available on a relation:
66
70
 
67
71
  ```ruby
68
- def get(params = {})
69
- request :get, nil, params
70
- end
71
-
72
- def delete(params = {})
73
- request :delete, nil, params
74
- end
75
-
76
- def post(data = {}, params = {})
77
- request :post, data, params
78
- end
79
-
80
- def put(data = {}, params = {})
81
- request :put, data, params
82
- end
72
+ def get(params, params:, headers:, **)
73
+ def head(params, params:, headers:, **)
74
+ def delete(params, params:, headers:, **)
83
75
 
84
- def patch(data = {}, params = {})
85
- request :patch, data, params
86
- end
76
+ def put(data = nil, params:, headers:, **)
77
+ def post(data = nil, params:, headers:, **)
78
+ def patch(data = nil, params:, headers:, **)
87
79
  ```
88
80
 
89
- URL templates can define some parameters such as `{owner}` or `{repo}`. They will be expanded from the `params` given to the HTTP method method.
81
+ URL templates can define some parameters such as `{owner}` or `{repo}`. They will be expanded from the `params` given to the HTTP method.
90
82
 
91
83
  Now send a GET request with some parameters to request a specific repository:
92
84
 
93
85
  ```ruby
94
- repo = repositories.get(owner: 'jgraichen', repo: 'restify').value
86
+ repo = repositories.get({owner: 'jgraichen', repo: 'restify'}).value
95
87
  ```
96
88
 
97
89
  Now fetch a list of commits for this repo and get this first one:
@@ -121,7 +113,7 @@ See commented example in main spec [`spec/restify_spec.rb`](https://github.com/j
121
113
 
122
114
  ## License
123
115
 
124
- Copyright (C) 2014-2018 Jan Graichen
116
+ Copyright (C) 2014-2025 Jan Graichen
125
117
 
126
118
  This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
127
119
 
@@ -0,0 +1,192 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>
7
+ File: README
8
+
9
+ &mdash; Documentation by YARD 0.9.37
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" />
16
+
17
+ <script type="text/javascript">
18
+ pathId = "README";
19
+ relpath = '';
20
+ </script>
21
+
22
+
23
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
24
+
25
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
26
+
27
+
28
+ </head>
29
+ <body>
30
+ <div class="nav_wrap">
31
+ <iframe id="nav" src="file_list.html?1"></iframe>
32
+ <div id="resizer"></div>
33
+ </div>
34
+
35
+ <div id="main" tabindex="-1">
36
+ <div id="header">
37
+ <div id="menu">
38
+
39
+ <a href="_index.html">Index</a> &raquo;
40
+ <span class="title">File: README</span>
41
+
42
+ </div>
43
+
44
+ <div id="search">
45
+
46
+ <a class="full_list_link" id="class_list_link"
47
+ href="class_list.html">
48
+
49
+ <svg width="24" height="24">
50
+ <rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
51
+ <rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
52
+ <rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
53
+ </svg>
54
+ </a>
55
+
56
+ </div>
57
+ <div class="clear"></div>
58
+ </div>
59
+
60
+ <div id="content"><div id='filecontents'><h1 id="restify">Restify</h1>
61
+
62
+ <p><a href="https://rubygems.org/gems/restify"><img src="https://img.shields.io/gem/v/restify?logo=ruby" alt="Gem Version"></a>
63
+ <a href="https://github.com/jgraichen/restify/actions"><img src="https://img.shields.io/github/actions/workflow/status/jgraichen/restify/test.yml?logo=github" alt="GitHub Actions Workflow Status"></a>
64
+ <a href="https://codebeat.co/projects/github-com-jgraichen-restify-main"><img src="https://codebeat.co/badges/368f8033-bd76-48bc-9777-85f1d4befa94" alt="Code Quality"></a></p>
65
+
66
+ <p>Restify is an hypermedia REST client that does parallel, concurrent and keep-alive requests by default.</p>
67
+
68
+ <p>Restify scans Link headers and returned resource for links and relations to other resources, represented as RFC6570 URI Templates, and exposes those to the developer.</p>
69
+
70
+ <p>Restify can be used to consume hypermedia REST APIs (like GitHubs), to build a site-specific library or to use within your own backend services.</p>
71
+
72
+ <p>Restify is build upon the following libraries:</p>
73
+
74
+ <ul>
75
+ <li><a href="https://github.com/ruby-concurrency/concurrent-ruby">concurrent-ruby</a></li>
76
+ <li><a href="https://github.com/sporkmonger/addressable">addressable</a></li>
77
+ <li><a href="https://github.com/typhoeus/typhoeus">typhoeus</a></li>
78
+ </ul>
79
+
80
+ <p>The HTTP adapters are mostly run in a background thread and may not survive mid-application forks.</p>
81
+
82
+ <p>Restify includes processors to parse responses and to extract links between resources. The following formats are can be parsed:</p>
83
+
84
+ <ul>
85
+ <li>JSON</li>
86
+ <li>MessagePack</li>
87
+ </ul>
88
+
89
+ <p>Links are extracted from</p>
90
+
91
+ <ul>
92
+ <li>HTTP Link header</li>
93
+ <li>Github-style relations in payloads</li>
94
+ </ul>
95
+
96
+ <h2 id="installation">Installation</h2>
97
+
98
+ <p>Add it to your Gemfile:</p>
99
+
100
+ <pre class="code ruby"><code class="ruby"><span class='id identifier rubyid_gem'>gem</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>restify</span><span class='tstring_end'>&#39;</span></span><span class='comma'>,</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>~&gt; 2.0</span><span class='tstring_end'>&#39;</span></span>
101
+ </code></pre>
102
+
103
+ <p>Or install it manually:</p>
104
+
105
+ <pre class="code console"><code class="console">gem install restify
106
+ </code></pre>
107
+
108
+ <h2 id="usage">Usage</h2>
109
+
110
+ <p>Create new Restify object. It essentially means to request some start-resource usually the &quot;root&quot; resource:</p>
111
+
112
+ <pre class="code ruby"><code class="ruby"><span class='id identifier rubyid_client'>client</span> <span class='op'>=</span> <span class='const'><span class='object_link'><a href="Restify.html" title="Restify (module)">Restify</a></span></span><span class='period'>.</span><span class='id identifier rubyid_new'><span class='object_link'><a href="Restify/Global.html#new-instance_method" title="Restify::Global#new (method)">new</a></span></span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>https://api.github.com</span><span class='tstring_end'>&#39;</span></span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_get'>get</span><span class='period'>.</span><span class='id identifier rubyid_value'>value</span>
113
+ <span class='comment'># =&gt; {&quot;current_user_url&quot;=&gt;&quot;https://api.github.com/user&quot;,
114
+ </span><span class='comment'># &quot;current_user_authorizations_html_url&quot;=&gt;&quot;https://github.com/settings/connections/applications{/client_id}&quot;,
115
+ </span><span class='comment'># ...
116
+ </span><span class='comment'># &quot;repository_url&quot;=&gt;&quot;https://api.github.com/repos/{owner}/{repo}&quot;,
117
+ </span><span class='comment'># ...
118
+ </span></code></pre>
119
+
120
+ <p>We are essentially requesting <code>&#39;http://api.github.com&#39;</code> via HTTP <code>get</code>. <code>get</code> is returning a <code>Promise</code>, similar to Java&#39;s <code>Future</code>. The <code>value</code> call resolves the returned <code>Promise</code> by blocking the thread until the resource is actually there. <code>value!</code> will additionally raise errors instead of returning <code>nil</code>. You can chain handlers using the <code>then</code> method. This allows you to be build a dependency chain that will be executed when the last promise is needed.</p>
121
+
122
+ <p>As we can see GitHub returns us a field <code>repository_url</code> with a URI template. Restify automatically scans for <code>*_url</code> fields in the JSON response and exposes these as relations. It additionally scans the HTTP Header field <code>Link</code> for relations like pagination.</p>
123
+
124
+ <p>We can now use the relations to navigate from resource to resource like a browser from one web page to another page.</p>
125
+
126
+ <pre class="code ruby"><code class="ruby"><span class='id identifier rubyid_repositories'>repositories</span> <span class='op'>=</span> <span class='id identifier rubyid_client'>client</span><span class='period'>.</span><span class='id identifier rubyid_rel'>rel</span><span class='lparen'>(</span><span class='symbol'>:repository</span><span class='rparen'>)</span>
127
+ <span class='comment'># =&gt; #&lt;Restify::Relation:0x00000005548968 @context=#&lt;Restify::Context:0x007f6024066ae0 @uri=#&lt;Addressable::URI:0x29d8684 URI:https://api.github.com&gt;&gt;, @template=#&lt;Addressable::Template:0x2aa44a0 PATTERN:https://api.github.com/repos/{owner}/{repo}&gt;&gt;
128
+ </span></code></pre>
129
+
130
+ <p>This gets us the relation named <code>repository</code> that we can request now. The usual HTTP methods are available on a relation:</p>
131
+
132
+ <pre class="code ruby"><code class="ruby">def get(params, params:, headers:, **)
133
+ def head(params, params:, headers:, **)
134
+ def delete(params, params:, headers:, **)
135
+
136
+ def put(data = nil, params:, headers:, **)
137
+ def post(data = nil, params:, headers:, **)
138
+ def patch(data = nil, params:, headers:, **)
139
+ </code></pre>
140
+
141
+ <p>URL templates can define some parameters such as <code>{owner}</code> or <code>{repo}</code>. They will be expanded from the <code>params</code> given to the HTTP method.</p>
142
+
143
+ <p>Now send a GET request with some parameters to request a specific repository:</p>
144
+
145
+ <pre class="code ruby"><code class="ruby"><span class='id identifier rubyid_repo'>repo</span> <span class='op'>=</span> <span class='id identifier rubyid_repositories'>repositories</span><span class='period'>.</span><span class='id identifier rubyid_get'>get</span><span class='lparen'>(</span><span class='lbrace'>{</span><span class='label'>owner:</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>jgraichen</span><span class='tstring_end'>&#39;</span></span><span class='comma'>,</span> <span class='label'>repo:</span> <span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>restify</span><span class='tstring_end'>&#39;</span></span><span class='rbrace'>}</span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_value'>value</span>
146
+ </code></pre>
147
+
148
+ <p>Now fetch a list of commits for this repo and get this first one:</p>
149
+
150
+ <pre class="code ruby"><code class="ruby"><span class='id identifier rubyid_commit'>commit</span> <span class='op'>=</span> <span class='id identifier rubyid_repo'>repo</span><span class='period'>.</span><span class='id identifier rubyid_rel'>rel</span><span class='lparen'>(</span><span class='symbol'>:commits</span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_get'>get</span><span class='period'>.</span><span class='id identifier rubyid_value'>value</span><span class='period'>.</span><span class='id identifier rubyid_first'>first</span>
151
+ </code></pre>
152
+
153
+ <p>And print it:</p>
154
+
155
+ <pre class="code ruby"><code class="ruby"><span class='id identifier rubyid_puts'>puts</span> <span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>Last commit: </span><span class='embexpr_beg'>#{</span><span class='id identifier rubyid_commit'>commit</span><span class='lbracket'>[</span><span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>sha</span><span class='tstring_end'>&#39;</span></span><span class='rbracket'>]</span><span class='embexpr_end'>}</span><span class='tstring_end'>&quot;</span></span>
156
+ <span class='id identifier rubyid_puts'>puts</span> <span class='tstring'><span class='tstring_beg'>&quot;</span><span class='tstring_content'>By </span><span class='embexpr_beg'>#{</span><span class='id identifier rubyid_commit'>commit</span><span class='lbracket'>[</span><span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>commit</span><span class='tstring_end'>&#39;</span></span><span class='rbracket'>]</span><span class='lbracket'>[</span><span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>author</span><span class='tstring_end'>&#39;</span></span><span class='rbracket'>]</span><span class='lbracket'>[</span><span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>name</span><span class='tstring_end'>&#39;</span></span><span class='rbracket'>]</span><span class='embexpr_end'>}</span><span class='tstring_content'> &lt;</span><span class='embexpr_beg'>#{</span><span class='id identifier rubyid_commit'>commit</span><span class='lbracket'>[</span><span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>commit</span><span class='tstring_end'>&#39;</span></span><span class='rbracket'>]</span><span class='lbracket'>[</span><span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>author</span><span class='tstring_end'>&#39;</span></span><span class='rbracket'>]</span><span class='lbracket'>[</span><span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>email</span><span class='tstring_end'>&#39;</span></span><span class='rbracket'>]</span><span class='embexpr_end'>}</span><span class='tstring_content'>&gt;</span><span class='tstring_end'>&quot;</span></span>
157
+ <span class='id identifier rubyid_puts'>puts</span> <span class='tstring'><span class='tstring_beg'>&quot;</span><span class='embexpr_beg'>#{</span><span class='id identifier rubyid_commit'>commit</span><span class='lbracket'>[</span><span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>commit</span><span class='tstring_end'>&#39;</span></span><span class='rbracket'>]</span><span class='lbracket'>[</span><span class='tstring'><span class='tstring_beg'>&#39;</span><span class='tstring_content'>message</span><span class='tstring_end'>&#39;</span></span><span class='rbracket'>]</span><span class='embexpr_end'>}</span><span class='tstring_end'>&quot;</span></span>
158
+ </code></pre>
159
+
160
+ <p>See commented example in main spec <a href="https://github.com/jgraichen/restify/blob/master/spec/restify_spec.rb#L100"><code>spec/restify_spec.rb</code></a> or in the <code>examples</code> directory.</p>
161
+
162
+ <h2 id="contributing">Contributing</h2>
163
+
164
+ <ol>
165
+ <li><a href="http://github.com/jgraichen/restify/fork">Fork it</a></li>
166
+ <li>Create your feature branch (<code>git checkout -b my-new-feature</code>)</li>
167
+ <li>Commit specs for your feature so that I do not break it later</li>
168
+ <li>Commit your changes (<code>git commit -am &#39;Add some feature&#39;</code>)</li>
169
+ <li>Push to the branch (<code>git push origin my-new-feature</code>)</li>
170
+ <li>Create new Pull Request</li>
171
+ </ol>
172
+
173
+ <h2 id="license">License</h2>
174
+
175
+ <p>Copyright (C) 2014-2025 Jan Graichen</p>
176
+
177
+ <p>This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</p>
178
+
179
+ <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.</p>
180
+
181
+ <p>You should have received a copy of the GNU Lesser General Public License along with this program. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.</p>
182
+ </div></div>
183
+
184
+ <div id="footer">
185
+ Generated on Sun Feb 16 14:11:59 2025 by
186
+ <a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
187
+ 0.9.37 (ruby-3.4.2).
188
+ </div>
189
+
190
+ </div>
191
+ </body>
192
+ </html>
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'restify/adapter/telemetry'
4
+
3
5
  module Restify
4
6
  module Adapter
5
7
  class Base
8
+ prepend Telemetry
9
+
6
10
  def call(request)
7
11
  Promise.create do |writer|
8
12
  call_native request, writer
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'opentelemetry'
4
+ require 'opentelemetry/common'
5
+
6
+ module Restify
7
+ module Adapter
8
+ module Telemetry
9
+ def call(request)
10
+ method = request.method.to_s.upcase
11
+ uri = URI.parse(request.uri)
12
+ name = "#{method} #{uri.scheme}://#{uri.host}:#{uri.port}"
13
+
14
+ attributes = {
15
+ 'http.request.method' => method,
16
+ 'server.address' => uri.host,
17
+ 'server.port' => uri.port,
18
+ 'url.full' => uri.to_s,
19
+ 'url.scheme' => uri.scheme,
20
+ }
21
+
22
+ span = tracer.start_span(name, attributes:, kind: :client)
23
+ OpenTelemetry::Trace.with_span(span) do
24
+ OpenTelemetry.propagation.inject(request.headers)
25
+
26
+ super.tap do |x|
27
+ x.add_observer do |_, response, err|
28
+ if response
29
+ span.set_attribute('http.response.status_code', response&.code)
30
+ span.status = OpenTelemetry::Trace::Status.error unless (100..399).cover?(response&.code)
31
+ end
32
+
33
+ span.status = OpenTelemetry::Trace::Status.error(err) if err
34
+
35
+ span.finish
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ protected
42
+
43
+ def tracer
44
+ Telemetry.tracer
45
+ end
46
+
47
+ class << self
48
+ def tracer
49
+ @tracer ||= OpenTelemetry.tracer_provider.tracer('restify', Restify::VERSION.to_s)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'typhoeus'
4
4
 
5
- ::Ethon.logger = ::Logging.logger[Ethon]
5
+ Ethon.logger = Logging.logger[Ethon]
6
6
 
7
7
  module Restify
8
8
  module Adapter
@@ -23,8 +23,28 @@ module Restify
23
23
  tcp_keepintvl: 5,
24
24
  }.freeze
25
25
 
26
+ # Patch Hydra to restore the correct OpenTelemetry span when
27
+ # adding the request, so that the Ethon instrumentation can
28
+ # properly pick up the context where the Restify request
29
+ # originated from.
30
+ #
31
+ # Handle exception from Ethon or WebMock too, and reject the
32
+ # promise, so that the errors can be handled in user code.
33
+ # Otherwise, the user would only receive a Promise timeout.
34
+ module EasyOverride
35
+ def add(request)
36
+ OpenTelemetry::Trace.with_span(request._otel_span) do
37
+ super(request)
38
+ rescue Exception => e # rubocop:disable Lint/RescueException
39
+ request._restify_writer.reject(e)
40
+ end
41
+ end
42
+ end
43
+
26
44
  def initialize(sync: false, options: {}, **kwargs)
27
- @hydra = ::Typhoeus::Hydra.new(**kwargs)
45
+ @hydra = ::Typhoeus::Hydra.new(**kwargs)
46
+ @hydra.extend(EasyOverride)
47
+
28
48
  @mutex = Mutex.new
29
49
  @options = DEFAULT_OPTIONS.merge(options)
30
50
  @queue = Queue.new
@@ -60,7 +80,7 @@ module Restify
60
80
  private
61
81
 
62
82
  def convert(request, writer)
63
- ::Typhoeus::Request.new(
83
+ Request.new(
64
84
  request.uri,
65
85
  **@options,
66
86
  method: request.method,
@@ -69,6 +89,9 @@ module Restify
69
89
  timeout: request.timeout,
70
90
  connecttimeout: request.timeout,
71
91
  ).tap do |req|
92
+ req._otel_span = OpenTelemetry::Trace.current_span
93
+ req._restify_writer = writer
94
+
72
95
  req.on_complete do |response|
73
96
  debug 'request:complete',
74
97
  tag: request.object_id,
@@ -102,7 +125,7 @@ module Restify
102
125
  def convert_headers(headers)
103
126
  return {} unless headers.respond_to?(:each_pair)
104
127
 
105
- headers.each_pair.each_with_object({}) do |header, memo|
128
+ headers.each_pair.with_object({}) do |header, memo|
106
129
  memo[header[0].upcase.tr('-', '_')] = header[1]
107
130
  end
108
131
  end
@@ -159,6 +182,16 @@ module Restify
159
182
  def _log_prefix
160
183
  "[#{object_id}/#{Thread.current.object_id}]"
161
184
  end
185
+
186
+ class Request < ::Typhoeus::Request
187
+ # Keep track of the OTEL span and the restify promise in the
188
+ # queued Typhoeus request.
189
+ #
190
+ # We need to access these to restore the tracing context, or
191
+ # bubble up exception that happen in the background thread after
192
+ # queuing, but when Hydra adds the requests to libcurl.
193
+ attr_accessor :_otel_span, :_restify_writer
194
+ end
162
195
  end
163
196
  end
164
197
  end
@@ -51,9 +51,9 @@ module Restify
51
51
  request = Request.new(
52
52
  headers: default_headers.merge(headers),
53
53
  **kwargs,
54
- method: method,
54
+ method:,
55
55
  uri: join(uri),
56
- data: data,
56
+ data:,
57
57
  )
58
58
 
59
59
  ret = cache.call(request) {|req| adapter.call(req) }
@@ -80,7 +80,7 @@ module Restify
80
80
  end
81
81
 
82
82
  def marshal_load(dump)
83
- initialize dump.delete(:uri), \
83
+ initialize dump.delete(:uri),
84
84
  headers: dump.fetch(:headers)
85
85
  end
86
86
 
data/lib/restify/error.rb CHANGED
@@ -55,8 +55,8 @@ module Restify
55
55
 
56
56
  def initialize(response)
57
57
  @response = response
58
- super "#{response.message} (#{response.code}) for `#{response.uri}':\n" \
59
- " #{errors.inspect}"
58
+ super("#{response.message} (#{response.code}) for `#{response.uri}':\n " \
59
+ "#{errors.inspect}")
60
60
  end
61
61
 
62
62
  # Return response status.
data/lib/restify/link.rb CHANGED
@@ -25,10 +25,10 @@ module Restify
25
25
  end
26
26
 
27
27
  class << self
28
- REGEXP_URI = /<[^>]*>\s*/.freeze
29
- REGEXP_PAR = /;\s*\w+\s*=\s*/i.freeze
30
- REGEXP_QUT = /"[^"]*"\s*/.freeze
31
- REGEXP_ARG = /\w+\s*/i.freeze
28
+ REGEXP_URI = /<[^>]*>\s*/
29
+ REGEXP_PAR = /;\s*\w+\s*=\s*/i
30
+ REGEXP_QUT = /"[^"]*"\s*/
31
+ REGEXP_ARG = /\w+\s*/i
32
32
 
33
33
  def parse(string)
34
34
  scanner = StringScanner.new(string.strip)
@@ -9,11 +9,6 @@ module Restify
9
9
  # Parses generic data structures into resources
10
10
  #
11
11
  module Parsing
12
- def self.included(base)
13
- base.extend ClassMethods
14
- base.indifferent_access = true
15
- end
16
-
17
12
  def load
18
13
  parse deserialized_body, root: true
19
14
  end
@@ -24,12 +19,10 @@ module Restify
24
19
  data = object.each_with_object({}) {|each, obj| parse_data(each, obj) }
25
20
  relations = object.each_with_object({}) {|each, obj| parse_rels(each, obj) }
26
21
 
27
- data = with_indifferent_access(data) if self.class.indifferent_access?
28
-
29
22
  Resource.new context,
30
- data: data,
23
+ data:,
31
24
  response: root ? response : nil,
32
- relations: relations
25
+ relations:
33
26
 
34
27
  when Array
35
28
  object.map {|each| parse(each) }
@@ -58,18 +51,6 @@ module Restify
58
51
 
59
52
  relations[name] = pair[1].to_s
60
53
  end
61
-
62
- def with_indifferent_access(data)
63
- Hashie::Mash.new data
64
- end
65
-
66
- module ClassMethods
67
- def indifferent_access?
68
- @indifferent_access
69
- end
70
-
71
- attr_writer :indifferent_access
72
- end
73
54
  end
74
55
  end
75
56
  end
@@ -16,7 +16,7 @@ module Restify
16
16
  @resource ||= begin
17
17
  resource = load
18
18
 
19
- resource = Resource.new context, response: response, data: resource unless resource.is_a? Restify::Resource
19
+ resource = Resource.new context, response:, data: resource unless resource.is_a? Restify::Resource
20
20
 
21
21
  resource._restify_response = response
22
22
  merge_relations! resource._restify_relations
@@ -25,8 +25,8 @@ module Restify
25
25
  self
26
26
  end
27
27
 
28
- def then(&block)
29
- Promise.new([self], &block)
28
+ def then(&)
29
+ Promise.new([self], &)
30
30
  end
31
31
 
32
32
  def execute(timeout = nil)
@@ -7,7 +7,7 @@ module Restify
7
7
  end
8
8
 
9
9
  def store(name, uri, **opts)
10
- @registry[name] = Context.new uri, **opts
10
+ @registry[name] = Context.new(uri, **opts)
11
11
  end
12
12
 
13
13
  def fetch(name)