haveapi 0.29.12 → 0.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Rakefile +1 -0
- data/doc/client-examples.md +198 -0
- data/haveapi.gemspec +1 -1
- data/lib/haveapi/client_example.rb +47 -1
- data/lib/haveapi/client_examples/dynamic_helpers.rb +129 -0
- data/lib/haveapi/client_examples/go_client.rb +37 -0
- data/lib/haveapi/client_examples/js_async_client.rb +14 -0
- data/lib/haveapi/client_examples/js_client.rb +179 -135
- data/lib/haveapi/client_examples/python_async_client.rb +14 -0
- data/lib/haveapi/client_examples/python_client.rb +172 -0
- data/lib/haveapi/client_examples/rust_client.rb +59 -0
- data/lib/haveapi/client_examples/ts_async_client.rb +14 -0
- data/lib/haveapi/client_examples/ts_client.rb +14 -0
- data/lib/haveapi/documentation_clients.rb +249 -0
- data/lib/haveapi/documentation_context.rb +47 -0
- data/lib/haveapi/documentation_schema.rb +73 -0
- data/lib/haveapi/public/js/highlight-LICENSE +29 -0
- data/lib/haveapi/public/js/highlight.pack.js +2 -2
- data/lib/haveapi/public/js/highlighter.js +16 -6
- data/lib/haveapi/public/js/main.js +9 -1
- data/lib/haveapi/public/js/nojs-tabs.js +37 -3
- data/lib/haveapi/server.rb +39 -0
- data/lib/haveapi/version.rb +1 -1
- data/lib/haveapi/views/main_layout.erb +11 -0
- data/lib/haveapi/views/version_page/auth_body.erb +3 -3
- data/lib/haveapi/views/version_page/client_auth.erb +2 -2
- data/lib/haveapi/views/version_page/client_example.erb +2 -11
- data/lib/haveapi/views/version_page/client_init.erb +2 -2
- data/lib/haveapi/views/version_page/client_result.erb +5 -0
- data/lib/haveapi/views/version_page/resource_body.erb +3 -7
- data/lib/haveapi/views/version_page.erb +13 -9
- data/spec/client_examples/assets/highlighter_test.cjs +26 -0
- data/spec/client_examples/assets/tabs_test.cjs +54 -0
- data/spec/client_examples/context_spec.rb +68 -0
- data/spec/client_examples/dynamic_acceptance.md +83 -0
- data/spec/client_examples/dynamic_acceptance.rb +151 -0
- data/spec/client_examples/dynamic_acceptance_spec.rb +76 -0
- data/spec/client_examples/dynamic_fixture.rb +36 -0
- data/spec/client_examples/dynamic_js_spec.rb +196 -0
- data/spec/client_examples/dynamic_metadata_spec.rb +52 -0
- data/spec/client_examples/dynamic_mounted_paths_spec.rb +65 -0
- data/spec/client_examples/dynamic_python_spec.rb +123 -0
- data/spec/client_examples/go_spec.rb +57 -0
- data/spec/client_examples/profile_mount_spec.rb +215 -0
- data/spec/client_examples/profiles_spec.rb +265 -0
- data/spec/client_examples/rust_client_spec.rb +75 -0
- data/spec/client_examples/structural_schema_spec.rb +48 -0
- data/spec/client_examples/syntax_spec.rb +2 -1
- data/spec/documentation/auth_filtering_spec.rb +12 -0
- data/spec/documentation/client_examples_spec.rb +92 -0
- data/spec/packaging_spec.rb +13 -4
- data/test_support/dynamic_documentation_api.rb +171 -0
- data/test_support/dynamic_protocol_spec.rb +133 -0
- data/test_support/dynamic_python_login_spec.rb +75 -0
- metadata +37 -4
- data/lib/haveapi/client_examples/fs_client.rb +0 -119
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3d58d9e427d490d025fec95d60739ea4aa17b4e86cc3fb9978b7c00b751e6e75
|
|
4
|
+
data.tar.gz: e0b958ac9924d4cacaf75513d79690c3ed2519d6b5d1522a0433d92285742887
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 78980868da2569146dccf39ce14309e27b97d51b4a266b7709813faafb191577520f31408dd6ebf1e5310b37bd43197e9b4e838503cd60c5d8b480607e8c5f6d
|
|
7
|
+
data.tar.gz: 5a2a6ad3e686e3d77b86fb83e7e27b5eaf5e2ac573013fa938255b203405fdabe899a605f71cea8088769e81f323b6678be00272889c56d629757356d9170d87
|
data/.gitignore
CHANGED
data/Rakefile
CHANGED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Client examples in API documentation
|
|
2
|
+
|
|
3
|
+
The Ruby server renders examples from the current user's API description.
|
|
4
|
+
JavaScript and TypeScript examples cover callback and async clients; Python
|
|
5
|
+
examples cover sync and async clients. These clients need no server configuration.
|
|
6
|
+
Ruby, PHP, CLI, curl and HTTP examples are also available. A sample
|
|
7
|
+
that a client cannot represent shows a limitation instead of dropping input or
|
|
8
|
+
inventing a successful result.
|
|
9
|
+
|
|
10
|
+
Go and Rust tabs require a profile for a generated SDK. Their package versions
|
|
11
|
+
are independent of HaveAPI and the API version. Without a profile, the server
|
|
12
|
+
hides those clients from the overview and all initialization, authentication and
|
|
13
|
+
action panels. Install the matching generator gem only when configuring its
|
|
14
|
+
profile; ordinary servers do not depend on either generator.
|
|
15
|
+
|
|
16
|
+
## Configure a generated SDK
|
|
17
|
+
|
|
18
|
+
Register profiles before mounting the server. For an SDK that matches the current
|
|
19
|
+
API, configure its package identity:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
api.documentation_clients('1.0',
|
|
23
|
+
go: {
|
|
24
|
+
import_path: 'example.org/widget/client'
|
|
25
|
+
},
|
|
26
|
+
rust: {
|
|
27
|
+
crate_name: 'widget-client'
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
api.mount('/api/')
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`generator_version` defaults to the server's `HaveAPI::VERSION`. Install the
|
|
34
|
+
optional `haveapi-go-client` or `haveapi-rust-client` generator gem at that
|
|
35
|
+
version. An explicit `generator_version` requires the installed generator to
|
|
36
|
+
report that version; it never downloads one. It is independent of the SDK and
|
|
37
|
+
API versions.
|
|
38
|
+
|
|
39
|
+
At the end of mount, the server builds one private structural model per configured
|
|
40
|
+
API version. The model includes authentication resources, routes and changes from
|
|
41
|
+
post-mount hooks, so canonical names and parent arguments use the complete API
|
|
42
|
+
declaration. The private copy excludes examples, prose and parameter defaults.
|
|
43
|
+
HTML requests use the current user's authorized description, fields and samples;
|
|
44
|
+
they never expose the private model or its initialization diagnostics. Without
|
|
45
|
+
a configured profile, the server loads no optional generator and shows no Go or
|
|
46
|
+
Rust panels.
|
|
47
|
+
|
|
48
|
+
Automatic profiles assume that the installed SDK matches the current server's
|
|
49
|
+
generation contract. A package name or version does not prove its contents.
|
|
50
|
+
An older SDK can lack new actions or use different names after a collision or
|
|
51
|
+
route change. Check actual SDK compatibility before advertising it; use a saved
|
|
52
|
+
schema override below when the SDK represents an older or restricted API.
|
|
53
|
+
|
|
54
|
+
### Optional SDK versions and sources
|
|
55
|
+
|
|
56
|
+
Set `module_version: 'v1.2.3'` for Go or `crate_version: '1.2.3'` for Rust when a
|
|
57
|
+
specific package version is needed. Omit the field or use nil to let the user's
|
|
58
|
+
package manager select a release. The server does not query a registry or claim
|
|
59
|
+
to have resolved a version. Omitted versions produce these installation hints:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
go get example.org/widget/client@latest
|
|
63
|
+
cargo add widget-client
|
|
64
|
+
# Blocking Rust variant:
|
|
65
|
+
cargo add widget-client --features blocking
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Go resolves `@latest` by its normal rules. Cargo selects a compatible release
|
|
69
|
+
according to its resolver, manifest and toolchain; existing lockfiles still
|
|
70
|
+
apply. Explicit Cargo versions remain version requirements, without an added
|
|
71
|
+
exact `=` pin. Latest selection can change over time; supply a version to control
|
|
72
|
+
the selection. Cargo never receives a literal `version = "latest"`. Rust
|
|
73
|
+
examples also show their Tokio and serde_json requirements.
|
|
74
|
+
|
|
75
|
+
For an unpublished Rust SDK, supply an HTTPS Git URL and a full commit SHA:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
api.documentation_clients('7.0',
|
|
79
|
+
rust: {
|
|
80
|
+
crate_name: 'vpsadmin-client',
|
|
81
|
+
source: {
|
|
82
|
+
git: 'https://github.com/vpsfreecz/vpsadmin-rust-client',
|
|
83
|
+
rev: 'ff876abe17e3301da62c1b6afbda055ec89db9d2'
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The dependency hint uses that source instead of a registry release:
|
|
90
|
+
|
|
91
|
+
```toml
|
|
92
|
+
# Cargo.toml
|
|
93
|
+
vpsadmin-client = { git = "https://github.com/vpsfreecz/vpsadmin-rust-client", rev = "ff876abe17e3301da62c1b6afbda055ec89db9d2" }
|
|
94
|
+
# The blocking variant also sets features = ["blocking"].
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`source` accepts only `git` and `rev`. URLs must be absolute HTTPS without
|
|
98
|
+
credentials, query strings, fragments or control characters; revisions must be
|
|
99
|
+
40 hexadecimal characters. Branches, tags, local paths and custom registries are
|
|
100
|
+
not supported. An explicit crate version additionally constrains the selected
|
|
101
|
+
source package. The server never fetches the repository or runs these user
|
|
102
|
+
installation commands. These settings do not regenerate or publish an SDK and
|
|
103
|
+
do not establish that this particular SDK matches a given API surface.
|
|
104
|
+
|
|
105
|
+
### Saved-schema overrides
|
|
106
|
+
|
|
107
|
+
The existing file-based profiles remain supported:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
api.documentation_clients('1.0',
|
|
111
|
+
go: {
|
|
112
|
+
import_path: 'example.org/widget/client',
|
|
113
|
+
module_version: 'v1.2.3',
|
|
114
|
+
description_path: '/srv/widget-sdk/api-1.0.json',
|
|
115
|
+
generator_version: '0.30.0'
|
|
116
|
+
},
|
|
117
|
+
rust: {
|
|
118
|
+
snapshot_path: '/srv/widget-sdk/api-1.0.snapshot.json',
|
|
119
|
+
generator_config_path: '/srv/widget-sdk/generator.toml',
|
|
120
|
+
generator_version: '0.30.0'
|
|
121
|
+
}
|
|
122
|
+
)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Paths name trusted absolute local files. `description_path` is Go's saved
|
|
126
|
+
machine-readable description, not HTML. `snapshot_path` is Rust's saved schema
|
|
127
|
+
and capture metadata. They select an exact SDK shape in place of the current
|
|
128
|
+
server declaration. Keep the files private and retain them with the SDK release.
|
|
129
|
+
|
|
130
|
+
Rust's legacy `generator_config_path` supplies the crate name/version and full
|
|
131
|
+
packaging configuration. It is mutually exclusive with inline `crate_name` and
|
|
132
|
+
`crate_version`, and can be paired with either a saved snapshot or automatic
|
|
133
|
+
schema. A saved snapshot can instead use inline crate identity. `source` works
|
|
134
|
+
with either identity form. The server validates the full legacy TOML; normal
|
|
135
|
+
inline documentation needs no license, compiler or formatter settings.
|
|
136
|
+
|
|
137
|
+
Saved files are read and validated during registration. Automatic models are
|
|
138
|
+
initialized at the end of mount. Missing files or generators, actual generator
|
|
139
|
+
version mismatches, unknown fields, conflicting identities and invalid schemas
|
|
140
|
+
fail startup. Mounting also rejects unavailable API versions and mismatched Rust
|
|
141
|
+
snapshot versions. Failed mounting publishes no profiles and leaves no usable
|
|
142
|
+
server application. An older explicitly pinned generator can serve a suitable
|
|
143
|
+
legacy file profile, but automatic/inline Rust mode requires the new factory
|
|
144
|
+
interface and never guesses a fallback.
|
|
145
|
+
|
|
146
|
+
A saved SDK can still serve examples after unrelated live additions. Each sample
|
|
147
|
+
must match its saved route, layout and supplied parameter contracts; unavailable
|
|
148
|
+
or stale samples show a limitation. Hidden actions, fields, defaults and samples
|
|
149
|
+
remain hidden from the current user. The server never fetches descriptions,
|
|
150
|
+
regenerates packages or runs a compiler/formatter during an HTML request.
|
|
151
|
+
|
|
152
|
+
Old profile settings work on the new server. Inline settings require the new
|
|
153
|
+
server code; rolling back that code also requires restoring compatible settings.
|
|
154
|
+
Mixed server workers can show different documentation examples without changing
|
|
155
|
+
wire requests or persisted data.
|
|
156
|
+
|
|
157
|
+
## Renderer extensions
|
|
158
|
+
|
|
159
|
+
Existing renderer positional constructors and `init`, `auth(method, desc)` and
|
|
160
|
+
`example(sample)` or `request(sample)`/`response(sample)` methods still work.
|
|
161
|
+
The registry binds a request-local `HaveAPI::ClientExample::Context` through
|
|
162
|
+
`with_context`. It holds the selected version, URLs including the API mount,
|
|
163
|
+
the filtered description, and the current resource, action and sample.
|
|
164
|
+
`context.with(...)` creates another frozen copy without freezing the server's
|
|
165
|
+
shared description.
|
|
166
|
+
|
|
167
|
+
Renderers may return a String or nil through their existing methods. New
|
|
168
|
+
renderers return either `{status: :code, code: source}` or
|
|
169
|
+
`{status: :limitation, reason: text}`. Source and limitation text are plain
|
|
170
|
+
strings, escaped once by the templates. A stable class `id` identifies a
|
|
171
|
+
variant; `code` selects its syntax language. `available?(context)` controls the
|
|
172
|
+
overview and every panel before rendering.
|
|
173
|
+
|
|
174
|
+
Generated-client adapters belong to their generator gems and consume context
|
|
175
|
+
readers without depending on the server gem. They resolve names using the
|
|
176
|
+
complete private generator model and never run a compiler, formatter or generator
|
|
177
|
+
in an HTML request. Keep adapter rendering read-only: configured adapters are frozen
|
|
178
|
+
and shared, while filtered contexts are copied per request.
|
|
179
|
+
|
|
180
|
+
## Verification
|
|
181
|
+
|
|
182
|
+
`make test-documentation` checks renderers, contexts, profiles, rendered HTML and
|
|
183
|
+
local assets. Prepare the component bundles and JavaScript development
|
|
184
|
+
dependencies with `make release-deps` first. The target builds the JavaScript
|
|
185
|
+
package before compiling TypeScript examples against its public declarations;
|
|
186
|
+
the Ruby server CI uses the same order.
|
|
187
|
+
|
|
188
|
+
`make test-documentation-python` additionally checks the Python renderer limits
|
|
189
|
+
against the real request encoder. It requires the locked Python environment
|
|
190
|
+
prepared by `make release-deps` and is included in `make test`. The ordinary
|
|
191
|
+
Ruby renderer checks run without the Python package environment.
|
|
192
|
+
|
|
193
|
+
Each client also has acceptance commands that check executable snippets against
|
|
194
|
+
installed packages or a generated SDK. These use
|
|
195
|
+
rendered source against disposable fixtures; syntax checks alone do not
|
|
196
|
+
establish that client calls or authentication work. See each client's
|
|
197
|
+
development documentation for its test environment. Highlighter rebuilding is
|
|
198
|
+
described in [utils/highlight](../utils/highlight/README.md).
|
data/haveapi.gemspec
CHANGED
|
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
|
|
|
15
15
|
s.required_ruby_version = ">= #{File.read('../../.ruby-version').strip}"
|
|
16
16
|
|
|
17
17
|
s.add_dependency 'activesupport', '>= 7.1'
|
|
18
|
-
s.add_dependency 'haveapi-client', '~> 0.
|
|
18
|
+
s.add_dependency 'haveapi-client', '~> 0.30.1'
|
|
19
19
|
s.add_dependency 'i18n', '>= 1.6', '< 2'
|
|
20
20
|
s.add_dependency 'json'
|
|
21
21
|
s.add_dependency 'mail'
|
|
@@ -33,6 +33,47 @@ module HaveAPI
|
|
|
33
33
|
@order
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
def id(value = nil)
|
|
37
|
+
@id = value.to_s.freeze unless value.nil?
|
|
38
|
+
return @id if @id
|
|
39
|
+
|
|
40
|
+
# Preserve the anchors of the retained built-in formats.
|
|
41
|
+
{ 'RubyClient' => '0', 'PhpClient' => '2', 'RubyCli' => '3', 'Curl' => '5', 'Http' => '6' }[name.to_s.split('::').last] ||
|
|
42
|
+
name.to_s.gsub('::', '-').downcase
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def available?(_context)
|
|
46
|
+
true
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def render(kind, context:, auth_method: nil, auth_description: nil)
|
|
50
|
+
arguments = [context.host, context.base_url, context.version]
|
|
51
|
+
arguments.push(context.resource_path, context.resource, context.action_name, context.action) if kind == :example
|
|
52
|
+
renderer = new(*arguments)
|
|
53
|
+
renderer.with_context(context)
|
|
54
|
+
result = case kind
|
|
55
|
+
when :init then renderer.init
|
|
56
|
+
when :auth then renderer.auth(auth_method, auth_description)
|
|
57
|
+
when :example
|
|
58
|
+
if renderer.respond_to?(:example)
|
|
59
|
+
renderer.example(context.sample)
|
|
60
|
+
else
|
|
61
|
+
[renderer.request(context.sample), renderer.response(context.sample)].compact.join("\n\n")
|
|
62
|
+
end
|
|
63
|
+
else raise ArgumentError, "Unknown client example kind: #{kind}"
|
|
64
|
+
end
|
|
65
|
+
return if result.nil?
|
|
66
|
+
return { status: :code, code: result.dup.freeze }.freeze if result.is_a?(String)
|
|
67
|
+
|
|
68
|
+
if result.is_a?(Hash) && result[:status] == :code && result[:code].is_a?(String)
|
|
69
|
+
{ status: :code, code: result[:code].dup.freeze }.freeze
|
|
70
|
+
elsif result.is_a?(Hash) && result[:status] == :limitation && result[:reason].is_a?(String)
|
|
71
|
+
{ status: :limitation, reason: result[:reason].dup.freeze }.freeze
|
|
72
|
+
else
|
|
73
|
+
raise ArgumentError, 'Invalid client example result'
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
36
77
|
def register(klass)
|
|
37
78
|
@clients ||= []
|
|
38
79
|
@clients << klass
|
|
@@ -59,7 +100,7 @@ module HaveAPI
|
|
|
59
100
|
end
|
|
60
101
|
end
|
|
61
102
|
|
|
62
|
-
attr_reader :resource_path, :resource, :action_name, :action, :host, :base_url, :version
|
|
103
|
+
attr_reader :resource_path, :resource, :action_name, :action, :host, :base_url, :version, :context
|
|
63
104
|
|
|
64
105
|
def initialize(host, base_url, version, *args)
|
|
65
106
|
@host = host
|
|
@@ -72,6 +113,11 @@ module HaveAPI
|
|
|
72
113
|
|
|
73
114
|
def auth(method, desc); end
|
|
74
115
|
|
|
116
|
+
def with_context(value)
|
|
117
|
+
@context = value
|
|
118
|
+
self
|
|
119
|
+
end
|
|
120
|
+
|
|
75
121
|
def version_url
|
|
76
122
|
File.join(base_url, "v#{version}", '/')
|
|
77
123
|
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'haveapi/client_example'
|
|
3
|
+
|
|
4
|
+
module HaveAPI::ClientExamples
|
|
5
|
+
module DynamicHelpers
|
|
6
|
+
class Unsupported < StandardError; end
|
|
7
|
+
|
|
8
|
+
def field(value, name, default = nil)
|
|
9
|
+
return default unless value.is_a?(Hash)
|
|
10
|
+
|
|
11
|
+
value.fetch(name) { value.fetch(name.to_s, default) }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def code_result
|
|
15
|
+
{ status: :code, code: yield.freeze }.freeze
|
|
16
|
+
rescue Unsupported, JSON::GeneratorError => e
|
|
17
|
+
{ status: :limitation, reason: e.message.freeze }.freeze
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def limitation(reason)
|
|
21
|
+
{ status: :limitation, reason: reason.freeze }.freeze
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def api_base
|
|
25
|
+
context ? context.base_url : base_url
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def api_version
|
|
29
|
+
context ? context.version : version.to_s
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def current_action
|
|
33
|
+
context&.action || action
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def current_resource_path
|
|
37
|
+
context&.resource_path || resource_path
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def current_action_name
|
|
41
|
+
context&.action_name || action_name
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def json_data(value)
|
|
45
|
+
case value
|
|
46
|
+
when Hash
|
|
47
|
+
result = {}
|
|
48
|
+
value.each do |key, entry|
|
|
49
|
+
unless key.is_a?(String) || key.is_a?(Symbol)
|
|
50
|
+
raise Unsupported, 'This sample has a non-string object key.'
|
|
51
|
+
end
|
|
52
|
+
raise Unsupported, 'This sample has duplicate serialized object keys.' if result.has_key?(key.to_s)
|
|
53
|
+
|
|
54
|
+
result[key.to_s] = json_data(entry)
|
|
55
|
+
end
|
|
56
|
+
result
|
|
57
|
+
when Array
|
|
58
|
+
value.map { |entry| json_data(entry) }
|
|
59
|
+
when String, Integer, TrueClass, FalseClass, NilClass
|
|
60
|
+
value
|
|
61
|
+
when Float
|
|
62
|
+
raise Unsupported, 'This sample has a non-finite number.' unless value.finite?
|
|
63
|
+
|
|
64
|
+
value
|
|
65
|
+
else
|
|
66
|
+
raise Unsupported, 'This sample contains a value without a supported literal representation.'
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def structured_sample(sample)
|
|
71
|
+
unless sample.is_a?(Hash) && (!field(sample, :request).nil? || !field(sample, :response).nil?)
|
|
72
|
+
raise Unsupported, 'This example contains prose only; no executable request is supplied.'
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
%i[global object].each do |scope|
|
|
76
|
+
next if scope == :object && field(current_action, :input).nil?
|
|
77
|
+
|
|
78
|
+
input = field(field(field(current_action, :meta), scope), :input)
|
|
79
|
+
if field(input, :parameters, {}).values.any? { |parameter| field(parameter, :required) }
|
|
80
|
+
raise Unsupported, 'This action requires input metadata that the sample format cannot supply.'
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
placeholders = field(current_action, :path, '').scan(/\{([a-zA-Z0-9_-]+)\}/).flatten
|
|
85
|
+
values = field(sample, :path_params, []) || []
|
|
86
|
+
unless values.is_a?(Array) && values.length == placeholders.length
|
|
87
|
+
raise Unsupported, 'The sample does not supply every action path argument.'
|
|
88
|
+
end
|
|
89
|
+
unless values.all? { |value| value.is_a?(String) || value.is_a?(Integer) }
|
|
90
|
+
raise Unsupported, 'Action path arguments must be strings or integers.'
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
request = field(sample, :request)
|
|
94
|
+
input = field(current_action, :input)
|
|
95
|
+
if !request.nil? && input.nil? && request != {}
|
|
96
|
+
raise Unsupported, 'The sample supplies input for an action without an input description.'
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
if request.is_a?(Hash) && input
|
|
100
|
+
known = field(input, :parameters, {}).keys.map(&:to_s)
|
|
101
|
+
unless (request.keys.map(&:to_s) - known).empty?
|
|
102
|
+
raise Unsupported, 'The sample contains input parameters absent from the visible description.'
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
[placeholders.zip(values).to_h, json_data(request)]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def supported_auth?(method)
|
|
109
|
+
%i[basic token oauth2].include?(method.to_sym) &&
|
|
110
|
+
(!context || field(context.description, :authentication, {}).keys.map(&:to_s).include?(method.to_s))
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def token_request_fields(description)
|
|
114
|
+
token = field(field(description, :resources, {}), :token, {})
|
|
115
|
+
request = field(field(token, :actions, {}), :request, {})
|
|
116
|
+
field(field(request, :input, {}), :parameters, {})
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def token_login_fields(description)
|
|
120
|
+
token_request_fields(description).reject do |name, _|
|
|
121
|
+
%w[token lifetime interval].include?(name.to_s)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def indent(source, spaces = 2)
|
|
126
|
+
source.lines.map { |line| line.strip.empty? ? line : "#{' ' * spaces}#{line}" }.join
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'haveapi/client_example'
|
|
2
|
+
|
|
3
|
+
module HaveAPI::ClientExamples
|
|
4
|
+
class GoClient < HaveAPI::ClientExample
|
|
5
|
+
label 'Go'
|
|
6
|
+
id 'go'
|
|
7
|
+
code :go
|
|
8
|
+
order 16
|
|
9
|
+
|
|
10
|
+
def self.available?(context)
|
|
11
|
+
context.profile && context.profile.language == :go
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def init
|
|
15
|
+
render_documentation(:init)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def auth(method, desc)
|
|
19
|
+
render_documentation(:auth, auth_method: method, auth_description: desc)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def example(sample)
|
|
23
|
+
render_documentation(:example, sample:)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
protected
|
|
27
|
+
|
|
28
|
+
def render_documentation(kind, sample: nil, **)
|
|
29
|
+
unless context && self.class.available?(context)
|
|
30
|
+
return { status: :limitation, reason: 'A Go SDK profile is required.' }.freeze
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
selected = kind == :example ? context.with(sample:) : context
|
|
34
|
+
context.profile.adapter.render(kind:, context: selected, mode: :sync, **)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|