@apollo-deploy/tesseract 1.8.0 → 2.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 (194) hide show
  1. package/README.md +218 -110
  2. package/dist/adapters/csharp/index.d.ts +5 -8
  3. package/dist/adapters/csharp/index.d.ts.map +1 -1
  4. package/dist/adapters/csharp/index.js +314 -74
  5. package/dist/adapters/csharp/index.js.map +1 -1
  6. package/dist/adapters/documentation.d.ts +17 -0
  7. package/dist/adapters/documentation.d.ts.map +1 -0
  8. package/dist/adapters/documentation.js +268 -0
  9. package/dist/adapters/documentation.js.map +1 -0
  10. package/dist/adapters/go/index.d.ts +5 -8
  11. package/dist/adapters/go/index.d.ts.map +1 -1
  12. package/dist/adapters/go/index.js +197 -101
  13. package/dist/adapters/go/index.js.map +1 -1
  14. package/dist/adapters/kotlin/index.d.ts +5 -9
  15. package/dist/adapters/kotlin/index.d.ts.map +1 -1
  16. package/dist/adapters/kotlin/index.js +280 -212
  17. package/dist/adapters/kotlin/index.js.map +1 -1
  18. package/dist/adapters/php/index.d.ts +5 -15
  19. package/dist/adapters/php/index.d.ts.map +1 -1
  20. package/dist/adapters/php/index.js +362 -90
  21. package/dist/adapters/php/index.js.map +1 -1
  22. package/dist/adapters/python/index.d.ts +5 -8
  23. package/dist/adapters/python/index.d.ts.map +1 -1
  24. package/dist/adapters/python/index.js +240 -109
  25. package/dist/adapters/python/index.js.map +1 -1
  26. package/dist/adapters/ruby/index.d.ts +3 -3
  27. package/dist/adapters/ruby/index.d.ts.map +1 -1
  28. package/dist/adapters/ruby/index.js +235 -46
  29. package/dist/adapters/ruby/index.js.map +1 -1
  30. package/dist/adapters/rust/index.d.ts +5 -9
  31. package/dist/adapters/rust/index.d.ts.map +1 -1
  32. package/dist/adapters/rust/index.js +237 -169
  33. package/dist/adapters/rust/index.js.map +1 -1
  34. package/dist/adapters/shared.d.ts +7 -0
  35. package/dist/adapters/shared.d.ts.map +1 -0
  36. package/dist/adapters/shared.js +68 -0
  37. package/dist/adapters/shared.js.map +1 -0
  38. package/dist/adapters/type-utils.d.ts +13 -0
  39. package/dist/adapters/type-utils.d.ts.map +1 -0
  40. package/dist/adapters/type-utils.js +105 -0
  41. package/dist/adapters/type-utils.js.map +1 -0
  42. package/dist/adapters/types.d.ts.map +1 -1
  43. package/dist/adapters/typescript/emitter/emitSchema.d.ts +2 -2
  44. package/dist/adapters/typescript/emitter/emitSchema.js +39 -27
  45. package/dist/adapters/typescript/emitter/emitSchema.js.map +1 -1
  46. package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts +2 -2
  47. package/dist/adapters/typescript/emitter/emitTypeScriptModels.js +15 -15
  48. package/dist/adapters/typescript/emitter/importCollector.d.ts +2 -4
  49. package/dist/adapters/typescript/emitter/importCollector.d.ts.map +1 -1
  50. package/dist/adapters/typescript/emitter/importCollector.js +40 -50
  51. package/dist/adapters/typescript/emitter/importCollector.js.map +1 -1
  52. package/dist/adapters/typescript/emitter/parsers.d.ts +1 -1
  53. package/dist/adapters/typescript/emitter/parsers.js +13 -13
  54. package/dist/adapters/typescript/emitter/typeHoister.d.ts +1 -1
  55. package/dist/adapters/typescript/emitter/typeHoister.d.ts.map +1 -1
  56. package/dist/adapters/typescript/emitter/typeHoister.js +12 -20
  57. package/dist/adapters/typescript/emitter/typeHoister.js.map +1 -1
  58. package/dist/adapters/typescript/index.d.ts +3 -3
  59. package/dist/adapters/typescript/index.d.ts.map +1 -1
  60. package/dist/adapters/typescript/index.js +148 -166
  61. package/dist/adapters/typescript/index.js.map +1 -1
  62. package/dist/adapters/typescript/shared.d.ts +2 -2
  63. package/dist/adapters/typescript/shared.js +12 -12
  64. package/dist/cli.js +50 -37
  65. package/dist/cli.js.map +1 -1
  66. package/dist/collector.d.ts +77 -3
  67. package/dist/collector.d.ts.map +1 -1
  68. package/dist/collector.js +198 -20
  69. package/dist/collector.js.map +1 -1
  70. package/dist/elysia.js +2 -2
  71. package/dist/elysia.js.map +1 -1
  72. package/dist/express.d.ts.map +1 -1
  73. package/dist/express.js.map +1 -1
  74. package/dist/fastify.d.ts +8 -9
  75. package/dist/fastify.d.ts.map +1 -1
  76. package/dist/fastify.js +105 -164
  77. package/dist/fastify.js.map +1 -1
  78. package/dist/helpers/handlebars.d.ts +7 -16
  79. package/dist/helpers/handlebars.d.ts.map +1 -1
  80. package/dist/helpers/handlebars.js +127 -177
  81. package/dist/helpers/handlebars.js.map +1 -1
  82. package/dist/hono.d.ts.map +1 -1
  83. package/dist/hono.js.map +1 -1
  84. package/dist/index.d.ts +14 -12
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +32 -30
  87. package/dist/index.js.map +1 -1
  88. package/dist/koa.d.ts.map +1 -1
  89. package/dist/koa.js.map +1 -1
  90. package/dist/nestjs.d.ts.map +1 -1
  91. package/dist/nestjs.js +41 -14
  92. package/dist/nestjs.js.map +1 -1
  93. package/dist/pipeline/enrich.d.ts +2 -13
  94. package/dist/pipeline/enrich.d.ts.map +1 -1
  95. package/dist/pipeline/enrich.js +157 -212
  96. package/dist/pipeline/enrich.js.map +1 -1
  97. package/dist/pipeline/intake.d.ts +3 -3
  98. package/dist/pipeline/intake.d.ts.map +1 -1
  99. package/dist/pipeline/intake.js +342 -238
  100. package/dist/pipeline/intake.js.map +1 -1
  101. package/dist/pipeline/write.d.ts +1 -14
  102. package/dist/pipeline/write.d.ts.map +1 -1
  103. package/dist/pipeline/write.js +73 -113
  104. package/dist/pipeline/write.js.map +1 -1
  105. package/dist/types/config.d.ts +14 -26
  106. package/dist/types/config.d.ts.map +1 -1
  107. package/dist/types/config.js +16 -12
  108. package/dist/types/config.js.map +1 -1
  109. package/dist/types/ir.d.ts +12 -8
  110. package/dist/types/ir.d.ts.map +1 -1
  111. package/dist/types/manifest.d.ts +10 -11
  112. package/dist/types/manifest.d.ts.map +1 -1
  113. package/dist/types/manifest.js +54 -14
  114. package/dist/types/manifest.js.map +1 -1
  115. package/dist/types/sdk-module.d.ts +2 -2
  116. package/dist/types/sdk-module.js +1 -1
  117. package/dist/utils/format.d.ts +1 -3
  118. package/dist/utils/format.d.ts.map +1 -1
  119. package/dist/utils/format.js +3 -10
  120. package/dist/utils/format.js.map +1 -1
  121. package/dist/utils/naming.d.ts.map +1 -1
  122. package/dist/utils/naming.js +174 -184
  123. package/dist/utils/naming.js.map +1 -1
  124. package/dist/utils/npm-token.d.ts +1 -17
  125. package/dist/utils/npm-token.d.ts.map +1 -1
  126. package/dist/utils/npm-token.js +27 -76
  127. package/dist/utils/npm-token.js.map +1 -1
  128. package/dist/utils/version.d.ts +14 -0
  129. package/dist/utils/version.d.ts.map +1 -0
  130. package/dist/utils/version.js +60 -0
  131. package/dist/utils/version.js.map +1 -0
  132. package/package.json +4 -3
  133. package/templates/csharp/client.hbs +26 -49
  134. package/templates/csharp/csproj.hbs +7 -0
  135. package/templates/csharp/domain.hbs +48 -130
  136. package/templates/csharp/errors.hbs +17 -25
  137. package/templates/csharp/readme.hbs +31 -44
  138. package/templates/csharp/transport.hbs +238 -136
  139. package/templates/csharp/types.hbs +30 -76
  140. package/templates/go/client.hbs +44 -109
  141. package/templates/go/domain.hbs +83 -25
  142. package/templates/go/go-mod.hbs +1 -4
  143. package/templates/go/readme.hbs +32 -29
  144. package/templates/go/transport.hbs +174 -173
  145. package/templates/go/types.hbs +17 -53
  146. package/templates/go/utils.hbs +3 -2
  147. package/templates/kotlin/build-gradle.hbs +1 -1
  148. package/templates/kotlin/client.hbs +18 -15
  149. package/templates/kotlin/domain.hbs +54 -51
  150. package/templates/kotlin/errors.hbs +1 -1
  151. package/templates/kotlin/readme.hbs +35 -137
  152. package/templates/kotlin/transport.hbs +140 -123
  153. package/templates/kotlin/types.hbs +30 -81
  154. package/templates/php/client.hbs +8 -46
  155. package/templates/php/composer.hbs +2 -1
  156. package/templates/php/domain.hbs +86 -117
  157. package/templates/php/errors.hbs +1 -1
  158. package/templates/php/readme.hbs +42 -49
  159. package/templates/php/transport.hbs +192 -145
  160. package/templates/php/types.hbs +63 -60
  161. package/templates/python/client.hbs +15 -2
  162. package/templates/python/domain.hbs +50 -108
  163. package/templates/python/pyproject.hbs +5 -0
  164. package/templates/python/readme.hbs +47 -30
  165. package/templates/python/resources-init.hbs +1 -1
  166. package/templates/python/transport.hbs +167 -53
  167. package/templates/python/types.hbs +22 -58
  168. package/templates/ruby/client.hbs +10 -6
  169. package/templates/ruby/domain.hbs +48 -67
  170. package/templates/ruby/errors.hbs +3 -3
  171. package/templates/ruby/gemspec.hbs +3 -10
  172. package/templates/ruby/index.hbs +3 -3
  173. package/templates/ruby/readme.hbs +33 -70
  174. package/templates/ruby/transport.hbs +129 -103
  175. package/templates/ruby/types.hbs +11 -16
  176. package/templates/ruby/version.hbs +1 -1
  177. package/templates/rust/cargo.hbs +2 -0
  178. package/templates/rust/client.hbs +10 -1
  179. package/templates/rust/domain.hbs +69 -52
  180. package/templates/rust/error.hbs +3 -1
  181. package/templates/rust/lib.hbs +4 -5
  182. package/templates/rust/readme.hbs +33 -46
  183. package/templates/rust/transport.hbs +170 -159
  184. package/templates/rust/types.hbs +34 -111
  185. package/templates/typescript/domain-class.hbs +14 -6
  186. package/templates/typescript/domain.hbs +13 -5
  187. package/templates/typescript/package-json.hbs +3 -1
  188. package/templates/typescript/readme.hbs +42 -42
  189. package/templates/typescript/transport.hbs +46 -8
  190. package/templates/typescript/types-errors.hbs +1 -1
  191. package/dist/adapters/typescript/model-emitter.d.ts +0 -13
  192. package/dist/adapters/typescript/model-emitter.d.ts.map +0 -1
  193. package/dist/adapters/typescript/model-emitter.js +0 -415
  194. package/dist/adapters/typescript/model-emitter.js.map +0 -1
