add openapi interface and interface tests generation
This commit is contained in:
@@ -2,9 +2,7 @@ module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code { Folder, IFile, VFile, CodeItem, File, Function, Import, Module, Struct, CustomCode }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification, ActorInterface}
|
||||
import os
|
||||
import json
|
||||
|
||||
@[params]
|
||||
@@ -19,6 +17,7 @@ pub fn generate_actor_module(spec ActorSpecification, params Params) !Module {
|
||||
files = [
|
||||
generate_readme_file(spec)!,
|
||||
generate_actor_file(spec)!,
|
||||
generate_specs_file(spec.name, params.interfaces)!,
|
||||
generate_actor_test_file(spec)!,
|
||||
generate_handle_file(spec)!,
|
||||
generate_methods_file(spec)!
|
||||
@@ -38,12 +37,31 @@ pub fn generate_actor_module(spec ActorSpecification, params Params) !Module {
|
||||
// generate openrpc code files
|
||||
// files << generate_openrpc_client_file(openrpc_spec)!
|
||||
// files << generate_openrpc_client_test_file(openrpc_spec)!
|
||||
files << generate_http_interface_file()!
|
||||
files << generate_openrpc_interface_file()!
|
||||
iface_file, iface_test_file := generate_openrpc_interface_files()
|
||||
files << iface_file
|
||||
files << iface_test_file
|
||||
|
||||
// add openrpc.json to docs
|
||||
docs_files << generate_openrpc_file(openrpc_spec)!
|
||||
}
|
||||
.openapi {
|
||||
// convert actor spec to openrpc spec
|
||||
openapi_spec := spec.to_openapi()
|
||||
|
||||
// generate openrpc code files
|
||||
iface_file, iface_test_file := generate_openapi_interface_files()
|
||||
files << iface_file
|
||||
files << iface_test_file
|
||||
|
||||
// add openrpc.json to docs
|
||||
docs_files << generate_openapi_file(openapi_spec)!
|
||||
}
|
||||
.http {
|
||||
// generate openrpc code files
|
||||
iface_file, iface_test_file := generate_http_interface_files()
|
||||
files << iface_file
|
||||
files << iface_test_file
|
||||
}
|
||||
.command {
|
||||
files << generate_command_file(spec)!
|
||||
}
|
||||
@@ -53,6 +71,7 @@ pub fn generate_actor_module(spec ActorSpecification, params Params) !Module {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// folder with docs
|
||||
docs_folder := Folder {
|
||||
name: 'docs'
|
||||
@@ -64,7 +83,10 @@ pub fn generate_actor_module(spec ActorSpecification, params Params) !Module {
|
||||
return code.new_module(
|
||||
name: '${name_fixed}_actor'
|
||||
files: files
|
||||
folders: [docs_folder]
|
||||
folders: [
|
||||
docs_folder,
|
||||
generate_scripts_folder()
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -98,13 +120,15 @@ fn generate_actor_test_file(spec ActorSpecification) !VFile {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn generate_openapi_file(spec ActorSpecification) !File {
|
||||
openapi_spec := spec.to_openapi()
|
||||
openapi_json := json.encode(openapi_spec)
|
||||
return File{
|
||||
name: 'openapi'
|
||||
extension: 'json'
|
||||
content: openapi_json
|
||||
fn generate_specs_file(name string, interfaces []ActorInterface) !VFile {
|
||||
support_openrpc := ActorInterface.openrpc in interfaces
|
||||
support_openapi := ActorInterface.openapi in interfaces
|
||||
dollar := '$'
|
||||
actor_name_snake := texttools.name_fix_snake(name)
|
||||
actor_name_pascal := texttools.name_fix_snake_to_pascal(name)
|
||||
actor_code := $tmpl('./templates/specifications.v.template')
|
||||
return VFile {
|
||||
name: 'specifications'
|
||||
items: [CustomCode{actor_code}]
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code
|
||||
import freeflowuniverse.herolib.baobab.specification
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
import os
|
||||
@@ -240,3 +239,29 @@ fn test_generate_actor_module_with_openrpc_interface() {
|
||||
test: true
|
||||
)!
|
||||
}
|
||||
|
||||
fn test_generate_actor_module_with_openapi_interface() {
|
||||
// plain actor module without interfaces
|
||||
actor_module := generate_actor_module(actor_spec,
|
||||
interfaces: [.openapi]
|
||||
)!
|
||||
actor_module.write(destination,
|
||||
format: true
|
||||
overwrite: true
|
||||
compile: true
|
||||
test: true
|
||||
)!
|
||||
}
|
||||
|
||||
fn test_generate_actor_module_with_all_interfaces() {
|
||||
// plain actor module without interfaces
|
||||
actor_module := generate_actor_module(actor_spec,
|
||||
interfaces: [.openapi, .openrpc, .http]
|
||||
)!
|
||||
actor_module.write(destination,
|
||||
format: true
|
||||
overwrite: true
|
||||
compile: true
|
||||
test: true
|
||||
)!
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.schemas.openrpc {ContentDescriptor}
|
||||
import freeflowuniverse.herolib.schemas.jsonschema.codegen {schemaref_to_type}
|
||||
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification}
|
||||
import os
|
||||
import json
|
||||
|
||||
fn generate_handle_file(spec ActorSpecification) !VFile {
|
||||
mut items := []CodeItem{}
|
||||
|
||||
@@ -1,22 +1,39 @@
|
||||
module generator
|
||||
|
||||
import freeflowuniverse.herolib.core.code { Folder, IFile, VFile, CodeItem, File, Function, Import, Module, Struct, CustomCode }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
import freeflowuniverse.herolib.baobab.specification {ActorMethod, ActorSpecification}
|
||||
import os
|
||||
import json
|
||||
|
||||
fn generate_openrpc_interface_file() !VFile {
|
||||
return VFile {
|
||||
fn generate_openrpc_interface_files() (VFile, VFile) {
|
||||
iface_file := VFile {
|
||||
name: 'interface_openrpc'
|
||||
items: [CustomCode{$tmpl('./templates/interface_openrpc.v.template')}]
|
||||
}
|
||||
iface_test_file := VFile {
|
||||
name: 'interface_openrpc_test'
|
||||
items: [CustomCode{$tmpl('./templates/interface_openrpc_test.v.template')}]
|
||||
}
|
||||
return iface_file, iface_test_file
|
||||
}
|
||||
|
||||
fn generate_http_interface_file() !VFile {
|
||||
return VFile {
|
||||
fn generate_openapi_interface_files() (VFile, VFile) {
|
||||
iface_file := VFile {
|
||||
name: 'interface_openapi'
|
||||
items: [CustomCode{$tmpl('./templates/interface_openapi.v.template')}]
|
||||
}
|
||||
iface_test_file := VFile {
|
||||
name: 'interface_openapi_test'
|
||||
items: [CustomCode{$tmpl('./templates/interface_openapi_test.v.template')}]
|
||||
}
|
||||
return iface_file, iface_test_file
|
||||
}
|
||||
|
||||
fn generate_http_interface_files() (VFile, VFile) {
|
||||
iface_file := VFile {
|
||||
name: 'interface_http'
|
||||
items: [CustomCode{$tmpl('./templates/interface_http.v.template')}]
|
||||
}
|
||||
iface_test_file := VFile {
|
||||
name: 'interface_http_test'
|
||||
items: [CustomCode{$tmpl('./templates/interface_http_test.v.template')}]
|
||||
}
|
||||
return iface_file, iface_test_file
|
||||
}
|
||||
|
||||
15
lib/baobab/generator/generate_openapi.v
Normal file
15
lib/baobab/generator/generate_openapi.v
Normal file
@@ -0,0 +1,15 @@
|
||||
module generator
|
||||
|
||||
import json
|
||||
import freeflowuniverse.herolib.core.code { VFile, File, Function, Module, Struct }
|
||||
import freeflowuniverse.herolib.schemas.openapi { Components, OpenAPI }
|
||||
// import freeflowuniverse.herolib.schemas.openapi.codegen { generate_client_file, generate_client_test_file }
|
||||
|
||||
pub fn generate_openapi_file(specification OpenAPI) !File {
|
||||
openapi_json := specification.encode_json()
|
||||
return File{
|
||||
name: 'openapi'
|
||||
extension: 'json'
|
||||
content: openapi_json
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fn test_new_http_server() ! {
|
||||
new_http_server()!
|
||||
}
|
||||
|
||||
fn test_run_http_server() ! {
|
||||
spawn run_http_server()
|
||||
}
|
||||
17
lib/baobab/generator/templates/interface_openapi.v.template
Normal file
17
lib/baobab/generator/templates/interface_openapi.v.template
Normal file
@@ -0,0 +1,17 @@
|
||||
import freeflowuniverse.herolib.baobab.stage.interfaces
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
|
||||
pub fn new_openapi_interface() !&interfaces.OpenAPIInterface {
|
||||
// create OpenAPI Handler with actor's client
|
||||
client := new_client()!
|
||||
return interfaces.new_openapi_interface(client.Client)
|
||||
}
|
||||
|
||||
// creates HTTP controller with the actor's OpenAPI Handler
|
||||
// and OpenAPI Specification
|
||||
pub fn new_openapi_http_controller() !&openapi.HTTPController {
|
||||
return openapi.new_http_controller(
|
||||
specification: openapi_specification
|
||||
handler: new_openapi_interface()!
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fn test_new_openapi_interface() ! {
|
||||
new_openapi_interface()!
|
||||
}
|
||||
|
||||
fn test_new_openapi_http_controller() ! {
|
||||
new_openapi_http_controller()!
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
import freeflowuniverse.herolib.baobab.stage.interfaces
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
|
||||
const specification_path = os.join_path(os.dir(@@FILE), '/testdata/openrpc.json')
|
||||
const specification = openrpc.new(path: specification_path)!
|
||||
|
||||
pub fn new_openrpc_interface() !&interfaces.OpenRPCInterface {
|
||||
// create OpenRPC Handler with actor's client
|
||||
client := new_client()!
|
||||
@@ -14,7 +11,7 @@ pub fn new_openrpc_interface() !&interfaces.OpenRPCInterface {
|
||||
// and OpenRPC Specification
|
||||
pub fn new_openrpc_http_controller() !&openrpc.HTTPController {
|
||||
return openrpc.new_http_controller(
|
||||
specification: specification
|
||||
specification: openrpc_specification
|
||||
handler: new_openrpc_interface()!
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
fn test_new_openrpc_interface() ! {
|
||||
new_openrpc_interface()!
|
||||
}
|
||||
|
||||
fn test_new_openrpc_http_controller() ! {
|
||||
new_openrpc_http_controller()!
|
||||
}
|
||||
14
lib/baobab/generator/templates/specifications.v.template
Normal file
14
lib/baobab/generator/templates/specifications.v.template
Normal file
@@ -0,0 +1,14 @@
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
import freeflowuniverse.herolib.schemas.openrpc
|
||||
import os
|
||||
|
||||
@if support_openrpc
|
||||
const openrpc_spec_path = '@{dollar}{os.dir(@@FILE)}/docs/openrpc.json'
|
||||
const openrpc_spec_json = os.read_file(openrpc_spec_path) or { panic(err) }
|
||||
const openrpc_specification = openrpc.decode(openrpc_spec_json)!
|
||||
@end
|
||||
@if support_openapi
|
||||
const openapi_spec_path = '@{dollar}{os.dir(@@FILE)}/docs/openapi.json'
|
||||
const openapi_spec_json = os.read_file(openapi_spec_path) or { panic(err) }
|
||||
const openapi_specification = openapi.json_decode(openapi_spec_json)!
|
||||
@end
|
||||
@@ -18,6 +18,7 @@ pub enum ActorInterface {
|
||||
openapi
|
||||
webui
|
||||
command
|
||||
http
|
||||
}
|
||||
|
||||
pub struct ActorMethod {
|
||||
|
||||
@@ -1,77 +1,48 @@
|
||||
module interfaces
|
||||
|
||||
import veb
|
||||
import freeflowuniverse.herolib.schemas.openapi { Context, Controller, OpenAPI, Request, Response }
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import x.json2
|
||||
import net.http
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
import rand
|
||||
import x.json2 as json {Any}
|
||||
import freeflowuniverse.herolib.baobab.stage {Action, Client}
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
import freeflowuniverse.herolib.schemas.openapi
|
||||
|
||||
// pub struct OpenAPIProxy {
|
||||
// pub:
|
||||
// client Client
|
||||
// specification OpenAPI
|
||||
// }
|
||||
pub struct OpenAPIInterface {
|
||||
pub mut:
|
||||
client Client
|
||||
}
|
||||
|
||||
// // creates and OpenAPI Proxy Controller
|
||||
// pub fn new_openapi_proxy(proxy OpenAPIProxy) OpenAPIProxy {
|
||||
// return proxy
|
||||
// }
|
||||
pub fn new_openapi_interface(client Client) &OpenAPIInterface {
|
||||
return &OpenAPIInterface{client}
|
||||
}
|
||||
|
||||
// // creates and OpenAPI Proxy Controller
|
||||
// pub fn (proxy OpenAPIProxy) controller() &Controller {
|
||||
// // Initialize the server
|
||||
// mut controller := &Controller{
|
||||
// specification: proxy.specification
|
||||
// handler: Handler{
|
||||
// client: proxy.client
|
||||
// }
|
||||
// }
|
||||
// return controller
|
||||
// }
|
||||
pub fn (mut i OpenAPIInterface) handle(request openapi.Request) !openapi.Response {
|
||||
// Convert incoming OpenAPI request to a procedure call
|
||||
action := action_from_openapi_request(request)
|
||||
response := i.client.call_to_action(action)!
|
||||
return action_to_openapi_response(response)
|
||||
}
|
||||
|
||||
// @[params]
|
||||
// pub struct RunParams {
|
||||
// pub:
|
||||
// port int = 8080
|
||||
// }
|
||||
pub fn action_from_openapi_request(request openapi.Request) Action {
|
||||
mut params := []Any{}
|
||||
if request.arguments.len > 0 {
|
||||
params << request.arguments.values()
|
||||
}
|
||||
if request.body != '' {
|
||||
params << request.body
|
||||
}
|
||||
if request.parameters.len > 0 {
|
||||
params << json.encode(request.parameters)
|
||||
}
|
||||
|
||||
// fn (proxy OpenAPIProxy) run(params RunParams) {
|
||||
// mut controller := proxy.controller()
|
||||
// veb.run[Controller, Context](mut controller, params.port)
|
||||
// }
|
||||
return Action {
|
||||
id: rand.uuid_v4()
|
||||
name: request.operation.operation_id
|
||||
params: json.encode(params.str())
|
||||
}
|
||||
}
|
||||
|
||||
// pub struct Handler {
|
||||
// pub mut:
|
||||
// client Client
|
||||
// }
|
||||
|
||||
// fn (mut handler Handler) handle(request Request) !Response {
|
||||
// // Convert incoming OpenAPI request to a procedure call
|
||||
// call := rpc.openapi_request_to_procedure_call(request)
|
||||
|
||||
// // Process the procedure call
|
||||
// procedure_response := handler.client.dialogue(call, Params{
|
||||
// timeout: 30 // Set timeout in seconds
|
||||
// }) or {
|
||||
// // Handle ProcedureError
|
||||
// if err is ProcedureError {
|
||||
// return Response{
|
||||
// status: http.status_from_int(err.code()) // Map ProcedureError reason to HTTP status code
|
||||
// body: json.encode({
|
||||
// 'error': err.msg()
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// return error('Unexpected error: ${err}')
|
||||
// }
|
||||
|
||||
// // Convert returned procedure response to OpenAPI response
|
||||
// return Response{
|
||||
// status: http.Status.ok // Assuming success if no error
|
||||
// body: procedure_response.result
|
||||
// }
|
||||
// }
|
||||
pub fn action_to_openapi_response(action Action) openapi.Response {
|
||||
return openapi.Response {
|
||||
body: action.result
|
||||
}
|
||||
}
|
||||
|
||||
220
lib/schemas/openapi/controller_http.v
Normal file
220
lib/schemas/openapi/controller_http.v
Normal file
@@ -0,0 +1,220 @@
|
||||
module openapi
|
||||
|
||||
import veb
|
||||
import freeflowuniverse.herolib.schemas.jsonschema {Schema}
|
||||
import x.json2 {Any}
|
||||
import net.http
|
||||
|
||||
pub struct HTTPController {
|
||||
Handler // Handles OpenAPI requests
|
||||
pub:
|
||||
specification OpenAPI
|
||||
}
|
||||
|
||||
pub struct Context {
|
||||
veb.Context
|
||||
}
|
||||
|
||||
// Creates a new HTTPController instance
|
||||
pub fn new_http_controller(c HTTPController) &HTTPController {
|
||||
return &HTTPController{
|
||||
...c,
|
||||
Handler: c.Handler
|
||||
}
|
||||
}
|
||||
|
||||
@['/:path...'; get; post; put; delete; patch]
|
||||
pub fn (mut c HTTPController) index(mut ctx Context, path string) veb.Result {
|
||||
println('Requested path: $path')
|
||||
|
||||
// Extract the HTTP method
|
||||
method := ctx.req.method.str().to_lower()
|
||||
|
||||
// Matches the request path against the OpenAPI specification and retrieves the corresponding PathItem
|
||||
path_item := match_path(path, c.specification) or {
|
||||
// Return a 404 error if no matching path is found
|
||||
return ctx.not_found()
|
||||
}
|
||||
|
||||
|
||||
// // Check if the path exists in the OpenAPI specification
|
||||
// path_item := c.specification.paths[path] or {
|
||||
// // Return a 404 error if the path is not defined
|
||||
// return ctx.not_found()
|
||||
// }
|
||||
|
||||
// Match the HTTP method with the OpenAPI specification
|
||||
operation := match method {
|
||||
'get' { path_item.get }
|
||||
'post' { path_item.post }
|
||||
'put' { path_item.put }
|
||||
'delete' { path_item.delete }
|
||||
'patch' { path_item.patch }
|
||||
else {
|
||||
// Return 405 Method Not Allowed if the method is not supported
|
||||
return ctx.method_not_allowed()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
mut arg_map := map[string]Any
|
||||
path_arg := path.all_after_last('/')
|
||||
// the OpenAPI Parameter specification belonging to the path argument
|
||||
arg_params := operation.parameters.filter(it.in_ == 'path')
|
||||
if arg_params.len > 1 {
|
||||
// TODO: use path template to support multiple arguments (right now just last arg supported)
|
||||
panic('implement')
|
||||
} else if arg_params.len == 1 {
|
||||
arg_map[arg_params[0].name] = arg_params[0].typed(path_arg)
|
||||
}
|
||||
|
||||
mut parameters := ctx.query.clone()
|
||||
// Build the Request object
|
||||
request := Request{
|
||||
path: path
|
||||
operation: operation
|
||||
method: method
|
||||
arguments: arg_map
|
||||
parameters: parameters
|
||||
body: ctx.req.data
|
||||
header: ctx.req.header
|
||||
}
|
||||
|
||||
// Use the handler to process the request
|
||||
response := c.handler.handle(request) or {
|
||||
// Use OpenAPI spec to determine the response status for the error
|
||||
return ctx.handle_error(operation.responses, err)
|
||||
}
|
||||
|
||||
// Return the response to the client
|
||||
ctx.res.set_status(response.status)
|
||||
|
||||
// ctx.res.header = response.header
|
||||
// ctx.set_content_type('application/json')
|
||||
|
||||
// return ctx.ok('[]')
|
||||
return ctx.send_response_to_client('application/json', response.body)
|
||||
}
|
||||
|
||||
// Handles errors and maps them to OpenAPI-defined response statuses
|
||||
fn (mut ctx Context) handle_error(possible_responses map[string]ResponseSpec, err IError) veb.Result {
|
||||
// Match the error with the defined responses
|
||||
for code, _ in possible_responses {
|
||||
if matches_error_to_status(err, code.int()) {
|
||||
ctx.res.set_status(http.status_from_int(code.int()))
|
||||
ctx.set_content_type('application/json')
|
||||
return ctx.send_response_to_client(
|
||||
'application/json',
|
||||
'{"error": "$err.msg()", "status": $code}'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Default to 500 Internal HTTPController Error if no match is found
|
||||
return ctx.server_error(
|
||||
'{"error": "Internal HTTPController Error", "status": 500}'
|
||||
)
|
||||
}
|
||||
|
||||
// Helper for 405 Method Not Allowed response
|
||||
fn (mut ctx Context) method_not_allowed() veb.Result {
|
||||
ctx.res.set_status(.method_not_allowed)
|
||||
ctx.set_content_type('application/json')
|
||||
return ctx.send_response_to_client(
|
||||
'application/json',
|
||||
'{"error": "Method Not Allowed", "status": 405}'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Matches a request path against OpenAPI path templates in the parsed structs
|
||||
// Returns the matching path key and corresponding PathItem if found
|
||||
fn match_path(req_path string, spec OpenAPI) !PathItem {
|
||||
// Iterate through all paths in the OpenAPI specification
|
||||
for template, path_item in spec.paths {
|
||||
if is_path_match(req_path, template) {
|
||||
// Return the matching path template and its PathItem
|
||||
return path_item
|
||||
}
|
||||
}
|
||||
// If no match is found, return an error
|
||||
return error('Path not found')
|
||||
}
|
||||
|
||||
// Helper to match an error to a specific response status
|
||||
fn matches_error_to_status(err IError, status int) bool {
|
||||
// This can be customized to map specific errors to statuses
|
||||
// For simplicity, we'll use a direct comparison here.
|
||||
return err.code() == status
|
||||
}
|
||||
|
||||
// Checks if a request path matches a given OpenAPI path template
|
||||
// Allows for dynamic path segments like `{petId}` in templates
|
||||
fn is_path_match(req_path string, template string) bool {
|
||||
// Split the request path and template into segments
|
||||
req_segments := req_path.split('/')
|
||||
template_segments := template.split('/')
|
||||
|
||||
// If the number of segments doesn't match, the paths can't match
|
||||
if req_segments.len != template_segments.len {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compare each segment in the template and request path
|
||||
for i, segment in template_segments {
|
||||
// If the segment is not dynamic (doesn't start with `{`), ensure it matches exactly
|
||||
if !segment.starts_with('{') && segment != req_segments[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// If all segments match or dynamic segments are valid, return true
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
pub fn (param Parameter) typed(value string) Any {
|
||||
param_schema := param.schema as Schema
|
||||
param_type := param_schema.typ
|
||||
param_format := param_schema.format
|
||||
|
||||
// Convert parameter value to corresponding type
|
||||
typ := match param_type {
|
||||
'integer' {
|
||||
param_format
|
||||
}
|
||||
'number' {
|
||||
param_format
|
||||
}
|
||||
else {
|
||||
param_type // Leave as param type for unknown types
|
||||
}
|
||||
}
|
||||
return typed(value, typ)
|
||||
}
|
||||
|
||||
// typed gets a value that is string and a desired type, and returns the typed string in Any Type.
|
||||
pub fn typed(value string, typ string) Any {
|
||||
match typ {
|
||||
'int32' {
|
||||
return value.int() // Convert to int
|
||||
}
|
||||
'int64' {
|
||||
return value.i64() // Convert to i64
|
||||
}
|
||||
'string' {
|
||||
return value // Already a string
|
||||
}
|
||||
'boolean' {
|
||||
return value.bool() // Convert to bool
|
||||
}
|
||||
'float' {
|
||||
return value.f32() // Convert to float
|
||||
}
|
||||
'double' {
|
||||
return value.f64() // Convert to double
|
||||
}
|
||||
else {
|
||||
return value.f64() // Leave as string for unknown types
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ module openapi
|
||||
|
||||
import net.http {CommonHeader}
|
||||
import x.json2 {Any}
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
|
||||
pub struct Request {
|
||||
pub:
|
||||
@@ -22,32 +23,44 @@ pub mut:
|
||||
header http.Header @[omitempty; str: skip; json:'-']// Response headers
|
||||
}
|
||||
|
||||
pub interface IHandler {
|
||||
mut:
|
||||
handle(Request) !Response
|
||||
}
|
||||
|
||||
pub struct Handler {
|
||||
pub:
|
||||
routes map[string]fn (Request) !Response // Map of route handlers
|
||||
specification OpenAPI @[required] // The OpenRPC specification
|
||||
pub mut:
|
||||
handler IHandler
|
||||
}
|
||||
|
||||
// Handle a request and return a response
|
||||
pub fn (handler Handler) handle(request Request) !Response {
|
||||
// Match the route based on the request path
|
||||
if route_handler := handler.routes[request.path] {
|
||||
// Call the corresponding route handler
|
||||
return route_handler(request)
|
||||
pub interface IHandler {
|
||||
mut:
|
||||
handle(Request) !Response // Custom handler for other methods
|
||||
}
|
||||
|
||||
// Return 404 if no route matches
|
||||
return Response{
|
||||
status: .not_found
|
||||
body: 'Not Found'
|
||||
header: http.new_header(
|
||||
key: CommonHeader.content_type,
|
||||
value: 'text/plain'
|
||||
)
|
||||
}
|
||||
@[params]
|
||||
pub struct HandleParams {
|
||||
timeout int = 60 // Timeout in seconds
|
||||
retry int // Number of retries
|
||||
}
|
||||
|
||||
// Handle a JSON-RPC request and return a response
|
||||
pub fn (mut h Handler) handle(req Request, params HandleParams) !Response {
|
||||
// Validate the method exists in the specification
|
||||
// if req.method !in h.specification.methods.map(it.name) {
|
||||
// // Return 404 if no route matches
|
||||
// return Response{
|
||||
// status: .not_found
|
||||
// body: 'Not Found'
|
||||
// header: http.new_header(
|
||||
// key: CommonHeader.content_type,
|
||||
// value: 'text/plain'
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
// Enforce timeout and retries (dummy implementation)
|
||||
if params.timeout < 0 || params.retry < 0 {
|
||||
panic('implement')
|
||||
}
|
||||
|
||||
// Forward the request to the custom handler
|
||||
return h.handler.handle(req)
|
||||
}
|
||||
Reference in New Issue
Block a user