Skip to content

Commit 4927dd4

Browse files
author
Daniel Jackson
committed
v2 begin
Signed-off-by: Daniel Jackson <[email protected]>
1 parent 5a04083 commit 4927dd4

File tree

5 files changed

+230
-25
lines changed

5 files changed

+230
-25
lines changed

_api-reference/cat/cat-allocation.md

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,26 +28,37 @@ GET /_cat/allocation/{node_id}
2828
<!-- spec_insert_end -->
2929

3030
<!-- spec_insert_start
31-
api: cat.allocation
31+
api: snapshot.restore
3232
component: example_code
33-
query_params: v
33+
rest: GET _nodes/stats/indices
3434
-->
3535
{% capture step1_rest %}
36-
GET /_cat/allocation?v
36+
POST _snapshot/<repository>/<snapshot>/_restore
3737
{% endcapture %}
3838

3939
{% capture step1_python %}
40-
response = client.cat.allocation(v: true)
40+
response = client.snapshot.restore(
41+
repository = "<repository>",
42+
snapshot = "<snapshot>",
43+
body = {
44+
"indices": "opendistro-reports-definitions",
45+
"ignore_unavailable": true,
46+
"include_global_state": false,
47+
"rename_pattern": "(.+)",
48+
"rename_replacement": "$1_restored",
49+
"include_aliases": false
50+
}
51+
)
52+
4153
{% endcapture %}
4254

4355
{% capture step1_javascript %}
44-
// TODO: add JS client call for cat.allocation
56+
JavaScript example code not yet implemented
4557
{% endcapture %}
4658

4759
{% include code-block.html
48-
rest=step1_rest
49-
python=step1_python
50-
javascript=step1_javascript
60+
rest=step1_rest
61+
python=step1_python
5162
%}
5263
<!-- spec_insert_end -->
5364

spec-insert/lib/insert_arguments.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def initialize(args)
1717
def self.from_marker(lines)
1818
end_index = lines.each_with_index.find { |line, _index| line.match?(/^\s*-->/) }&.last&.- 1
1919
args = lines[1..end_index].filter { |line| line.include?(':') }.to_h do |line|
20-
key, value = line.split(':')
20+
key, value = line.split(':',2)
2121
[key.strip, value.strip]
2222
end
2323
new(args)

spec-insert/lib/jekyll-spec-insert.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ def self.process_file(file, fail_on_error: false)
3030
raise e if fail_on_error
3131
relative_path = Pathname(file).relative_path_from(Pathname.new(Dir.pwd))
3232
Jekyll.logger.error "Error processing #{relative_path}: #{e.message}"
33+
Jekyll.logger.error "Error backtrace: #{e.backtrace.join("\n")}"
3334
end
3435

3536
def self.watch(fail_on_error: false)
@@ -43,4 +44,4 @@ def self.watch(fail_on_error: false)
4344
trap('TERM') { exit }
4445
sleep
4546
end
46-
end
47+
end
Lines changed: 206 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,225 @@
11
# frozen_string_literal: true
22

3+
require 'json'
4+
35
class ExampleCode < BaseMustacheRenderer
46
self.template_file = "#{__dir__}/templates/example_code.mustache"
57

68
def initialize(action, args)
79
super(action, args)
810
end
911

10-
def query_params
11-
@args.raw['query_params']&.split(',')&.map(&:strip) || []
12+
# Resolves the correct OpenSearch client method call
13+
def client_method_call
14+
segments = @action.full_name.to_s.split('.')
15+
return "client" if segments.empty?
16+
17+
if segments.size == 1
18+
"client.#{segments.first}"
19+
else
20+
"client.#{se gments.first}.#{segments[1]}"
21+
end
22+
end
23+
24+
def rest_lines
25+
@args.raw['rest']&.split("\n")&.map(&:strip) || []
1226
end
1327

1428
def rest_code
15-
method = @action.http_verbs.first.upcase rescue 'GET'
16-
path = @action.urls.first rescue '/'
17-
query = query_params
18-
query_string = query.any? ? "?#{query.join('&')}" : ""
19-
"#{method} #{path}#{query_string}"
29+
rest_lines.join("\n")
2030
end
2131

22-
def python_code
23-
query = query_params
24-
args = query.map { |k| "#{k}: true" }.join(', ')
25-
"response = client.#{@action.full_name}(#{args})"
32+
# Uses the declared HTTP method in the OpenAPI spec
33+
def http_method
34+
@action.http_verbs.first&.upcase || "GET"
2635
end
2736

