restify 1.15.2 → 2.0.1

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 (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)