This commit is contained in:
2025-08-05 15:15:36 +02:00
parent 4bd960ed05
commit 7fabb4163a
192 changed files with 14901 additions and 0 deletions

View File

@@ -0,0 +1,169 @@
import os
from pathlib import Path
from typing import Dict, List, Optional, Union
from jinja2 import Environment, FileSystemLoader
from heroserver.openrpc.generator.actor.vlang.vlang_code_generator import VlangGenerator
from heroserver.openrpc.generator.model_generator import ModelGenerator
from heroserver.openrpc.model.common import ContentDescriptorObject, ReferenceObject
from heroserver.openrpc.model.openrpc_spec import OpenRPCSpec
script_dir = os.path.dirname(os.path.abspath(__file__))
env = Environment(loader=FileSystemLoader(script_dir))
class RestServerGenerator:
def __init__(
self,
spec: OpenRPCSpec,
dir: Path,
) -> None:
self.lang_code_generator = VlangGenerator()
self.model_generator = ModelGenerator(spec, VlangGenerator())
self.spec = spec
self.dir = dir
self.crud_methods_template = env.get_template("templates/crud_methods.jinja")
self.internal_crud_methods_template = env.get_template("templates/internal_crud_methods.jinja")
self.imports_template = env.get_template("templates/imports.jinja")
self.actor_method_template = env.get_template("templates/actor_method.jinja")
self.internal_actor_method_template = env.get_template("templates/internal_actor_method.jinja")
self.server_template = env.get_template("templates/server.jinja")
def generate(self):
self.dir.mkdir(parents=True, exist_ok=True)
self.generate_models()
self.generate_crud()
self.generate_internal_actor_methods()
self.generate_openapi()
self.generate_server()
print(f"Generated API code has been written to {self.dir}")
def generate_server(self):
imports = self.imports_template.render(import_vweb=True)
code = self.server_template.render()
path = self.dir.joinpath("server.v")
with open(path, "w") as file:
file.write(f"{imports}\n\n{code}")
def generate_openapi(self):
imports = self.imports_template.render(import_vweb=True)
methods = ""
for path_str in self.model_generator.spec.get_root_objects().keys():
object = self.model_generator.processed_objects[path_str]
if object["code"] == "":
continue
type_name = object["name"]
variable_name = type_name.lower()
methods += self.crud_methods_template.render(variable_name=variable_name, type_name=type_name) + "\n\n"
for method in self.spec.methods:
if any(method.name.endswith(end) for end in ["get", "set", "delete"]):
continue
params: Dict[str, str] = {}
for param in method.params:
params[param.name] = self.model_generator.jsonschema_to_type(["methods", method.name, "params", param.name], param.schema)
return_type = self.method_result_return_type(["methods", method.name, "result"], method.result)
function_name = method.name.lower().replace(".", "_")
methods += (
self.actor_method_template.render(
rest_server_generator=self,
function_name=function_name,
method_params=params,
method_result=return_type,
)
+ "\n\n"
)
path = self.dir.joinpath("open_api.v")
with open(path, "w") as file:
file.write(f"{imports}\n\n{methods}")
def generate_models(self):
imports = self.imports_template.render()
code = self.model_generator.generate_models()
path = self.dir.joinpath("models.v")
with open(path, "w") as file:
file.write(f"{imports}\n\n{code}\n")
def generate_crud(self):
imports = self.imports_template.render(import_models=True)
methods = ""
for path_str in self.model_generator.spec.get_root_objects().keys():
object = self.model_generator.processed_objects[path_str]
if object["code"] == "":
continue
type_name = object["name"]
variable_name = type_name.lower()
methods += self.internal_crud_methods_template.render(variable_name=variable_name, type_name=type_name) + "\n\n"
path = self.dir.joinpath("crud.v")
with open(path, "w") as file:
file.write(f"{imports}\n\n{methods}")
def generate_internal_actor_methods(self):
imports = self.imports_template.render(import_models=True)
for method in self.spec.methods:
function_name = method.name.lower().replace(".", "_") + "_internal"
file_path = self.dir.joinpath(f"{function_name}.v")
if file_path.exists():
continue
if any(method.name.endswith(end) for end in ["get", "set", "delete"]):
continue
params: Dict[str, str] = {}
for param in method.params:
params[param.name] = self.model_generator.jsonschema_to_type(["methods", method.name, "params", param.name], param.schema)
return_type = self.method_result_return_type(["methods", method.name, "result"], method.result)
code = self.internal_actor_method_template.render(
rest_server_generator=self,
function_name=function_name,
method_params=params,
method_result=return_type,
)
with open(file_path, "w") as file:
file.write(f"{imports}\n\n{code}")
def get_method_params(self, method_params: Dict[str, str]) -> str:
return ", ".join([f"{param_name} {param_type}" for param_name, param_type in method_params.items()])
def method_result_return_type(
self,
path: List[str],
method_result: Optional[Union[ContentDescriptorObject, ReferenceObject]],
) -> str:
if not method_result:
type_name = ""
if isinstance(method_result, ContentDescriptorObject):
schema = method_result.schema
type_name = self.model_generator.jsonschema_to_type(path, schema)
elif isinstance(method_result, ReferenceObject):
type_name = self.model_generator.jsonschema_to_type(path, method_result)
return type_name
if __name__ == "__main__":
from heroserver.openrpc.generator.model_generator import ModelGenerator
from heroserver.openrpc.parser.parser import parser
data = parser(path="/root/code/git.threefold.info/projectmycelium/hero_server/generatorexamples/example1/specs/storymanager")
spec_object = OpenRPCSpec.load(data)
server_generator = RestServerGenerator(spec_object, Path("/tmp/rest3"))
server_generator.generate()

