@apollo-deploy/tesseract 0.9.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) 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 +186 -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 +2 -2
  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/cli.js +2 -1
  40. package/dist/cli.js.map +1 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +14 -0
  43. package/dist/index.js.map +1 -1
  44. package/dist/pipeline/intake.d.ts +3 -3
  45. package/dist/pipeline/intake.d.ts.map +1 -1
  46. package/dist/pipeline/intake.js +194 -121
  47. package/dist/pipeline/intake.js.map +1 -1
  48. package/dist/types/ir.d.ts +1 -1
  49. package/dist/types/ir.d.ts.map +1 -1
  50. package/dist/types/sdk-module.d.ts +22 -2
  51. package/dist/types/sdk-module.d.ts.map +1 -1
  52. package/dist/types/sdk-module.js +1 -1
  53. package/package.json +1 -1
  54. package/templates/csharp/client.hbs +95 -0
  55. package/templates/csharp/csproj.hbs +18 -0
  56. package/templates/csharp/domain.hbs +183 -0
  57. package/templates/csharp/errors.hbs +45 -0
  58. package/templates/csharp/readme.hbs +76 -0
  59. package/templates/csharp/transport.hbs +291 -0
  60. package/templates/csharp/types.hbs +104 -0
  61. package/templates/go/client.hbs +158 -0
  62. package/templates/go/domain.hbs +89 -0
  63. package/templates/go/errors.hbs +38 -0
  64. package/templates/go/go-mod.hbs +6 -0
  65. package/templates/go/readme.hbs +73 -0
  66. package/templates/go/transport.hbs +379 -0
  67. package/templates/go/types.hbs +80 -0
  68. package/templates/go/utils.hbs +45 -0
  69. package/templates/kotlin/build-gradle.hbs +41 -0
  70. package/templates/kotlin/client.hbs +57 -0
  71. package/templates/kotlin/domain.hbs +73 -0
  72. package/templates/kotlin/errors.hbs +42 -0
  73. package/templates/kotlin/gradle-properties.hbs +2 -0
  74. package/templates/kotlin/readme.hbs +105 -0
  75. package/templates/kotlin/settings-gradle.hbs +1 -0
  76. package/templates/kotlin/transport.hbs +246 -0
  77. package/templates/kotlin/types.hbs +100 -0
  78. package/templates/php/client.hbs +74 -0
  79. package/templates/php/composer.hbs +19 -0
  80. package/templates/php/domain.hbs +173 -0
  81. package/templates/php/errors.hbs +131 -0
  82. package/templates/php/readme.hbs +89 -0
  83. package/templates/php/transport.hbs +285 -0
  84. package/templates/php/types.hbs +95 -0
  85. package/templates/python/client.hbs +102 -0
  86. package/templates/python/domain.hbs +139 -0
  87. package/templates/python/index.hbs +20 -0
  88. package/templates/python/pyproject.hbs +26 -0
  89. package/templates/python/readme.hbs +64 -0
  90. package/templates/python/resources-init.hbs +11 -0
  91. package/templates/python/transport.hbs +191 -0
  92. package/templates/python/types-errors.hbs +75 -0
  93. package/templates/python/types.hbs +79 -0
  94. package/templates/ruby/Gemfile.hbs +5 -0
  95. package/templates/ruby/client.hbs +86 -0
  96. package/templates/ruby/domain.hbs +110 -0
  97. package/templates/ruby/errors.hbs +150 -0
  98. package/templates/ruby/gemspec.hbs +30 -0
  99. package/templates/ruby/index.hbs +54 -0
  100. package/templates/ruby/readme.hbs +113 -0
  101. package/templates/ruby/transport.hbs +177 -0
  102. package/templates/ruby/types.hbs +126 -0
  103. package/templates/ruby/version.hbs +5 -0
  104. package/templates/rust/api-mod.hbs +3 -0
  105. package/templates/rust/cargo.hbs +22 -0
  106. package/templates/rust/client.hbs +114 -0
  107. package/templates/rust/domain.hbs +94 -0
  108. package/templates/rust/error.hbs +74 -0
  109. package/templates/rust/lib.hbs +85 -0
  110. package/templates/rust/readme.hbs +77 -0
  111. package/templates/rust/transport.hbs +287 -0
  112. package/templates/rust/types.hbs +142 -0