@@ -2,89 +2,91 @@
2
2
 
3
3
  require "faraday"
4
4
  require "json"
5
+ require "time"
6
+ require "uri"
5
7
  require_relative "errors"
6
8
 
7
- module {{pascalCase meta.clientName}}
9
+ module {{moduleName}}
8
10
  # HTTP transport layer built on Faraday.
9
11
  class Transport
12
+ RETRYABLE_METHODS = %i[get head options put delete].freeze
13
+ RETRYABLE_STATUSES = [408, 425, 429, 500, 502, 503, 504].freeze
14
+
10
15
  attr_reader :config
11
16
  attr_reader :connection
12
17
 
13
18
  def initialize(config)
14
19
  @config = config
15
- @connection = Faraday.new(url: config.base_url) do |f|
16
- f.request :json
17
- f.response :raise_error
18
- f.options.timeout = config.timeout
19
- f.options.open_timeout = [config.timeout / 3, 2].max
20
- config.default_headers.each do |key, value|
21
- f.headers[key] = value
22
- end
23
- f.adapter Faraday.default_adapter
20
+ @connection = Faraday.new(url: config.base_url) do |faraday|
21
+ faraday.request :json
22
+ faraday.response :raise_error
23
+ faraday.options.timeout = config.timeout
24
+ faraday.options.open_timeout = [config.timeout / 3.0, 2.0].max
25
+ faraday.headers["Accept"] = "application/json"
26
+ faraday.headers["User-Agent"] = "{{gemName}}-ruby-sdk/{{meta.packageVersion}}"
27
+ config.default_headers.each { |key, value| faraday.headers[key] = value }
28
+ faraday.adapter Faraday.default_adapter
24
29
  end
