feat: generate SDK for Remnawave API v2.7.4
This commit is contained in:
Executable
+234
@@ -0,0 +1,234 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Final client_ext.go generator that actually works.
|
||||
Reads api-2-2-2-consolidated.json and oas_client_gen.go
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
|
||||
print("=" * 70)
|
||||
print("CLIENT_EXT.GO GENERATOR")
|
||||
print("=" * 70)
|
||||
|
||||
# Step 1: Parse oas_client_gen.go for method signatures
|
||||
print("\n[1/4] Parsing oas_client_gen.go...")
|
||||
with open('api/oas_client_gen.go', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# Extract method signatures more carefully
|
||||
methods = {}
|
||||
# Match: func (c *Client) MethodName(ctx context.Context, ...) (...) {
|
||||
pattern = r'func \(c \*Client\) (\w+)\((ctx context\.Context(?:,\s*[^)]+)?)\)\s*\(([^)]+)\)'
|
||||
|
||||
for match in re.finditer(pattern, content, re.MULTILINE):
|
||||
method_name = match.group(1)
|
||||
if method_name in ['requestURL', 'sendApiTokensControllerCreate']: # Skip internal
|
||||
continue
|
||||
if method_name.startswith('send'):
|
||||
continue
|
||||
|
||||
full_params = match.group(2) # "ctx context.Context, request *Type, params ParamsType"
|
||||
returns = match.group(3) # "TypeRes, error"
|
||||
|
||||
# Parse params (skip ctx)
|
||||
params_list = []
|
||||
if ', ' in full_params:
|
||||
params_str = full_params.split(', ', 1)[1] # Remove "ctx context.Context"
|
||||
# Split remaining params carefully
|
||||
for param in re.findall(r'(\w+)\s+([\*\w\.]+)', params_str):
|
||||
params_list.append((param[0], param[1]))
|
||||
|
||||
# Parse returns
|
||||
returns_list = [r.strip() for r in returns.split(',')]
|
||||
|
||||
methods[method_name] = {
|
||||
'params': params_list,
|
||||
'returns': returns_list
|
||||
}
|
||||
|
||||
print(f" ✓ Found {len(methods)} client methods")
|
||||
|
||||
# Step 2: Parse api-2-2-2-consolidated.json for operations
|
||||
print("\n[2/4] Parsing api-2-2-2-consolidated.json...")
|
||||
with open('api-2-2-2-consolidated.json', 'r') as f:
|
||||
spec = json.load(f)
|
||||
|
||||
operations_by_controller = {}
|
||||
|
||||
for path, path_item in spec.get('paths', {}).items():
|
||||
for http_method, op_spec in path_item.items():
|
||||
if http_method not in ['get', 'post', 'put', 'patch', 'delete']:
|
||||
continue
|
||||
|
||||
op_id = op_spec.get('operationId')
|
||||
if not op_id or '_' not in op_id:
|
||||
continue
|
||||
|
||||
# Parse: "ApiTokensController_create" -> controller="ApiTokensController", method="create"
|
||||
parts = op_id.split('_', 1)
|
||||
controller_full = parts[0] # e.g., "ApiTokensController"
|
||||
method_snake = parts[1] # e.g., "create"
|
||||
|
||||
# Controller name without "Controller" suffix
|
||||
controller = controller_full.replace('Controller', '')
|
||||
|
||||
# Convert method to PascalCase: findAll -> FindAll, get_status -> GetStatus
|
||||
# Just capitalize first letter of each word, preserve rest
|
||||
def to_pascal(s):
|
||||
if not s:
|
||||
return s
|
||||
# Capitalize first letter, keep rest as-is
|
||||
return s[0].upper() + s[1:]
|
||||
|
||||
# Split by underscore and capitalize each part
|
||||
parts = method_snake.split('_')
|
||||
method_pascal = ''.join(to_pascal(p) for p in parts)
|
||||
|
||||
# The actual Go method name in oas_client_gen.go
|
||||
go_method = controller_full + method_pascal # e.g., "ApiTokensControllerCreate"
|
||||
|
||||
if controller not in operations_by_controller:
|
||||
operations_by_controller[controller] = []
|
||||
|
||||
operations_by_controller[controller].append({
|
||||
'operationId': op_id,
|
||||
'goMethod': go_method,
|
||||
'displayMethod': method_pascal
|
||||
})
|
||||
|
||||
total_ops = sum(len(ops) for ops in operations_by_controller.values())
|
||||
print(f" ✓ Found {total_ops} operations in {len(operations_by_controller)} controllers")
|
||||
|
||||
# Step 3: Generate code
|
||||
print("\n[3/4] Generating code...")
|
||||
|
||||
def to_camel(s):
|
||||
"""PascalCase -> camelCase"""
|
||||
return s[0].lower() + s[1:] if s else s
|
||||
|
||||
code = '''// Code generated by generate_clientext_final.py. DO NOT EDIT manually.
|
||||
// Generated from api-2-2-2-consolidated.json with renamed schemas.
|
||||
|
||||
package api
|
||||
|
||||
import "context"
|
||||
|
||||
// ClientExt wraps the base Client with organized sub-client access.
|
||||
type ClientExt struct {
|
||||
\t*Client
|
||||
'''
|
||||
|
||||
# Add fields for each controller
|
||||
for controller in sorted(operations_by_controller.keys()):
|
||||
field_name = to_camel(controller)
|
||||
code += f'\t{field_name} *{controller}Client\n'
|
||||
|
||||
code += '''}
|
||||
|
||||
// NewClientExt creates a new ClientExt wrapper.
|
||||
func NewClientExt(client *Client) *ClientExt {
|
||||
\treturn &ClientExt{
|
||||
\t\tClient: client,
|
||||
'''
|
||||
|
||||
# Initialize fields
|
||||
for controller in sorted(operations_by_controller.keys()):
|
||||
field_name = to_camel(controller)
|
||||
code += f'\t\t{field_name}: New{controller}Client(client),\n'
|
||||
|
||||
code += '''\t}
|
||||
}
|
||||
|
||||
'''
|
||||
|
||||
# Accessor methods
|
||||
for controller in sorted(operations_by_controller.keys()):
|
||||
field_name = to_camel(controller)
|
||||
code += f'''// {controller} returns the {controller}Client.
|
||||
func (ce *ClientExt) {controller}() *{controller}Client {{
|
||||
\treturn ce.{field_name}
|
||||
}}
|
||||
|
||||
'''
|
||||
|
||||
# Sub-client types and methods
|
||||
for controller in sorted(operations_by_controller.keys()):
|
||||
code += f'''// {controller}Client provides {controller} operations.
|
||||
type {controller}Client struct {{
|
||||
\tclient *Client
|
||||
}}
|
||||
|
||||
// New{controller}Client creates a new {controller}Client.
|
||||
func New{controller}Client(client *Client) *{controller}Client {{
|
||||
\treturn &{controller}Client{{client: client}}
|
||||
}}
|
||||
|
||||
'''
|
||||
|
||||
# Generate methods for this controller
|
||||
for op in sorted(operations_by_controller[controller], key=lambda x: x['goMethod']):
|
||||
go_method = op['goMethod']
|
||||
display_method = op['displayMethod']
|
||||
op_id = op['operationId']
|
||||
|
||||
if go_method not in methods:
|
||||
print(f" ⚠ Warning: {go_method} not found in oas_client_gen.go")
|
||||
continue
|
||||
|
||||
method_info = methods[go_method]
|
||||
params = method_info['params']
|
||||
returns = method_info['returns']
|
||||
|
||||
# Build parameter list
|
||||
if params:
|
||||
params_sig = ', '.join([f'{p[0]} {p[1]}' for p in params])
|
||||
params_call = ', '.join([p[0] for p in params])
|
||||
else:
|
||||
params_sig = ''
|
||||
params_call = ''
|
||||
|
||||
# Build return type
|
||||
if returns:
|
||||
ret_type = ', '.join(returns)
|
||||
if len(returns) > 1:
|
||||
ret_type = f'({ret_type})'
|
||||
else:
|
||||
ret_type = ''
|
||||
|
||||
# Generate method
|
||||
code += f'''// {display_method} calls {op_id}.
|
||||
func (sc *{controller}Client) {display_method}(ctx context.Context'''
|
||||
|
||||
if params_sig:
|
||||
code += f', {params_sig}'
|
||||
|
||||
code += ')'
|
||||
|
||||
if ret_type:
|
||||
code += f' {ret_type}'
|
||||
|
||||
code += ' {\n'
|
||||
|
||||
if returns:
|
||||
code += f'\treturn sc.client.{go_method}(ctx'
|
||||
else:
|
||||
code += f'\tsc.client.{go_method}(ctx'
|
||||
|
||||
if params_call:
|
||||
code += f', {params_call}'
|
||||
|
||||
code += ')\n}\n\n'
|
||||
|
||||
# Step 4: Write to file
|
||||
print("\n[4/4] Writing api/client_ext.go...")
|
||||
with open('api/client_ext.go', 'w') as f:
|
||||
f.write(code)
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print(f"✅ SUCCESS!")
|
||||
print("=" * 70)
|
||||
print(f" Controllers: {len(operations_by_controller)}")
|
||||
print(f" Operations: {total_ops}")
|
||||
print(f" File: api/client_ext.go")
|
||||
print(f" Uses: api-2-2-2-consolidated.json (renamed schemas)")
|
||||
print("=" * 70)
|
||||
Reference in New Issue
Block a user