@apollo-deploy/tesseract 0.9.1 → 1.2.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 (114) hide show
  1. package/dist/adapters/csharp/index.d.ts +12 -0
  2. package/dist/adapters/csharp/index.d.ts.map +1 -0
  3. package/dist/adapters/csharp/index.js +99 -0
  4. package/dist/adapters/csharp/index.js.map +1 -0
  5. package/dist/adapters/go/index.d.ts +12 -0
  6. package/dist/adapters/go/index.d.ts.map +1 -0
  7. package/dist/adapters/go/index.js +135 -0
  8. package/dist/adapters/go/index.js.map +1 -0
  9. package/dist/adapters/kotlin/index.d.ts +13 -0
  10. package/dist/adapters/kotlin/index.d.ts.map +1 -0
  11. package/dist/adapters/kotlin/index.js +188 -0
  12. package/dist/adapters/kotlin/index.js.map +1 -0
  13. package/dist/adapters/php/index.d.ts +20 -0
  14. package/dist/adapters/php/index.d.ts.map +1 -0
  15. package/dist/adapters/php/index.js +123 -0
  16. package/dist/adapters/php/index.js.map +1 -0
  17. package/dist/adapters/python/index.d.ts +12 -0
  18. package/dist/adapters/python/index.d.ts.map +1 -0
  19. package/dist/adapters/python/index.js +145 -0
  20. package/dist/adapters/python/index.js.map +1 -0
  21. package/dist/adapters/ruby/index.d.ts +12 -0
  22. package/dist/adapters/ruby/index.d.ts.map +1 -0
  23. package/dist/adapters/ruby/index.js +86 -0
  24. package/dist/adapters/ruby/index.js.map +1 -0
  25. package/dist/adapters/rust/index.d.ts +13 -0
  26. package/dist/adapters/rust/index.d.ts.map +1 -0
  27. package/dist/adapters/rust/index.js +213 -0
  28. package/dist/adapters/rust/index.js.map +1 -0
  29. package/dist/adapters/typescript/emitter/emitSchema.d.ts +1 -1
  30. package/dist/adapters/typescript/emitter/emitSchema.d.ts.map +1 -1
  31. package/dist/adapters/typescript/emitter/emitSchema.js +1 -1
  32. package/dist/adapters/typescript/emitter/emitSchema.js.map +1 -1
  33. package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts +2 -2
  34. package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts.map +1 -1
  35. package/dist/adapters/typescript/emitter/emitTypeScriptModels.js +5 -3
  36. package/dist/adapters/typescript/emitter/emitTypeScriptModels.js.map +1 -1
  37. package/dist/adapters/typescript/emitter/importCollector.d.ts +1 -1
  38. package/dist/adapters/typescript/emitter/importCollector.d.ts.map +1 -1
  39. package/dist/adapters/typescript/index.d.ts.map +1 -1
  40. package/dist/adapters/typescript/index.js +43 -2
  41. package/dist/adapters/typescript/index.js.map +1 -1
  42. package/dist/cli.js +2 -1
  43. package/dist/cli.js.map +1 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +14 -0
  46. package/dist/index.js.map +1 -1
  47. package/dist/pipeline/intake.d.ts.map +1 -1
  48. package/dist/pipeline/intake.js +32 -21
  49. package/dist/pipeline/intake.js.map +1 -1
  50. package/dist/types/ir.d.ts +1 -1
  51. package/dist/types/ir.d.ts.map +1 -1
  52. package/dist/types/sdk-module.d.ts +22 -2
  53. package/dist/types/sdk-module.d.ts.map +1 -1
  54. package/dist/types/sdk-module.js +1 -1
  55. package/package.json +1 -1
  56. package/templates/csharp/client.hbs +95 -0
  57. package/templates/csharp/csproj.hbs +18 -0
  58. package/templates/csharp/domain.hbs +183 -0
  59. package/templates/csharp/errors.hbs +45 -0
  60. package/templates/csharp/readme.hbs +76 -0
  61. package/templates/csharp/transport.hbs +291 -0
  62. package/templates/csharp/types.hbs +104 -0
  63. package/templates/go/client.hbs +158 -0
  64. package/templates/go/domain.hbs +89 -0
  65. package/templates/go/errors.hbs +38 -0
  66. package/templates/go/go-mod.hbs +6 -0
  67. package/templates/go/readme.hbs +73 -0
  68. package/templates/go/transport.hbs +379 -0
  69. package/templates/go/types.hbs +80 -0
  70. package/templates/go/utils.hbs +45 -0
  71. package/templates/kotlin/build-gradle.hbs +151 -0
  72. package/templates/kotlin/client.hbs +57 -0
  73. package/templates/kotlin/domain.hbs +73 -0
  74. package/templates/kotlin/errors.hbs +42 -0
  75. package/templates/kotlin/gradle-properties.hbs +2 -0
  76. package/templates/kotlin/readme.hbs +133 -0
  77. package/templates/kotlin/settings-gradle.hbs +1 -0
  78. package/templates/kotlin/transport.hbs +246 -0
  79. package/templates/kotlin/types.hbs +100 -0
  80. package/templates/php/client.hbs +74 -0
  81. package/templates/php/composer.hbs +19 -0
  82. package/templates/php/domain.hbs +173 -0
  83. package/templates/php/errors.hbs +131 -0
  84. package/templates/php/readme.hbs +89 -0
  85. package/templates/php/transport.hbs +285 -0
  86. package/templates/php/types.hbs +95 -0
  87. package/templates/python/client.hbs +102 -0
  88. package/templates/python/domain.hbs +139 -0
  89. package/templates/python/index.hbs +20 -0
  90. package/templates/python/pyproject.hbs +26 -0
  91. package/templates/python/readme.hbs +64 -0
  92. package/templates/python/resources-init.hbs +11 -0
  93. package/templates/python/transport.hbs +191 -0
  94. package/templates/python/types-errors.hbs +75 -0
  95. package/templates/python/types.hbs +79 -0
  96. package/templates/ruby/Gemfile.hbs +5 -0
  97. package/templates/ruby/client.hbs +86 -0
  98. package/templates/ruby/domain.hbs +110 -0
  99. package/templates/ruby/errors.hbs +150 -0
  100. package/templates/ruby/gemspec.hbs +30 -0
  101. package/templates/ruby/index.hbs +54 -0
  102. package/templates/ruby/readme.hbs +113 -0
  103. package/templates/ruby/transport.hbs +177 -0
  104. package/templates/ruby/types.hbs +126 -0
  105. package/templates/ruby/version.hbs +5 -0
  106. package/templates/rust/api-mod.hbs +3 -0
  107. package/templates/rust/cargo.hbs +22 -0
  108. package/templates/rust/client.hbs +114 -0
  109. package/templates/rust/domain.hbs +94 -0
  110. package/templates/rust/error.hbs +74 -0
  111. package/templates/rust/lib.hbs +85 -0
  112. package/templates/rust/readme.hbs +77 -0
  113. package/templates/rust/transport.hbs +287 -0
  114. package/templates/rust/types.hbs +142 -0
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../transport"
4
+ require_relative "../errors"
5
+
6
+ module {{pascalCase meta.clientName}}
7
+ # {{#if description}}{{description}}{{else}}{{name}} API domain{{/if}}
8
+ class {{pascalCase name}}
9
+ # @return [Transport]
10
+ attr_reader :transport
11
+
12
+ # @param transport [Transport]
13
+ def initialize(transport)
14
+ @transport = transport
15
+ end
16
+
17
+ {{#each operations}}
18
+ {{#if deprecated}}
19
+ # @deprecated {{#if deprecationMessage}}{{deprecationMessage}}{{else}}Use a replacement method.{{/if}}
20
+ {{/if}}
21
+ {{#if (or summary description)}}
22
+ # {{#if summary}}{{summary}}{{#if description}} — {{description}}{{/if}}{{else}}{{description}}{{/if}}
23
+ {{/if}}
24
+ {{#if pathParams.length}}
25
+ # @param {{#each pathParams}}{{snakeCase name}} [{{type}}]{{#unless @last}}, {{/unless}}{{/each}}
26
+ {{/if}}
27
+ {{#if requestBody}}
28
+ # @param body [{{requestBody.type}}{{#unless requestBody.required}}, nil{{/unless}}] request body
29
+ {{/if}}
30
+ {{#if (or queryParams.length queryType)}}
31
+ # @param query [Hash, nil] query parameters
32
+ {{/if}}
33
+ {{#if (and headerParams.length headerType)}}
34
+ # @param headers [Hash, nil] extra request headers
35
+ {{/if}}
36
+ {{#if (and cookieParams.length cookieType)}}
37
+ # @param cookies [Hash, nil] cookie values
38
+ {{/if}}
39
+ {{#if timeoutMs}}
40
+ # @param timeout [Integer, nil] per-request timeout override (seconds)
41
+ {{/if}}
42
+ # @raise [{{pascalCase ../meta.clientName}}::SDKError]
43
+ # @return [{{responseType}}]
44
+ def {{snakeCase name}}({{#if pathParams.length}}{{#each pathParams}}{{snakeCase name}}:{{#if (or ../requestBody (or ../queryType ../queryParams.length) (or ../headerType ../cookieType))}}, {{/if}}{{/each}}{{/if}}{{#if requestBody}}body: nil{{#if (or ../queryType ../queryParams.length (or ../headerType ../cookieType))}}, {{/if}}{{/if}}{{#if (or queryParams.length queryType)}}query: nil{{#if (or ../headerType ../cookieType)}}, {{/if}}{{/if}}{{#if (and headerParams.length headerType)}}headers: {}{{#if ../cookieType}}, {{/if}}{{/if}}{{#if (and cookieParams.length cookieType)}}cookies: nil{{/if}})
45
+ {{#if (or pathParams.length headerParams.length cookieParams.length)}}
46
+ # Build request headers
47
+ request_headers = {}
48
+ {{#if (and headerParams.length headerType)}}
49
+ {{#each headerParams}}
50
+ request_headers["{{originalName}}"] = headers[:{{snakeCase name}}].to_s if headers[:{{snakeCase name}}]
51
+ {{/each}}
52
+ {{/if}}
53
+ {{#if (and cookieParams.length cookieType)}}
54
+ if cookies
55
+ cookie_pairs = []
56
+ {{#each cookieParams}}
57
+ cookie_pairs << "{{originalName}}=#{::URI.encode_www_form_component(cookies[:{{snakeCase name}}].to_s)}" if cookies[:{{snakeCase name}}]
58
+ {{/each}}
59
+ request_headers["Cookie"] = cookie_pairs.join("; ") unless cookie_pairs.empty?
60
+ end
61
+ {{/if}}
62
+ {{/if}}
63
+
64
+ {{#if pathParams.length}}
65
+ # Path parameter substitution
66
+ path_params = {
67
+ {{#each pathParams}}
68
+ {{originalName}}: {{snakeCase name}},
69
+ {{/each}}
70
+ }
71
+ {{/if}}
72
+
73
+ {{#if queryParams.length}}
74
+ # Query parameters
75
+ query_params = {}
76
+ {{#each queryParams}}
77
+ query_params["{{originalName}}"] = query[:{{snakeCase name}}] if query && query.key?(:{{snakeCase name}})
78
+ {{/each}}
79
+ query_params = nil if query_params.empty?
80
+ {{else if queryType}}
81
+ # Free-form query parameters
82
+ query_params = query
83
+ {{/if}}
84
+
85
+ transport.request(
86
+ method: :{{httpMethod}},
87
+ path: "{{path}}",
88
+ {{#if pathParams.length}}
89
+ path_params: path_params,
90
+ {{/if}}
91
+ {{#if requestBody}}
92
+ body: body,
93
+ {{/if}}
94
+ {{#if (or queryParams.length queryType)}}
95
+ query: query_params,
96
+ {{/if}}
97
+ {{#if (or (and headerParams.length headerType) (and cookieParams.length cookieType))}}
98
+ headers: request_headers,
99
+ {{/if}}
100
+ {{#if (neq requestBody.contentType "application/json")}}
101
+ {{#if requestBody.contentType}}
102
+ content_type: "{{requestBody.contentType}}",
103
+ {{/if}}
104
+ {{/if}}
105
+ )
106
+ end
107
+
108
+ {{/each}}
109
+ end
110
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module {{pascalCase meta.clientName}}
4
+ # Structured error raised on API and network failures.
5
+ #
6
+ # Inherits from StandardError and provides structured access to error
7
+ # details including status code, error code, request ID, and convenience
8
+ # methods for common error classifications.
9
+ #
10
+ # @example Basic error handling
11
+ # begin
12
+ # client.users.get_user(id: "123")
13
+ # rescue {{pascalCase meta.clientName}}::SDKError => e
14
+ # puts "Error #{e.status}: #{e.message} (#{e.code})"
15
+ # puts "Request ID: #{e.request_id}" if e.request_id
16
+ #
17
+ # if e.rate_limited?
18
+ # sleep 1
19
+ # retry
20
+ # end
21
+ #
22
+ # if e.server_error?
23
+ # # Log and alert
24
+ # end
25
+ # end
26
+ class SDKError < StandardError
27
+ # @return [String] machine-readable error code (e.g. "not_found", "rate_limit_exceeded")
28
+ attr_reader :code
29
+
30
+ # @return [Integer] HTTP status code (0 for network errors)
31
+ attr_reader :status
32
+
33
+ # @return [String, nil] unique request identifier from the server
34
+ attr_reader :request_id
35
+
36
+ # @return [String, nil] ISO 8601 timestamp of error occurrence
37
+ attr_reader :timestamp
38
+
39
+ # @return [String, nil] request path that caused the error
40
+ attr_reader :path
41
+
42
+ # @return [String, nil] HTTP method that caused the error
43
+ attr_reader :method
44
+
45
+ # @return [String, nil] helpful hint for resolving the error
46
+ attr_reader :hint
47
+
48
+ # @return [Hash, nil] structured error details
49
+ attr_reader :details
50
+
51
+ # Create a new SDKError.
52
+ #
53
+ # @param message [String] human-readable error message
54
+ # @param status [Integer] HTTP status code (0 for network errors)
55
+ # @param code [String] machine-readable error code
56
+ # @param request_id [String, nil] server-provided request ID
57
+ # @param timestamp [String, nil] error timestamp
58
+ # @param path [String, nil] request path
59
+ # @param method [String, nil] HTTP method
60
+ # @param hint [String, nil] resolution hint
61
+ # @param details [Hash, nil] structured error details
62
+ def initialize(
63
+ message,
64
+ status = 0,
65
+ code = "unknown_error",
66
+ request_id: nil,
67
+ timestamp: nil,
68
+ path: nil,
69
+ method: nil,
70
+ hint: nil,
71
+ details: nil
72
+ )
73
+ super(message)
74
+
75
+ @code = code
76
+ @status = status
77
+ @request_id = request_id
78
+ @timestamp = timestamp
79
+ @path = path
80
+ @method = method
81
+ @hint = hint
82
+ @details = details
83
+ end
84
+
85
+ # @return [Boolean] true if the error is a rate limit (HTTP 429)
86
+ def rate_limited?
87
+ @code == "rate_limit_exceeded" || @status == 429
88
+ end
89
+
90
+ # @return [Boolean] true if the error is authentication-related
91
+ def auth_error?
92
+ %w[unauthorized credential_expired credential_revoked].include?(@code)
93
+ end
94
+
95
+ # @return [Boolean] true if the error is a permission error (HTTP 403)
96
+ def forbidden?
97
+ @code == "forbidden" || @status == 403
98
+ end
99
+
100
+ # @return [Boolean] true if the resource was not found (HTTP 404)
101
+ def not_found?
102
+ @code == "not_found" || @status == 404
103
+ end
104
+
105
+ # @return [Boolean] true if the error is a validation error
106
+ def validation_error?
107
+ %w[validation_failed request_validation_failed bad_request unprocessable_entity].include?(@code) ||
108
+ @status == 422
109
+ end
110
+
111
+ # @return [Boolean] true if status is 5xx (server error)
112
+ def server_error?
113
+ @status >= 500
114
+ end
115
+
116
+ # @return [Boolean] true if status is 4xx (client error)
117
+ def client_error?
118
+ @status >= 400 && @status < 500
119
+ end
120
+
121
+ # @return [Boolean] true if this error is safe to retry
122
+ def retryable?
123
+ @code == "network_error" ||
124
+ @code == "gateway_timeout" ||
125
+ @status == 429 ||
126
+ @status >= 500
127
+ end
128
+
129
+ # @return [Hash] a hash representation for logging and serialization
130
+ def to_h
131
+ {
132
+ name: "SDKError",
133
+ message: message,
134
+ status: @status,
135
+ code: @code,
136
+ request_id: @request_id,
137
+ timestamp: @timestamp,
138
+ path: @path,
139
+ method: @method,
140
+ hint: @hint,
141
+ details: @details
142
+ }.compact
143
+ end
144
+
145
+ # @return [String] JSON representation for logging
146
+ def to_json(*args)
147
+ to_h.to_json(*args)
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/{{snakeModule}}/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "{{gemName}}"
7
+ spec.version = {{pascalCase snakeModule}}::VERSION
8
+ spec.authors = ["API SDK Generator"]
9
+ spec.email = ["sdk@example.com"]
10
+
11
+ spec.summary = "{{#if meta.description}}{{meta.description}}{{else}}Ruby SDK for {{meta.title}}{{/if}}"
12
+ spec.description = "{{#if meta.description}}{{meta.description}}{{else}}A Ruby client library for the {{meta.title}} API.{{/if}}"
13
+ spec.homepage = "https://github.com/example/{{gemName}}"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/example/{{gemName}}"
19
+ spec.metadata["changelog_uri"] = "https://github.com/example/{{gemName}}/blob/main/CHANGELOG.md"
20
+
21
+ spec.files = Dir.glob("lib/**/*.rb") + %w[README.md LICENSE Gemfile]
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_dependency "faraday", "~> 2.0"
25
+ spec.add_dependency "json"
26
+
27
+ spec.add_development_dependency "rake", "~> 13.0"
28
+ spec.add_development_dependency "rspec", "~> 3.0"
29
+ spec.add_development_dependency "webmock", "~> 3.0"
30
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "{{snakeModule}}/version"
4
+ require_relative "{{snakeModule}}/errors"
5
+ require_relative "{{snakeModule}}/transport"
6
+ require_relative "{{snakeModule}}/types"
7
+ require_relative "{{snakeModule}}/client"
8
+ {{#each groups}}
9
+ require_relative "{{../snakeModule}}/resources/{{fileName}}"
10
+ {{/each}}
11
+
12
+ module {{pascalCase snakeModule}}
13
+ # Factory method to create a new client instance.
14
+ #
15
+ # @param base_url [String] the base URL for the API
16
+ # @param timeout [Integer] request timeout in seconds (default: 15)
17
+ # @param retries [Hash] retry configuration
18
+ # @option retries [Integer] :attempts (3)
19
+ # @option retries [Float] :backoff (0.4)
20
+ # @option retries [Boolean] :jitter (true)
21
+ # @option retries [Float] :max_backoff (30.0)
22
+ {{#each securitySchemes}}
23
+ # @param {{camelCase name}} [String, nil] {{#if description}}{{description}}{{else}}credential for {{name}}{{/if}}
24
+ {{/each}}
25
+ {{#if hasSSE}}
26
+ # @param sse_client [Object, nil] optional SSE client implementation
27
+ {{/if}}
28
+ # @return [{{pascalCase meta.clientName}}::Client]
29
+ def self.create_client(
30
+ base_url: '{{meta.baseUrl}}',
31
+ timeout: 15,
32
+ retries: { attempts: 3, backoff: 0.4, jitter: true, max_backoff: 30.0 },
33
+ {{#each securitySchemes}}
34
+ {{camelCase name}}: nil,
35
+ {{/each}}
36
+ {{#if hasSSE}}
37
+ sse_client: nil,
38
+ {{/if}}
39
+ **extra
40
+ )
41
+ {{pascalCase meta.clientName}}::Client.new(
42
+ base_url: base_url,
43
+ timeout: timeout,
44
+ retries: retries,
45
+ {{#each securitySchemes}}
46
+ {{camelCase name}}: {{camelCase name}},
47
+ {{/each}}
48
+ {{#if hasSSE}}
49
+ sse_client: sse_client,
50
+ {{/if}}
51
+ **extra
52
+ )
53
+ end
54
+ end
@@ -0,0 +1,113 @@
1
+ # {{meta.title}} Ruby SDK
2
+
3
+ {{#if meta.description}}
4
+ {{meta.description}}
5
+
6
+ {{/if}}
7
+ > This SDK was automatically generated by [Tesseract](https://www.npmjs.com/package/@apollo-deploy/tesseract).
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem '{{gemName}}'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ ```bash
20
+ bundle install
21
+ ```
22
+
23
+ Or install it yourself as:
24
+
25
+ ```bash
26
+ gem install {{gemName}}
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ```ruby
32
+ require '{{gemName}}'
33
+
34
+ client = {{pascalCase meta.clientName}}::Client.new(
35
+ base_url: '{{meta.baseUrl}}',
36
+ {{#each securitySchemes}}
37
+ {{camelCase name}}: 'your-{{kebabCase name}}-credential',
38
+ {{/each}}
39
+ )
40
+
41
+ # Make API calls
42
+ result = client.{{groups.[0].name}}.{{groups.[0].operations.[0].name}}
43
+ ```
44
+
45
+ ## Configuration
46
+
47
+ ```ruby
48
+ client = {{pascalCase meta.clientName}}::Client.new(
49
+ base_url: '{{meta.baseUrl}}',
50
+ {{#each securitySchemes}}
51
+ {{camelCase name}}: ENV['{{upperCase (snakeCase name)}}'],
52
+ {{/each}}
53
+ timeout: 30, # seconds
54
+ retries: { attempts: 3, backoff: 0.4, jitter: true }
55
+ )
56
+ ```
57
+
58
+ ## API Reference
59
+
60
+ {{#each groups}}
61
+ ### {{pascalCase name}}
62
+
63
+ {{#if description}}
64
+ {{description}}
65
+
66
+ {{/if}}
67
+ | Method | HTTP | Path | Description |
68
+ |--------|------|------|-------------|
69
+ {{#each operations}}
70
+ | `{{snakeCase name}}` | {{httpMethod}} | `{{path}}` | {{#if summary}}{{summary}}{{else}}-{{/if}} |
71
+ {{/each}}
72
+
73
+ {{/each}}
74
+
75
+ ## Types
76
+
77
+ {{#each schemas}}
78
+ {{#if isEnum}}
79
+ - `{{pascalCase name}}` — module with constants: {{#each enumValues}}`{{this}}`{{#unless @last}}, {{/unless}}{{/each}}
80
+ {{else if isTypeAlias}}
81
+ - `{{pascalCase name}}` — type alias
82
+ {{else}}
83
+ - `{{pascalCase name}}`{{#if description}} — {{description}}{{/if}}
84
+ {{/if}}
85
+ {{/each}}
86
+
87
+ ## Error Handling
88
+
89
+ ```ruby
90
+ begin
91
+ result = client.some_api.some_method
92
+ rescue {{pascalCase meta.clientName}}::SDKError => e
93
+ puts "Error #{e.status}: #{e.message} (#{e.code})"
94
+ puts "Request ID: #{e.request_id}" if e.request_id
95
+
96
+ if e.rate_limited?
97
+ sleep 1
98
+ retry
99
+ end
100
+
101
+ raise
102
+ end
103
+ ```
104
+
105
+ ## Development
106
+
107
+ After checking out the repo, run `bin/setup` to install dependencies.
108
+ Then, run `rake spec` to run the tests.
109
+ You can also run `bin/console` for an interactive prompt.
110
+
111
+ ## License
112
+
113
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "json"
5
+ require_relative "errors"
6
+
7
+ module {{pascalCase meta.clientName}}
8
+ # HTTP transport layer built on Faraday.
9
+ class Transport
10
+ attr_reader :config
11
+ attr_reader :connection
12
+
13
+ def initialize(config)
14
+ @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
24
+ end
25
+ end
26
+
27
+ def request(method:, path:, path_params: {}, query: nil, body: nil, headers: {}, content_type: nil)
28
+ 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
+ begin
35
+ attempts += 1
36
+ 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?
40
+ end
41
+ 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))
45
+ retry
46
+ end
47
+ end
48
+
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)
70
+ end
71
+
72
+ private
73
+
74
+ def build_url(path, path_params)
75
+ return path if path_params.empty?
76
+ path.gsub(/\{(\w+)\}/) do |_match|
77
+ key = ::Regexp.last_match(1)
78
+ value = path_params[key.to_sym] || path_params[key]
79
+ value.nil? ? _match : value.to_s
80
+ end
81
+ end
82
+
83
+ def build_headers(extra_headers = {}, content_type = nil)
84
+ headers = {}
85
+ if content_type
86
+ headers["Content-Type"] = content_type
87
+ end
88
+ {{#each securitySchemes}}
89
+ {{#if (eq type "apiKey")}}
90
+ {{#if (eq in "header")}}
91
+ headers["{{paramName}}"] = @config.{{camelCase name}} if @config.{{camelCase name}}
92
+ {{/if}}
93
+ {{/if}}
94
+ {{#if (and (eq type "http") (eq scheme "bearer"))}}
95
+ headers["Authorization"] = "Bearer " + (@config.{{camelCase name}} || "") if @config.{{camelCase name}}
96
+ {{/if}}
97
+ {{#if (eq type "oauth2")}}
98
+ headers["Authorization"] = "Bearer " + (@config.{{camelCase name}} || "") if @config.{{camelCase name}}
99
+ {{/if}}
100
+ {{#if (eq type "openIdConnect")}}
101
+ headers["Authorization"] = "Bearer " + (@config.{{camelCase name}} || "") if @config.{{camelCase name}}
102
+ {{/if}}
103
+ {{/each}}
104
+ headers.merge!(extra_headers)
105
+ headers
106
+ end
107
+
108
+ def parse_response(response)
109
+ body = response.body
110
+ 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
119
+ end
120
+ end
121
+
122
+ def map_error(error, method:, path:)
123
+ case error
124
+ when Faraday::TimeoutError
125
+ SDKError.new("Request timed out", 0, "network_error", method: method, path: path)
126
+ 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)
152
+ else
153
+ SDKError.new(error.message, 0, "unknown_error", method: method, path: path)
154
+ end
155
+ end
156
+
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
168
+ end
169
+
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
175
+ end
176
+ end
177
+ end