37+
# Converts OpenAPI-style path (/index/{id}) into Ruby-style interpolation (/index/#{id})
38+
def path_only
39+
url = @action.urls.first
40+
return '' unless url
41+
url.gsub(/\{(\w+)\}/, '#{\1}')
42+
end
2843
def javascript_code
29-
"// TODO: add JS client call for #{@action.full_name}"
44+
"JavaScript example code not yet implemented"
45+
end
46+
# Assembles a query string from the declared query parameters
47+
def query_string
48+
return '' if @action.query_parameters.empty?
49+
@action.query_parameters.map { |param| "#{param.name}=example" }.join('&')
50+
end
51+
52+
# Combines path and query string for display
53+
def path_with_query
54+
qs = query_string
55+
qs.empty? ? path_only : "#{path_only}?#{qs}"
56+
end
57+
58+
# Hash version of query params
59+
def query_params
60+
@action.query_parameters.to_h { |param| [param.name, "example"] }
61+
end
62+
63+
# Parses the body from the REST example (only for preserving raw formatting)
64+
def body
65+
body_lines = rest_lines[1..]
66+
return nil if body_lines.empty?
67+
begin
68+
JSON.parse(body_lines.join("\n"))
69+
rescue
70+
nil
71+
end
72+
end
73+
74+
def action_expects_body?(verb)
75+
verb = verb.downcase
76+
@action.operations.any? do |op|
77+
op.http_verb.to_s.downcase == verb &&
78+
op.spec&.requestBody &&
79+
op.spec.requestBody.respond_to?(:content)
80+
end
81+
end
82+
83+
def matching_spec_path
84+
return @matching_spec_path if defined?(@matching_spec_path)
85+
86+
# Extract raw request path from rest line
87+
raw_line = rest_lines.first.to_s
88+
_, request_path = raw_line.split
89+
request_segments = request_path.split('?').first.split('/').reject(&:empty?)
90+
91+
# Choose the best matching spec URL
92+
best = nil
93+
best_score = -1
94+
95+
@action.urls.each do |spec_path|
96+
spec_segments = spec_path.split('/').reject(&:empty?)
97+
next unless spec_segments.size == request_segments.size
98+
99+
score = 0
100+
spec_segments.each_with_index do |seg, i|
101+
if seg.start_with?('{')
102+
score += 1 # parameter match
103+
elsif seg == request_segments[i]
104+
score += 2 # exact match
105+
else
106+
score = -1
107+
break
108+
end
109+
end
110+
111+
if score > best_score
112+
best = spec_path
113+
best_score = score
114+
end
115+
end
116+
117+
@matching_spec_path = best
118+
end
119+
120+
# Final Python code using action metadata
121+
def python_code
122+
return "# Invalid action" unless @action&.full_name
123+
124+
client_setup = <<~PYTHON
125+
from opensearchpy import OpenSearch
126+
127+
host = 'localhost'
128+
port = 9200
129+
auth = ('admin', 'admin') # For testing only. Don't store credentials in code.
130+
ca_certs_path = '/full/path/to/root-ca.pem' # Provide a CA bundle if you use intermediate CAs with your root CA.
131+
132+
# Create the client with SSL/TLS enabled, but hostname verification disabled.
133+
client = OpenSearch(
134+
hosts = [{'host': host, 'port': port}],
135+
http_compress = True, # enables gzip compression for request bodies
136+
http_auth = auth,
137+
use_ssl = True,
138+
verify_certs = True,
139+
ssl_assert_hostname = False,
140+
ssl_show_warn = False,
141+
ca_certs = ca_certs_path
142+
)
143+
144+
PYTHON
145+
146+
if @args.raw['body'] == '{"hello"}'
147+
puts "# This is a debug example"
148+
end
149+
150+
namespace, method = @action.full_name.split('.')
151+
client_call = "client"
152+
client_call += ".#{namespace}" if namespace
153+
client_call += ".#{method}"
154+
155+
args = []
156+
157+
# Extract actual path and query from the first line of the REST input
158+
raw_line = rest_lines.first.to_s
159+
http_verb, full_path = raw_line.split
160+
path_part, query_string = full_path.to_s.split('?', 2)
161+
162+
# Extract used path values from the path part
163+
path_values = path_part.split('/').reject(&:empty?)
164+
165+
# Match spec path (e.g. /_cat/aliases/{name}) to determine which param this value belongs to
166+
spec_path = matching_spec_path.to_s
167+
spec_parts = spec_path.split('/').reject(&:empty?)
168+
169+
param_mapping = {}
170+
spec_parts.each_with_index do |part, i|
171+
if part =~ /\{(.+?)\}/ && path_values[i]
172+
param_mapping[$1] = path_values[i]
173+
end
174+
end
175+
176+
# Add path parameters if they were present in the example
177+
@action.path_parameters.each do |param|
178+
if param_mapping.key?(param.name)
179+
args << "#{param.name} = \"#{param_mapping[param.name]}\""
180+
end
181+
end
182+
183+
# Add query parameters from query string
184+
if query_string
185+
query_pairs = query_string.split('&').map { |s| s.split('=', 2) }
186+
query_hash = query_pairs.map do |k, v|
187+
"#{k}: #{v ? "\"#{v}\"" : "True"}"
188+
end.join(', ')
189+
args << "params = { #{query_hash} }" unless query_hash.empty?
190+
end
191+
192+
# Add body if spec allows it AND it's present in REST
193+
if action_expects_body?(http_verb)
194+
if @args.raw['body']
195+
begin
196+
parsed = JSON.parse(@args.raw['body'])
197+
pretty = JSON.pretty_generate(parsed).gsub(/^/, ' ')
198+
args << "body = #{pretty}"
199+
rescue JSON::ParserError
200+
args << "body = #{JSON.dump(@args.raw['body'])}"
201+
end
202+
else
203+
args << 'body = { "Insert body here" }'
204+
end
205+
end
206+
207+
# Final result
208+
call_code = if args.empty?
209+
"response = #{client_call}()"
210+
else
211+
final_args = args.map { |line| " #{line}" }.join(",\n")
212+
<<~PYTHON
213+
response = #{client_call}(
214+
#{final_args}
215+
)
216+
PYTHON
217+
end
218+
# Prepend client if requested
219+
if @args.raw['include_client_setup']
220+
client_setup + call_code
221+
else
222+
call_code
223+
end
30224
end
31225
end

spec-insert/lib/renderers/templates/example_code.mustache

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
{% endcapture %}
1212

1313
{% include code-block.html
14-
rest=step1_rest
15-
python=step1_python
16-
javascript=step1_javascript
14+
rest=step1_rest
15+
python=step1_python
1716
%}

0 commit comments

Comments
 (0)