@apollo-deploy/tesseract 0.9.1 → 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.
- package/dist/adapters/csharp/index.d.ts +12 -0
- package/dist/adapters/csharp/index.d.ts.map +1 -0
- package/dist/adapters/csharp/index.js +99 -0
- package/dist/adapters/csharp/index.js.map +1 -0
- package/dist/adapters/go/index.d.ts +12 -0
- package/dist/adapters/go/index.d.ts.map +1 -0
- package/dist/adapters/go/index.js +135 -0
- package/dist/adapters/go/index.js.map +1 -0
- package/dist/adapters/kotlin/index.d.ts +13 -0
- package/dist/adapters/kotlin/index.d.ts.map +1 -0
- package/dist/adapters/kotlin/index.js +186 -0
- package/dist/adapters/kotlin/index.js.map +1 -0
- package/dist/adapters/php/index.d.ts +20 -0
- package/dist/adapters/php/index.d.ts.map +1 -0
- package/dist/adapters/php/index.js +123 -0
- package/dist/adapters/php/index.js.map +1 -0
- package/dist/adapters/python/index.d.ts +12 -0
- package/dist/adapters/python/index.d.ts.map +1 -0
- package/dist/adapters/python/index.js +145 -0
- package/dist/adapters/python/index.js.map +1 -0
- package/dist/adapters/ruby/index.d.ts +12 -0
- package/dist/adapters/ruby/index.d.ts.map +1 -0
- package/dist/adapters/ruby/index.js +86 -0
- package/dist/adapters/ruby/index.js.map +1 -0
- package/dist/adapters/rust/index.d.ts +13 -0
- package/dist/adapters/rust/index.d.ts.map +1 -0
- package/dist/adapters/rust/index.js +213 -0
- package/dist/adapters/rust/index.js.map +1 -0
- package/dist/adapters/typescript/emitter/emitSchema.d.ts +1 -1
- package/dist/adapters/typescript/emitter/emitSchema.d.ts.map +1 -1
- package/dist/adapters/typescript/emitter/emitSchema.js +1 -1
- package/dist/adapters/typescript/emitter/emitSchema.js.map +1 -1
- package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts +2 -2
- package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts.map +1 -1
- package/dist/adapters/typescript/emitter/emitTypeScriptModels.js +2 -2
- package/dist/adapters/typescript/emitter/emitTypeScriptModels.js.map +1 -1
- package/dist/adapters/typescript/emitter/importCollector.d.ts +1 -1
- package/dist/adapters/typescript/emitter/importCollector.d.ts.map +1 -1
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/intake.js +23 -16
- package/dist/pipeline/intake.js.map +1 -1
- package/dist/types/ir.d.ts +1 -1
- package/dist/types/ir.d.ts.map +1 -1
- package/dist/types/sdk-module.d.ts +22 -2
- package/dist/types/sdk-module.d.ts.map +1 -1
- package/dist/types/sdk-module.js +1 -1
- package/package.json +1 -1
- package/templates/csharp/client.hbs +95 -0
- package/templates/csharp/csproj.hbs +18 -0
- package/templates/csharp/domain.hbs +183 -0
- package/templates/csharp/errors.hbs +45 -0
- package/templates/csharp/readme.hbs +76 -0
- package/templates/csharp/transport.hbs +291 -0
- package/templates/csharp/types.hbs +104 -0
- package/templates/go/client.hbs +158 -0
- package/templates/go/domain.hbs +89 -0
- package/templates/go/errors.hbs +38 -0
- package/templates/go/go-mod.hbs +6 -0
- package/templates/go/readme.hbs +73 -0
- package/templates/go/transport.hbs +379 -0
- package/templates/go/types.hbs +80 -0
- package/templates/go/utils.hbs +45 -0
- package/templates/kotlin/build-gradle.hbs +41 -0
- package/templates/kotlin/client.hbs +57 -0
- package/templates/kotlin/domain.hbs +73 -0
- package/templates/kotlin/errors.hbs +42 -0
- package/templates/kotlin/gradle-properties.hbs +2 -0
- package/templates/kotlin/readme.hbs +105 -0
- package/templates/kotlin/settings-gradle.hbs +1 -0
- package/templates/kotlin/transport.hbs +246 -0
- package/templates/kotlin/types.hbs +100 -0
- package/templates/php/client.hbs +74 -0
- package/templates/php/composer.hbs +19 -0
- package/templates/php/domain.hbs +173 -0
- package/templates/php/errors.hbs +131 -0
- package/templates/php/readme.hbs +89 -0
- package/templates/php/transport.hbs +285 -0
- package/templates/php/types.hbs +95 -0
- package/templates/python/client.hbs +102 -0
- package/templates/python/domain.hbs +139 -0
- package/templates/python/index.hbs +20 -0
- package/templates/python/pyproject.hbs +26 -0
- package/templates/python/readme.hbs +64 -0
- package/templates/python/resources-init.hbs +11 -0
- package/templates/python/transport.hbs +191 -0
- package/templates/python/types-errors.hbs +75 -0
- package/templates/python/types.hbs +79 -0
- package/templates/ruby/Gemfile.hbs +5 -0
- package/templates/ruby/client.hbs +86 -0
- package/templates/ruby/domain.hbs +110 -0
- package/templates/ruby/errors.hbs +150 -0
- package/templates/ruby/gemspec.hbs +30 -0
- package/templates/ruby/index.hbs +54 -0
- package/templates/ruby/readme.hbs +113 -0
- package/templates/ruby/transport.hbs +177 -0
- package/templates/ruby/types.hbs +126 -0
- package/templates/ruby/version.hbs +5 -0
- package/templates/rust/api-mod.hbs +3 -0
- package/templates/rust/cargo.hbs +22 -0
- package/templates/rust/client.hbs +114 -0
- package/templates/rust/domain.hbs +94 -0
- package/templates/rust/error.hbs +74 -0
- package/templates/rust/lib.hbs +85 -0
- package/templates/rust/readme.hbs +77 -0
- package/templates/rust/transport.hbs +287 -0
- 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,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
|