25
30
  end
26
31
 
27
32
  def request(method:, path:, path_params: {}, query: nil, body: nil, headers: {}, content_type: nil)
28
33
  url = build_url(path, path_params)
29
- attempts = 0
30
- max_attempts = [(@config.retries[:attempts] || 3), 1].max
31
- backoff = @config.retries[:backoff] || 0.4
32
- jitter = @config.retries.fetch(:jitter, true)
33
- max_backoff = @config.retries[:max_backoff] || 30.0
34
+ body = body.to_h if body.respond_to?(:to_h)
35
+ method = method.to_sym
36
+ max_retries = [(@config.retries[:attempts] || 3).to_i, 0].max
37
+ attempt = 0
38
+
34
39
  begin
35
- attempts += 1
36
40
  response = @connection.run_request(
37
- method.to_sym, url, body, build_headers(headers, content_type)
38
- ) do |req|
39
- req.params.merge!(query) if query && !query.empty?
41
+ method, url, body, build_headers(headers, content_type)
42
+ ) do |request|
43
+ request.params.merge!(query) if query && !query.empty?
40
44
  end
41
45
  parse_response(response)
42
- rescue Faraday::Error => e
43
- raise map_error(e, method: method, path: path) unless should_retry?(e, attempts, max_attempts)
44
- sleep(retry_delay(backoff, attempts, jitter, max_backoff))
46
+ rescue Faraday::Error => error
47
+ mapped = map_error(error, method: method, path: path)
48
+ unless retryable_request?(method, headers) && mapped.retryable? && attempt < max_retries
49
+ raise mapped
50
+ end
51
+
52
+ sleep(retry_delay(error, attempt))
53
+ attempt += 1
45
54
  retry