@@ -0,0 +1,75 @@
1
+ """Error classes for {{moduleName}}."""
2
+
3
+ from typing import Optional, Dict, Any
4
+
5
+
6
+ class SDKError(Exception):
7
+ """Structured error thrown by all SDK operations."""
8
+
9
+ def __init__(
10
+ self,
11
+ message: str,
12
+ status: int,
13
+ code: str,
14
+ request_id: Optional[str] = None,
15
+ timestamp: Optional[str] = None,
16
+ path: Optional[str] = None,
17
+ method: Optional[str] = None,
18
+ hint: Optional[str] = None,
19
+ details: Optional[Any] = None,
20
+ ):
21
+ super().__init__(message)
22
+ self.message = message
23
+ self.status = status
24
+ self.code = code
25
+ self.request_id = request_id
26
+ self.timestamp = timestamp
27
+ self.path = path
28
+ self.method = method
29
+ self.hint = hint
30
+ self.details = details
31
+
32
+ def is_rate_limited(self) -> bool:
33
+ return self.code == "rate_limit_exceeded"
34
+
35
+ def is_auth_error(self) -> bool:
36
+ return self.code in ("unauthorized", "credential_expired", "credential_revoked")
37
+
38
+ def is_forbidden(self) -> bool:
39
+ return self.code == "forbidden"
40
+
41
+ def is_not_found(self) -> bool:
42
+ return self.code == "not_found"
43
+
44
+ def is_validation_error(self) -> bool:
45
+ return self.code in (
46
+ "validation_failed",
47
+ "request_validation_failed",
48
+ "bad_request",
49
+ "unprocessable_entity",
50
+ )
51
+
52
+ def is_server_error(self) -> bool:
53
+ return self.status >= 500
54
+
55
+ def is_client_error(self) -> bool:
56
+ return 400 <= self.status < 500
57
+
58
+ def is_retryable(self) -> bool:
59
+ return (
60
+ self.code in ("network_error", "gateway_timeout")
61
+ or self.status == 429
62
+ or self.status >= 500
63
+ )
64
+
65
+ def to_dict(self) -> Dict[str, Any]:
66
+ return {
67
+ "message": self.message,
68
+ "status": self.status,
69
+ "code": self.code,
70
+ "request_id": self.request_id,
71
+ "timestamp": self.timestamp,
72
+ "path": self.path,
73
+ "method": self.method,
74
+ "hint": self.hint,
75
+ }
@@ -0,0 +1,79 @@
1
+ """Generated type definitions for {{moduleName}}."""
2
+
3
+ from typing import Optional, List, Dict, Any, Literal, Union
4
+ from datetime import datetime
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ # -- Pagination --------------------------------------------------
9
+
10
+ class PaginationQuery(BaseModel):
11
+ page: Optional[int] = Field(None, description="Page number (1-indexed)")
12
+ limit: Optional[int] = Field(None, description="Number of items per page")
13
+ sort_by: Optional[str] = Field(None, description="Column name to sort by")
14
+ sort_order: Optional[Literal["asc", "desc"]] = Field(None, description="Sort direction")
15
+
16
+
17
+ class PageMeta(BaseModel):
18
+ size: int
19
+ total_pages: int
20
+ has_more: bool
21
+
22
+
23
+ class Page(BaseModel):
24
+ data: List[Any]
25
+ page: PageMeta
26
+
27
+
28
+ # -- Common Responses --------------------------------------------
29
+
30
+ class SuccessResponse(BaseModel):
31
+ success: bool
32
+ message: Optional[str] = None
33
+
34
+
35
+ class MessageResponse(BaseModel):
36
+ message: str
37
+
38
+
39
+ class ApiErrorEnvelope(BaseModel):
40
+ error: str
41
+ message: str
42
+ status: int
43
+ request_id: Optional[str] = None
44
+ timestamp: Optional[str] = None
45
+ path: Optional[str] = None
46
+ method: Optional[str] = None
47
+ hint: Optional[str] = None
48
+
49
+
50
+ # -- Generated Schemas -------------------------------------------
51
+
52
+ {{#each schemas}}
53
+ {{#if (eq ownership.kind "external")}}
54
+ {{else}}
55
+ {{#if isEnum}}
56
+ class {{name}}(str):
57
+ """{{#if description}}{{description}}{{else}}Enum for {{name}}{{/if}}"""
58
+ {{#each enumValues}}
59
+ {{pascalCase this}} = "{{this}}"
60
+ {{/each}}
61
+
62
+ {{else if isUnionType}}
63
+ {{name}} = Union[{{#each unionMembers}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}]
64
+ """{{#if description}}{{description}}{{else}}Union type for {{name}}{{/if}}"""
65
+
66
+ {{else if isTypeAlias}}
67
+ {{name}} = {{additionalPropertiesType}}
68
+ """{{#if description}}{{description}}{{else}}Type alias for {{name}}{{/if}}"""
69
+
70
+ {{else}}
71
+ class {{name}}(BaseModel):
72
+ """{{#if description}}{{description}}{{else}}Schema for {{name}}{{/if}}"""
73
+ {{#each properties}}
74
+ {{name}}: {{#if nullable}}Optional[{{/if}}{{pythonType type}}{{#if nullable}}] = None{{else}}{{#unless required}} = None{{/unless}}{{/if}}
75
+ {{/each}}
76
+
77
+ {{/if}}
78
+ {{/if}}
79
+ {{/each}}
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "transport"
4
+ {{#each groups}}
5
+ require_relative "resources/{{fileName}}"
6
+ {{/each}}
7
+
8
+ module {{pascalCase meta.clientName}}
9
+ # Configuration for the {{meta.title}} SDK client.
10
+ class Config
11
+ # @return [String] the base URL for API requests
12
+ attr_accessor :base_url
13
+ # @return [Integer] request timeout in seconds
14
+ attr_accessor :timeout
15
+ # @return [Hash] retry configuration
16
+ attr_accessor :retries
17
+ # @return [Hash] default headers sent with every request
18
+ attr_accessor :default_headers
19
+ {{#each securitySchemes}}
20
+ # @return [String, nil] credential for {{name}} security scheme
21
+ attr_accessor :{{camelCase name}}
22
+ {{/each}}
23
+
24
+ # @param base_url [String] the base URL for API requests
25
+ # @param timeout [Integer] request timeout in seconds (default: 15)
26
+ # @param retries [Hash] retry configuration
27
+ # @param default_headers [Hash] headers sent with every request
28
+ {{#each securitySchemes}}
29
+ # @param {{camelCase name}} [String, nil] credential for {{name}}
30
+ {{/each}}
31
+ def initialize(
32
+ base_url: '{{meta.baseUrl}}',
33
+ timeout: 15,
34
+ retries: { attempts: 3, backoff: 0.4, jitter: true, max_backoff: 30.0 },
35
+ default_headers: {},
36
+ {{#each securitySchemes}}
37
+ {{camelCase name}}: nil,
38
+ {{/each}}
39
+ **_
40
+ )
41
+ @base_url = base_url
42
+ @timeout = timeout
43
+ @retries = retries
44
+ @default_headers = default_headers
45
+ {{#each securitySchemes}}
46
+ @{{camelCase name}} = {{camelCase name}}
47
+ {{/each}}
48
+ end
49
+ end
50
+
51
+ # Main client for the {{meta.title}} API.
52
+ #
53
+ # Provides access to all API domains as instance methods.
54
+ class Client
55
+ {{#each groups}}
56
+ # @return [{{pascalCase ../meta.clientName}}::{{pascalCase name}}]
57
+ attr_reader :{{name}}
58
+ {{/each}}
59
+
60
+ # @return [{{pascalCase meta.clientName}}::Config] the client configuration
61
+ attr_reader :config
62
+
63
+ # @return [{{pascalCase meta.clientName}}::Transport] the underlying HTTP transport
64
+ attr_reader :transport
65
+
66
+ # Create a new API client.
67
+ #
68
+ # @param config [{{pascalCase meta.clientName}}::Config, Hash, nil]
69
+ # Either a Config instance or keyword arguments matching Config#initialize.
70
+ def initialize(config = nil)
71
+ @config = if config.is_a?(Config)
72
+ config
73
+ elsif config.is_a?(Hash)
74
+ Config.new(**config)
75
+ else
76
+ Config.new
77
+ end
78
+
79
+ @transport = Transport.new(@config)
80
+
81
+ {{#each groups}}
82
+ @{{name}} = {{pascalCase ../meta.clientName}}::{{pascalCase name}}.new(@transport)
83
+ {{/each}}
84
+ end
85
+ end
86
+ end
@@ -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