View File

@@ -0,0 +1,20 @@
@['/:circleguid/{{function_name}}'; post]
pub fn (mut v_server_app VServerApp) {{ function_name }}(circleguid int) vweb.Result{
body := json2.raw_decode(v_server_app.req.data)!.as_map()
{% for param_name, param_tpe in method_params.items() %}
{% if rest_server_generator.lang_code_generator.is_primitive(param_type) %}
{{param_name}} := body['{{param_name}}'].{{param_type}}()
{% else %}
{{param_name}} := json2.decode[{{param_type}}](body['{{param_name}}'].json_str()) or {
v_server_app.set_status(400, '')
return v_server_app.text('HTTP 400: Bad Request')
}
{% endif %}
{% endfor %}
res := {{function_name}}_internal(context, circleguid, {{', '.join(method_params.keys())}}) or {
v_server_app.set_status(500, '')
return v_server_app.text('HTTP 500: Internal Server Error')
}
return v_server_app.json(res)
}

View File

@@ -0,0 +1,32 @@
@['/:circleguid/{{variable_name}}_manager/{{variable_name}}/:id'; get]
pub fn (mut v_server_app VServerApp) {{variable_name}}_get(circleguid int, id str) vweb.Result{
res := {{variable_name}}_get_internal(circleguid, id) or {
v_server_app.set_status(500, '')
return v_server_app.text('HTTP 500: Internal Server Error')
}
return v_server_app.json(res)
}
@['/:circleguid/{{variable_name}}_manager/{{variable_name}}'; post]
pub fn (mut v_server_app VServerApp) {{variable_name}}_set(circleguid int) vweb.Result{
{{variable_name}} := json2.decode[{{type_name}}](v_server_app.req.data) or {
v_server_app.set_status(400, '')
return v_server_app.text('HTTP 400: Bad Request')
}
res := {{variable_name}}_set_internal(circleguid, {{variable_name}})or {
v_server_app.set_status(500, '')
return v_server_app.text('HTTP 500: Internal Server Error')
}
return v_server_app.json(res)
}
@['/:circleguid/{{variable_name}}_manager/{{variable_name}}/:id'; delete]
pub fn (mut v_server_app VServerApp) {{variable_name}}_delete(circleguid int, id str) vweb.Result{
##would use osis to delete this objecc
res := {{variable_name}}_delete_internal(circleguid, id) or {
v_server_app.set_status(500, '')
return v_server_app.text('HTTP 500: Internal Server Error')
}
return v_server_app.json(res)
}

View File

@@ -0,0 +1,4 @@
module main
import x.json2
{% if import_vweb %}import vweb{% endif %}

View File

@@ -0,0 +1,11 @@
module main
import freeflowuniverse.crystallib.context
pub fn {{function_name}}(ctx context.Context, circleguid int, {{rest_server_generator.get_method_params(method_params)}}) !{{method_result}}{
// context allows us to see who the user is and which groups the user is
// context also gives a logging feature
// context is linked to 1 circle
// context is linked to a DB (OSIS)
return 0
}

View File

@@ -0,0 +1,28 @@
pub fn {{variable_name}}_get_internal(db &backend.Indexer, circleguid int, id string) !{{type_name}}{
json_str := db.get(id, RootObject{
name: '{{type_name}}'
})!
return json.decode({{type_name}}, json_str)!
}
pub fn {{variable_name}}_set_internal(db &backend.Indexer, circleguid int, id string, {{variable_name}} {{type_name}}) !{
if id != ''{
db.set(RootObject{
id: id
name: '{{type_name}}'
})!
}
db.new(RootObject{
name: '{{type_name}}'
})!
}
pub fn {{variable_name}}_delete_internal(db &backend.Indexer, circleguid int, id string) !{
db.delete(id, RootObject{
name: '{{type_name}}'
})!
}

View File

@@ -0,0 +1,73 @@
module main
import freeflowuniverse.crystallib.baobab.backend
import freeflowuniverse.crystallib.clients.redisclient
import net.http
struct RPCServer {
pub mut:
backend &backend.Backend
redis &redisclient.Redis
}
fn main() {
rpc_server := new_server() or{
eprintln('failed to create server: ${err}')
exit(1)
}
rpc_server.listen_and_serve(8000) or {
eprintln('server error: ${err}')
exit(1)
}
}
fn new_server() !RPCServer{
db := start_new_backend_conn()!
redis_client := redisclient.new(['localhost:6379'])!
return RPCServer{
backend: &db
redis: &redis_client
}
}
fn (mut s RPCServer) listen_and_serve(port int) !{
mut server := &http.Server{
addr: 'localhost:${port}'
handler: s.handler
}
server.listen_and_serve()!
}
fn (mut s RPCServer) handler(req http.Request) http.Response{
if req.method != .post || req.url != '/'{
return http.Response{
status: 400
status_msg: 'Bad Request. invalid method or path'
}
}
body := req.data
id := id := jsonrpc.decode_request_id(body) or {
return http.Response(status: 400, status_msg: 'Bad Request. Cannot decode request.id ${msg}')
}
method := jsonrpc.jsonrpcrequest_decode_method(body) or {
return http.Response(status: 400, status_msg: 'Bad Request. Cannot decode request.method ${msg}')
}
params_str := jsonrpc.request_params(body) or {
return http.Response(status: 400, status_msg: 'Bad Request. Cannot decode request.params ${msg}')
}
s.handle_rpc(id, method, params_str)
response_body := s.redis.brpop([id]) or {
return http.Response(status: 500, status_msg: 'Internal Server Error: Serer timed-out while waiting for a response')
}
return http.Response{
status: 200
body: response_body
}
}