46
55
  end
47
56
  end
48
57
 
49
- def get(path:, path_params: {}, query: nil, headers: {})
50
- request(method: :get, path: path, path_params: path_params, query: query, headers: headers)
51
- end
52
-
53
- def post(path:, body: nil, path_params: {}, query: nil, headers: {}, content_type: nil)
54
- request(method: :post, path: path, body: body, path_params: path_params,
55
- query: query, headers: headers, content_type: content_type)
56
- end
57
-
58
- def put(path:, body: nil, path_params: {}, query: nil, headers: {}, content_type: nil)
59
- request(method: :put, path: path, body: body, path_params: path_params,
60
- query: query, headers: headers, content_type: content_type)
61
- end
62
-
63
- def patch(path:, body: nil, path_params: {}, query: nil, headers: {}, content_type: nil)
64
- request(method: :patch, path: path, body: body, path_params: path_params,
65
- query: query, headers: headers, content_type: content_type)
66
- end
67
-
68
- def delete(path:, path_params: {}, query: nil, headers: {})
69
- request(method: :delete, path: path, path_params: path_params, query: query, headers: headers)
58
+ def stream(method:, path:, path_params: {}, query: nil, body: nil, headers: {}, content_type: nil)
59
+ Enumerator.new do |output|
60
+ url = build_url(path, path_params)
61
+ @connection.run_request(
62
+ method.to_sym,
63
+ url,
64
+ body&.respond_to?(:to_h) ? body.to_h : body,
65
+ build_headers(headers, content_type)
66
+ ) do |request|
67
+ request.params.merge!(query) if query && !query.empty?
68
+ request.options.on_data = proc { |chunk, _bytes| output << chunk }
69
+ end
70
+ rescue Faraday::Error => error
71
+ raise map_error(error, method: method, path: path)
72
+ end
70
73
  end
71
74
 
72
75
  private
73
76
 
74
77
  def build_url(path, path_params)
75
78
  return path if path_params.empty?
76
- path.gsub(/\{(\w+)\}/) do |_match|
77
- key = ::Regexp.last_match(1)
79
+
80
+ path.gsub(/(?:\$\{(\w+)\}|:(\w+))/) do |match|
81
+ key = Regexp.last_match(1) || Regexp.last_match(2)
78
82
  value = path_params[key.to_sym] || path_params[key]
79
- value.nil? ? _match : value.to_s
83
+ value.nil? ? match : URI.encode_www_form_component(value.to_s)
80
84
  end
81
85
  end
82
86
 
83
87
  def build_headers(extra_headers = {}, content_type = nil)
84
88
  headers = {}
85
- if content_type
86
- headers["Content-Type"] = content_type
87
- end
89
+ headers["Content-Type"] = content_type if content_type
88
90
  {{#each securitySchemes}}
89
91
  {{#if (eq type "apiKey")}}
90
92
  {{#if (eq in "header")}}
@@ -92,13 +94,13 @@ module {{pascalCase meta.clientName}}
92
94
  {{/if}}
93
95
  {{/if}}
94
96
  {{#if (and (eq type "http") (eq scheme "bearer"))}}
95
- headers["Authorization"] = "Bearer " + (@config.{{camelCase name}} || "") if @config.{{camelCase name}}
97
+ headers["Authorization"] = "Bearer " + @config.{{camelCase name}} if @config.{{camelCase name}}
96
98
  {{/if}}
97
99
  {{#if (eq type "oauth2")}}
98
- headers["Authorization"] = "Bearer " + (@config.{{camelCase name}} || "") if @config.{{camelCase name}}
100
+ headers["Authorization"] = "Bearer " + @config.{{camelCase name}} if @config.{{camelCase name}}
99
101
  {{/if}}
100
102
  {{#if (eq type "openIdConnect")}}
101
- headers["Authorization"] = "Bearer " + (@config.{{camelCase name}} || "") if @config.{{camelCase name}}
103
+ headers["Authorization"] = "Bearer " + @config.{{camelCase name}} if @config.{{camelCase name}}
102
104
  {{/if}}
103
105
  {{/each}}
104
106
  headers.merge!(extra_headers)
@@ -108,70 +110,94 @@ module {{pascalCase meta.clientName}}
108
110
  def parse_response(response)
109
111
  body = response.body
110
112
  return nil if body.nil? || (body.is_a?(String) && body.strip.empty?)
111
- if body.is_a?(String)
112
- begin
113
- JSON.parse(body)
114
- rescue JSON::ParserError
115
- body
116
- end
117
- else
118
- body
113
+ return body unless body.is_a?(String)
114
+
115
+ JSON.parse(body)
116
+ rescue JSON::ParserError
117
+ body
118
+ end
119
+
120
+ def retryable_request?(method, headers)
121
+ return true if @config.retry_unsafe_requests
122
+ return true if RETRYABLE_METHODS.include?(method)
123
+
124
+ @config.default_headers.merge(headers).any? do |key, value|
125
+ key.to_s.casecmp("X-Idempotency-Key").zero? && !value.to_s.empty?
126
+ end
127
+ end
128
+
129
+ def retry_delay(error, attempt)
130
+ retry_after = retry_after_seconds(error)
131
+ return retry_after unless retry_after.nil?
132
+
133
+ base = (@config.retries[:backoff] || 0.4).to_f
134
+ max_backoff = (@config.retries[:max_backoff] || 30.0).to_f
135
+ delay = [base * (2**attempt), max_backoff].min
136
+ @config.retries.fetch(:jitter, true) ? rand * delay : delay
137
+ end
138
+
139
+ def retry_after_seconds(error)
140
+ headers = error.response&.[](:headers) || {}
141
+ value = headers["retry-after"] || headers["Retry-After"]
142
+ return nil if value.nil? || value.to_s.empty?
143
+
144
+ [Float(value), (@config.retries[:max_backoff] || 30.0).to_f].min
145
+ rescue ArgumentError, TypeError
146
+ begin
147
+ [
148
+ [Time.httpdate(value.to_s) - Time.now, 0.0].max,
149
+ (@config.retries[:max_backoff] || 30.0).to_f
150
+ ].min
151
+ rescue ArgumentError
152
+ nil
119
153
  end
120
154
  end
121
155
 
122
156
  def map_error(error, method:, path:)
157
+ status = error.response&.[](:status).to_i
158
+ headers = error.response&.[](:headers) || {}
159
+ request_id = headers["x-request-id"] || headers["X-Request-ID"] || headers["request-id"]
160
+
123
161
  case error
124
162
  when Faraday::TimeoutError
125
- SDKError.new("Request timed out", 0, "network_error", method: method, path: path)
163
+ SDKError.new("Request timed out", 0, "timeout", method: method, path: path)
126
164
  when Faraday::ConnectionFailed
127
- SDKError.new("Connection failed: " + error.message, 0, "network_error", method: method, path: path)
128
- when Faraday::ResourceNotFound
129
- SDKError.new("Not found: " + error.message, 404, "not_found", method: method, path: path)
130
- when Faraday::ClientError
131
- status = error.response&.[](:status) || 400
132
- begin
133
- body = error.response&.[](:body)
134
- envelope = body.is_a?(String) ? JSON.parse(body) : body
135
- if envelope.is_a?(Hash)
136
- SDKError.new(
137
- envelope["message"] || envelope["error"] || "Request failed",
138
- status,
139
- envelope["error"] || envelope["code"] || "unknown_error",
140
- request_id: envelope["request_id"],
141
- method: method, path: path
142
- )
143
- else
144
- SDKError.new("Request failed with status " + status.to_s, status, "unknown_error", method: method, path: path)
145
- end
146
- rescue
147
- SDKError.new("Request failed with status " + status.to_s, status, "unknown_error", method: method, path: path)
148
- end
149
- when Faraday::ServerError
150
- status = error.response&.[](:status) || 500
151
- SDKError.new("Server error: " + error.message, status, "internal_server_error", method: method, path: path)
165
+ SDKError.new("Connection failed: #{error.message}", 0, "network_error", method: method, path: path)
152
166
  else
153
- SDKError.new(error.message, 0, "unknown_error", method: method, path: path)
167
+ body = error.response&.[](:body)
168
+ envelope = parse_error_body(body)
169
+ message = envelope["message"] || envelope.dig("error", "message") || error.message
170
+ code = envelope["code"] || envelope.dig("error", "code")
171
+ code ||= envelope["error"] if envelope["error"].is_a?(String)
172
+ SDKError.new(
173
+ message,
174
+ status,
175
+ code || default_error_code(status),
176
+ request_id: envelope["request_id"] || request_id,
177
+ method: method,
178
+ path: path,
179
+ details: envelope["details"]
180
+ )
154
181
  end
155
182
  end
156
183
 
157
- def should_retry?(_error, attempts, max_attempts)
158
- return false if attempts >= max_attempts
159
- case _error
160
- when Faraday::TimeoutError, Faraday::ConnectionFailed, Faraday::ServerError
161
- true
162
- when Faraday::ClientError
163
- status = _error.response&.[](:status)
164
- status == 429
165
- else
166
- false
167
- end
184
+ def parse_error_body(body)
185
+ return body if body.is_a?(Hash)
186
+ return {} unless body.is_a?(String) && !body.empty?
187
+
188
+ parsed = JSON.parse(body)
189
+ parsed.is_a?(Hash) ? parsed : {}
190
+ rescue JSON::ParserError
191
+ {}
168
192
  end
169
193
 
170
- def retry_delay(base, attempt, jitter, max_backoff)
171
- delay = base * (2**(attempt - 1))
172
- delay = max_backoff if delay > max_backoff
173
- delay *= (0.5 + rand * 0.5) if jitter
174
- delay
194
+ def default_error_code(status)
195
+ return "network_error" if status.zero?
196
+ return "rate_limit_exceeded" if status == 429
197
+ return "request_timeout" if status == 408
198
+ return "internal_server_error" if status >= 500
199
+
200
+ "http_error"
175
201
  end
176
202
  end
177
203
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module {{pascalCase meta.clientName}}
3
+ module {{moduleName}}
4
4
  {{#each schemas}}
5
5
  {{#if isEnum}}
6
6
  # {{#if description}}{{description}}{{else}}Enum for {{name}} values.{{/if}}
@@ -34,13 +34,8 @@ module {{pascalCase meta.clientName}}
34
34
  end
35
35
 
36
36
  {{else if isTypeAlias}}
37
- {{#if unionMembers}}
38
- # {{pascalCase name}} type alias for: {{#each unionMembers}}{{this}}{{#unless @last}} | {{/unless}}{{/each}}
39
- {{pascalCase name}} = Class.new
40
- {{else}}
41
- # {{pascalCase name}} — schema type alias
42
- {{pascalCase name}} = Class.new
43
- {{/if}}
37
+ # {{pascalCase name}} — type alias for {{aliasDocType}}
38
+ {{pascalCase name}} = {{aliasTarget}}
44
39
 
45
40
  {{else if isUnionType}}
46
41
  # {{pascalCase name}} — union type: {{#each unionMembers}}{{this}}{{#unless @last}} | {{/unless}}{{/each}}
@@ -56,14 +51,14 @@ module {{pascalCase meta.clientName}}
56
51
  # {{#if description}}{{description}}{{else}}Schema type: {{name}}{{/if}}
57
52
  class {{pascalCase name}}
58
53
  {{#each properties}}
59
- # @return [{{type}}{{#if nullable}}, nil{{/if}}]{{#if description}} {{description}}{{/if}}
60
- attr_accessor :{{snakeCase name}}
54
+ # @return [{{docType}}]{{#if description}} {{description}}{{/if}}
55
+ attr_accessor :{{propertyName}}
61
56
  {{/each}}
62
57
 
63
- # @param {{#each properties}}{{snakeCase name}} [{{type}}{{#if nullable}}, nil{{/if}}]{{#unless @last}}, {{/unless}}{{/each}}
64
- def initialize({{#each properties}}{{snakeCase name}}: {{^required}}nil{{/required}}{{#unless @last}}, {{/unless}}{{/each}})
58
+ # @param {{#each properties}}{{propertyName}} [{{docType}}]{{#unless @last}}, {{/unless}}{{/each}}
59
+ def initialize({{#each properties}}{{propertyName}}: {{^required}}nil{{/required}}{{#unless @last}}, {{/unless}}{{/each}})
65
60
  {{#each properties}}
66
- @{{snakeCase name}} = {{snakeCase name}}
61
+ @{{propertyName}} = {{propertyName}}
67
62
  {{/each}}
68
63
  end
69
64
 
@@ -72,9 +67,9 @@ module {{pascalCase meta.clientName}}
72
67
  {
73
68
  {{#each properties}}
74
69
  {{#if (eq type "DateTime")}}
75
- {{snakeCase name}}: @{{snakeCase name}}&.iso8601,
70
+ {{propertyName}}: @{{propertyName}}&.iso8601,
76
71
  {{else}}
77
- {{snakeCase name}}: @{{snakeCase name}},
72
+ {{propertyName}}: @{{propertyName}},
78
73
  {{/if}}
79
74
  {{/each}}
80
75
  }.compact
@@ -88,7 +83,7 @@ module {{pascalCase meta.clientName}}
88
83
 
89
84
  new(
90
85
  {{#each properties}}
91
- {{snakeCase name}}: attributes["{{snakeCase name}}"] || attributes["{{originalName}}"] || attributes[:{{snakeCase name}}],
86
+ {{propertyName}}: attributes.key?("{{name}}") ? attributes["{{name}}"] : attributes[:{{propertyName}}],
92
87
  {{/each}}
93
88
  )
94
89
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module {{pascalCase snakeModule}}
3
+ module {{moduleName}}
4
4
  VERSION = "{{#if meta.packageVersion}}{{meta.packageVersion}}{{else}}{{meta.version}}{{/if}}"
5
5
  end
@@ -13,6 +13,8 @@ reqwest = { version = "0.12", default-features = false, features = ["json", "rus
13
13
  tokio = { version = "1", features = ["full"] }
14
14
  thiserror = "2"
15
15
  url = "2"
16
+ urlencoding = "2"
17
+ httpdate = "1"
16
18
  {{#if hasSSE}}
17
19
  futures-core = "0.3"
18
20
  tokio-stream = "0.1"
@@ -2,6 +2,7 @@ use crate::transport::{Transport, TransportConfig};
2
2
  {{#each groups}}
3
3
  use crate::api::{{snakeCase fileName}}::{{interfaceName}};
4
4
  {{/each}}
5
+ use std::collections::HashMap;
5
6
  use std::time::Duration;
6
7
 
7
8
  /// Configuration for the {{title}} API client.
@@ -31,6 +32,10 @@ pub struct ClientConfig {
31
32
  pub timeout: Duration,
32
33
  /// Maximum number of retry attempts.
33
34
  pub max_retries: u32,
35
+ /// Allow automatic retries for unsafe methods without an idempotency key.
36
+ pub retry_unsafe_requests: bool,
37
+ /// Headers sent with every request.
38
+ pub default_headers: HashMap<String, String>,
34
39
  }
35
40
 
36
41
  impl Default for ClientConfig {
@@ -42,6 +47,8 @@ impl Default for ClientConfig {
42
47
  {{/each}}
43
48
  timeout: Duration::from_secs(15),
44
49
  max_retries: 3,
50
+ retry_unsafe_requests: false,
51
+ default_headers: HashMap::new(),
45
52
  }
46
53
  }
47
54
  }
@@ -77,13 +84,15 @@ impl Client {
77
84
  /// let client = Client::new(config);
78
85
  /// ```
79
86
  pub fn new(config: ClientConfig) -> Self {
80
- let mut transport_config = TransportConfig {
87
+ let transport_config = TransportConfig {
81
88
  base_url: config.base_url,
82
89
  {{#each securitySchemes}}
83
90
  {{snakeCase configKey}}: config.{{snakeCase configKey}},
84
91
  {{/each}}
85
92
  timeout: config.timeout,
86
93
  max_retries: config.max_retries,
94
+ retry_unsafe_requests: config.retry_unsafe_requests,
95
+ default_headers: config.default_headers,
87
96
  };
88
97
 
89
98
  let transport = Transport::new(transport_config);
@@ -1,6 +1,7 @@
1
- use crate::transport::Transport;
2
1
  use crate::error::SdkError;
2
+ use crate::transport::Transport;
3
3
  use reqwest::Method;
4
+ use serde::Serialize;
4
5
  {{#if imports.length}}
5
6
  use crate::types::{ {{join imports ", "}} };
6
7
  {{/if}}
@@ -16,79 +17,95 @@ impl {{interfaceName}} {
16
17
  }
17
18
 
18
19
  {{#each operations}}
19
- {{#if summary}}
20
+ {{#if summary}}
20
21
  /// {{summary}}{{#if description}}
21
22
  ///
22
23
  /// {{description}}{{/if}}
23
- {{/if}}
24
- {{#if deprecated}}
24
+ {{/if}}
25
+ {{#if deprecated}}
25
26
  #[deprecated{{#if deprecationMessage}}(note = "{{deprecationMessage}}"){{/if}}]
26
- {{/if}}
27
- pub async fn {{snakeCase name}}(
28
- &self,
27
+ {{/if}}
28
+ pub async fn {{methodName}}(
29
+ &self{{#if hasParameters}},
30
+ {{{signature}}}{{/if}},
31
+ ) -> Result<{{targetResponseType}}, SdkError> {
32
+ let mut path = "{{path}}".to_string();
29
33
  {{#each pathParams}}
30
- {{snakeCase name}}: {{{rustParamType type}}},
34
+ path = path.replace("{{placeholder}}", &encode_path(&{{parameterName}}.to_string()));
31
35
  {{/each}}
32
- {{#if requestBody}}
33
- body: &{{{requestBody.rustType}}},
34
- {{/if}}
35
- {{#if queryType}}
36
- query: Option<&{{{rustQueryType queryType}}}>,
37
- {{/if}}
38
- ) -> Result<{{#if isEventStream}}Vec<{{#if eventSchema}}{{{rustType eventSchema}}}{{else}}serde_json::Value{{/if}}>{{else}}{{{rustResponseType}}}{{/if}}, SdkError> {
39
- let path = format!("{{path}}"{{#each pathParams}}, {{snakeCase name}} = {{#if (isRustCopyType type)}}{{snakeCase name}}{{else}}{{snakeCase name}}{{/if}}{{/each}});
40
- {{#if (hasQueryParams this)}}
36
+
41
37
  let mut query_params: Vec<(String, String)> = Vec::new();
42
- if let Some(q) = query {
43
- {{#each _queryParams}}
44
- if let Some(ref v) = q.{{snakeCase name}} {
45
- query_params.push(("{{originalName}}".to_string(), v.to_string()));
46
- }
47
- {{/each}}
48
- }
49
- let query_slice: Option<&[(String, String)]> = if query_params.is_empty() { None } else { Some(&query_params) };
50
- {{else if queryType}}
51
- // Query params from typed query object
52
- let query_slice: Option<&[(String, String)]> = None; // TODO: serialize query type
38
+ {{#each queryParams}}
39
+ {{#if required}}
40
+ query_params.push(("{{originalName}}".to_string(), encode_value(&{{parameterName}})?));
53
41
  {{else}}
54
- let query_slice: Option<&[(String, String)]> = None;
42
+ if let Some(value) = {{parameterName}}.as_ref() {
43
+ query_params.push(("{{originalName}}".to_string(), encode_value(value)?));
44
+ }
55
45
  {{/if}}
46
+ {{/each}}
47
+ let query = (!query_params.is_empty()).then_some(query_params.as_slice());
56
48
 
57
- {{#if (or (hasRequestBody this) (needsRequestHeaders this))}}
58
49
  let mut headers: Vec<(String, String)> = Vec::new();
59
- {{#if (hasRequestBody this)}}
60
- {{#if (and (neq requestBody.contentType "application/json") (neq requestBody.contentType "multipart/form-data"))}}
61
- headers.push(("Content-Type".to_string(), "{{requestBody.contentType}}".to_string()));
62
- {{/if}}
63
- {{/if}}
64
- {{#if (and headerParams.length headerType)}}
65
- // Header params injected from header options
66
- {{/if}}
67
50
  {{#each headerParams}}
68
- if let Some(ref h) = header_options.{{snakeCase name}} {
69
- headers.push(("{{originalName}}".to_string(), h.to_string()));
51
+ {{#if required}}
52
+ headers.push(("{{originalName}}".to_string(), encode_value(&{{parameterName}})?));
53
+ {{else}}
54
+ if let Some(value) = {{parameterName}}.as_ref() {
55
+ headers.push(("{{originalName}}".to_string(), encode_value(value)?));
70
56
  }
71
- {{/each}}
72
- {{#if cookieParams}}
73
- // Cookie params
74
57
  {{/if}}
75
- let headers_slice: Option<&[(String, String)]> = if headers.is_empty() { None } else { Some(&headers) };
58
+ {{/each}}
59
+ {{#each cookieParams}}
60
+ {{#if required}}
61
+ headers.push(("Cookie".to_string(), format!("{{originalName}}={}", encode_value(&{{parameterName}})?)));
76
62
  {{else}}
77
- let headers_slice: Option<&[(String, String)]> = None;
63
+ if let Some(value) = {{parameterName}}.as_ref() {
64
+ headers.push(("Cookie".to_string(), format!("{{originalName}}={}", encode_value(value)?)));
65
+ }
78
66
  {{/if}}
67
+ {{/each}}
68
+ let headers = (!headers.is_empty()).then_some(headers.as_slice());
79
69
 
80
- self.transport.execute::<{{#if isEventStream}}Vec<{{#if eventSchema}}{{{rustType eventSchema}}}{{else}}serde_json::Value{{/if}}>{{else}}{{{rustResponseType}}}{{/if}}>(
70
+ {{#if isEventStream}}
71
+ self.transport.execute_raw(
72
+ Method::{{httpMethod}},
73
+ &path,
74
+ query,
75
+ {{{bodyArgument}}},
76
+ headers,
77
+ ).await
78
+ {{else if isVoid}}
79
+ self.transport.execute_raw(
81
80
  Method::{{httpMethod}},
82
81
  &path,
83
- query_slice,
84
- {{#if requestBody}}
85
- Some(body),
82
+ query,
83
+ {{{bodyArgument}}},
84
+ headers,
85
+ ).await.map(|_| ())
86
86
  {{else}}
87
- None::<&serde_json::Value>,
88
- {{/if}}
89
- headers_slice,
87
+ self.transport.execute::<{{targetResponseType}}>(
88
+ Method::{{httpMethod}},
89
+ &path,
90
+ query,
91
+ {{{bodyArgument}}},
92
+ headers,
90
93
  ).await
94
+ {{/if}}
91
95
  }
92
96
 
93
97
  {{/each}}
94
98
  }
99
+
100
+ fn encode_path(value: &str) -> String {
101
+ urlencoding::encode(value).into_owned()
102
+ }
103
+
104
+ fn encode_value<T: Serialize>(value: &T) -> Result<String, SdkError> {
105
+ match serde_json::to_value(value)
106
+ .map_err(|error| SdkError::ValidationError(error.to_string()))?
107
+ {
108
+ serde_json::Value::String(value) => Ok(value),
109
+ value => Ok(value.to_string()),
110
+ }
111
+ }
@@ -14,6 +14,8 @@ pub enum SdkError {
14
14
  code: Option<String>,
15
15
  /// Human-readable error message.
16
16
  message: String,
17
+ /// Server-provided request identifier.
18
+ request_id: Option<String>,
17
19
  /// Raw response body for debugging.
18
20
  body: String,
19
21
  },
@@ -56,7 +58,7 @@ impl SdkError {
56
58
  self,
57
59
  Self::NetworkError(_)
58
60
  | Self::ApiError {
59
- status: 429 | 502 | 503 | 504,
61
+ status: 408 | 425 | 429 | 500 | 502 | 503 | 504,
60
62
  ..
61
63
  }
62
64
  )
@@ -59,12 +59,11 @@
59
59
  //! - **DeserializationError** — response body could not be parsed
60
60
  //! - **ValidationError** — invalid request parameters
61
61
  //!
62
- //! ```rust
63
- //! match client.some_domain.some_method().await {
62
+ //! ```rust,ignore
63
+ //! let result = client.resource.some_method().await;
64
+ //! match result {
64
65
  //! Ok(response) => println!("Success: {:?}", response),
65
- //! Err(SdkError::ApiError { status, .. }) => eprintln!("API error ({}): {}", status, error),
66
- //! Err(SdkError::NetworkError(e)) => eprintln!("Network error: {}", e),
67
- //! Err(e) => eprintln!("Other error: {}", e),
66
+ //! Err(error) => eprintln!("SDK error: {}", error),
68
67
  //! }
69
68
  //! ```
70
69