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

View File

@@ -0,0 +1,77 @@
from typing import Dict, List, Optional, Union
from urllib.parse import urlparse
from heroserver.openrpc.generator.code.lang_code_generator import LangCodeGenerator
from heroserver.openrpc.generator.model.model_generator import ModelGenerator
from heroserver.openrpc.model.common import (
ContentDescriptorObject,
ReferenceObject,
)
from heroserver.openrpc.model.openrpc_spec import OpenRPCSpec
class ClientGenerator:
def __init__(
self,
spec: OpenRPCSpec,
lang_code_generator: LangCodeGenerator,
output_file: str,
) -> None:
self.spec = spec
self.model_generator = ModelGenerator(spec, lang_code_generator)
self.lang_code_generator = lang_code_generator
self.output_file = output_file
def generate_client(self):
code_pre = self.lang_code_generator.generate_imports()
code_models = self.model_generator.generate_models()
code_methods = self.generate_methods()
# Write the generated code to a file
with open(self.output_file, "w") as file:
file.write(code_pre)
file.write("\n")
file.write(code_models)
file.write("\n")
file.write(code_methods)
print(f"Generated API code has been written to {self.output_file}")
def generate_methods(self):
servers = self.spec.servers
base_url = "http://localhost:8000"
if servers:
base_url = servers[0].url
url = urlparse(base_url)
methods = []
for method_spec in self.spec.methods:
params: Dict[str, str] = {}
for param in method_spec.params:
params[param.name] = self.model_generator.jsonschema_to_type(
["methods", method_spec.name, "params", param.name],
param.schema,
)
return_type = self.method_result_return_type(["methods", method_spec.name, "result"], method_spec.result)
methods.append(self.lang_code_generator.generate_method(method_spec, url, params, return_type))
return "\n\n".join(methods)
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

View File

@@ -0,0 +1,177 @@
import json
import os
from typing import Any, Dict, List
from urllib.parse import ParseResult
import inflect
from jinja2 import Environment, FileSystemLoader
from heroserver.openrpc.generator.lang_code_generator import LangCodeGenerator, PropertyInfo
from heroserver.openrpc.model.common import (
ReferenceObject,
SchemaObject,
)
from heroserver.openrpc.model.methods import MethodObject
from heroserver.openrpc.model.openrpc_spec import (
OpenRPCSpec,
)
script_dir = os.path.dirname(os.path.abspath(__file__))
env = Environment(loader=FileSystemLoader(script_dir))
inflector = inflect.engine()
class GolangCodeGenerator(LangCodeGenerator):
def __init__(self) -> None:
self.struct_template = env.get_template("templates/struct.jinja")
self.methods_template = env.get_template("templates/methods.jinja")
self.pre_template = env.get_template("templates/pre.jinja")
def generate_imports(self) -> str:
return self.pre_template.render(
package_name="rpcclient",
imports=[
"net/http",
"github.com/mitchellh/mapstructure",
"encoding/json",
"bytes",
"fmt",
"io",
],
)
def generate_object(
self,
type_name: str,
properties: Dict[str, PropertyInfo],
):
return self.struct_template.render(generator=self, type_name=type_name, properties=properties)
def generate_method(
self,
method_spec: MethodObject,
url: ParseResult,
params: Dict[str, str],
return_type: str,
) -> str:
function_name = self.get_camel_case_name(method_spec.name)
method_name = method_spec.name
method_result = self.type_to_method_result(return_type)
method_description = ""
if method_spec.description:
method_description = method_spec.description.replace("'", " ")
method_example = ""
if method_spec.examples and len(method_spec.examples) > 0:
method_example = json.dumps(method_spec.examples[0], indent=4)
method_code = self.methods_template.render(
generator=self,
url=url.geturl(),
function_name=function_name,
method_name=method_name,
method_params=params,
method_result=method_result,
return_type=return_type,
method_description=method_description,
method_example=method_example,
)
return method_code
def string_primitive(self) -> str:
return "string"
def integer_primitive(self) -> str:
return "int64"
def number_primitive(self) -> str:
return "float64"
def null_primitive(self) -> str:
return "nil"
def bool_primitive(self) -> str:
return "bool"
def array_of_type(self, type_name: str) -> str:
return f"[]{type_name}"
def generate_multitype(self, types: List[str]) -> str:
if len(types) > 2:
raise Exception("only a type and null are supported with anyOf/allOf keyword")
if len(types) == 1:
return types[0]
if types[0] == "nil":
return f"*{types[1]}"
if types[1] == "nil":
return f"*{types[0]}"
raise Exception("only a type and null are supported with anyOf/allOf keyword")
def encapsulate_types(self, path: List[str], types: List[SchemaObject | ReferenceObject]) -> str:
raise Exception("no support for allOf keyword")
def generate_enum(self, enum: List[Any], type_name: str) -> str:
if all(isinstance(elem, str) for elem in enum):
return self.string_primitive()
elif all(isinstance(elem, int) for elem in enum):
return self.integer_primitive()
else:
raise Exception(f"failed to generate enum code for: {enum}")
def type_to_method_result(self, type_name: str) -> str:
method_result = "error"
if len(type_name) > 0 and type_name != "nil":
method_result = f"({type_name}, error)"
return method_result
def is_primitive(self, type: str) -> bool:
return type in ["int64", "float64", "int", "bool", "string"]
def is_array(self, type: str) -> bool:
return type.startswith("[]")
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 get_camel_case_name(self, method_name: str) -> str:
return "".join([item.title() for item in method_name.split("_")])
def get_default_return_with_error(self, return_type: str, error_statement: str) -> str:
if return_type == "nil":
return error_statement
if return_type == "string":
return f'"", {error_statement}'
if return_type == "bool":
return f"false, {error_statement}"
if return_type == "float64" or return_type == "int64":
return f"0, {error_statement}"
return f"{return_type}{{}}, {error_statement}"
# main()
if __name__ == "__main__":
from heroserver.openrpc.generator.generator import ClientGenerator
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)
golang_code_generator = GolangCodeGenerator()
generator = ClientGenerator(
spec_object,
golang_code_generator,
"/tmp/go_client_new.go",
)
generator.generate_client()

View File

@@ -0,0 +1,92 @@
{% if method_example -%}
/*
Example:
{{ method_example }}
*/
{% endif -%}
{% if method_description -%}
/*
{{ method_description }}
*/
{% endif -%}
func {{ function_name }}({{ generator.get_method_params(method_params) }}) {{ method_result }} {
params := map[string]interface{}{}
{%- for param_name, param_type in method_params.items() %}
params["{{param_name}}"] = {{param_name}}
{%- endfor %}
payload := map[string]interface{}{}
payload["jsonrpc"] = "2.0"
payload["id"] = 0
payload["method"] = "{{ method_name }}"
payload["params"] = params
payloadBytes, err := json.Marshal(payload)
if err != nil{
return {{generator.get_default_return_with_error(return_type, 'err')}}
}
resp, err := http.Post("{{url}}", "application/json", bytes.NewBuffer(payloadBytes))
if err != nil{
return {{generator.get_default_return_with_error(return_type, 'fmt.Errorf("failed to make post request: %w", err)')}}
}
if resp.StatusCode >= 400{
return {{generator.get_default_return_with_error(return_type, 'fmt.Errorf("request failed with status %d: %s", resp.StatusCode, resp.Status)')}}
}
{%- if return_type != 'nil' %}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil{
return {{generator.get_default_return_with_error(return_type, 'fmt.Errorf("failed to read response body: %w", err)')}}
}
mp := map[string]interface{}{}
if err := json.Unmarshal(body, &mp); err != nil{
return {{generator.get_default_return_with_error(return_type, 'fmt.Errorf("failed to decode response body: %w", err)')}}
}
result, ok := mp["result"]
if !ok {
return {{generator.get_default_return_with_error(return_type, 'fmt.Errorf("invalid jsonrpc result: %v", mp)')}}
}
if result == nil {
{%- if return_type == 'nil '%}
return {{generator.get_default_return_with_error(return_type, 'nil')}}
{%- else %}
return {{generator.get_default_return_with_error(return_type, 'fmt.Errorf("invalid jsonrpc result: {{return_type}} was expected but found nil")')}}
{%- endif %}
}
{%- if generator.is_primitive(return_type) %}
return result.({{return_type}}), nil
{%- elif generator.is_array(return_type) %}
resSlice := {{return_type}}{}
for item := range result.([]intreface{}) {
{%- if generator.is_primitive(return_type[2:]) %}
resSlice = append(resSlice, item.({{return_type[2:]}}))
{%- else %}
tmp := {{return_type[2:]}}{}
if err := mapstructure.Decode(item, &tmp); err != nil{
return {{generator.get_default_return_with_error(return_type, 'fmt.Errorf("failed to decode result: %w", err)')}}
}
resSlice = append(resSlice, tmp)
{%- endif %}
}
return resSlice, nil
{%- else %}
ret := {{return_type}}{}
if err := mapstructure.Decode(result, &ret); err != nil{
return {{generator.get_default_return_with_error(return_type, 'fmt.Errorf("failed to decode result: %w", err)')}}
}
return ret, nil
{%- endif %}
{%- else %}
return nil
{%- endif %}
}

View File

@@ -0,0 +1,5 @@
package {{package_name}}
{% for item in imports %}
import "{{item}}"
{%- endfor %}

View File

@@ -0,0 +1,8 @@
type {{type_name}} struct{
{%- for property_name, property_info in properties.items() %}
{%- if property_info.description %}
// {{ property_info.description }}
{%- endif %}
{{ generator.get_camel_case_name(property_name) }} {{ property_info.type_name }} `json:"{{property_name}}"`
{%- endfor%}
}

View File

@@ -0,0 +1,97 @@
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional, Union
from urllib.parse import ParseResult
from heroserver.openrpc.model.common import (
ReferenceObject,
SchemaObject,
)
from heroserver.openrpc.model.methods import MethodObject
class PropertyInfo:
def __init__(
self,
name: str,
type_name: str,
description: Optional[str] = None,
example: Optional[str] = None,
) -> None:
self.name = name
self.type_name = type_name
self.description = description
self.example = example
class LangCodeGenerator(ABC):
@abstractmethod
def generate_imports(self) -> str:
pass
@abstractmethod
def generate_object(
self,
type_name: str,
properties: Dict[str, PropertyInfo],
):
pass
@abstractmethod
def generate_method(
self,
method_spec: MethodObject,
url: ParseResult,
params: Dict[str, str],
return_type: str,
) -> str:
pass
@abstractmethod
def string_primitive(self) -> str:
pass
@abstractmethod
def integer_primitive(self) -> str:
pass
@abstractmethod
def number_primitive(self) -> str:
pass
@abstractmethod
def null_primitive(self) -> str:
pass
@abstractmethod
def bool_primitive(self) -> str:
pass
@abstractmethod
def is_primitive(self, type_name: str) -> bool:
pass
@abstractmethod
def generate_multitype(self, path: List[str], types: List[Union[SchemaObject, ReferenceObject]]) -> str:
"""handles `anyOf` and `oneOf` in a json schema"""
pass
@abstractmethod
def array_of_type(self, type_name: str) -> str:
pass
@abstractmethod
def encapsulate_types(self, path: List[str], types: List[Union[SchemaObject, ReferenceObject]]) -> str:
"""handles `allOf` in a json schema"""
pass
@abstractmethod
def generate_enum(self, enum: List[Any], type_name: str) -> str:
pass
@abstractmethod
def type_to_method_result(self, type_name: str) -> str:
"""
convert type to method result
- type_name can be empty
"""
pass

View File

@@ -0,0 +1,205 @@
import json
import os
from typing import Any, Dict, List
from urllib.parse import ParseResult
import inflect
from jinja2 import Environment, FileSystemLoader
from heroserver.openrpc.generator.code.lang_code_generator import LangCodeGenerator, PropertyInfo
from heroserver.openrpc.model.common import (
ReferenceObject,
SchemaObject,
)
from heroserver.openrpc.model.methods import MethodObject
from heroserver.openrpc.model.openrpc_spec import (
OpenRPCSpec,
)
script_dir = os.path.dirname(os.path.abspath(__file__))
env = Environment(loader=FileSystemLoader(script_dir))
inflector = inflect.engine()
STRING_PRIMITIVE = "str"
INT_PRIMITIVE = "int"
FLOAT_PRIMITIVE = "float"
BOOL_PRMITIVE = "bool"
NONE_PRIMITIVE = "None"
class PythonCodeGenerator(LangCodeGenerator):
def __init__(self) -> None:
self.class_template = env.get_template("templates/class.jinja")
self.enum_template = env.get_template("templates/enum.jinja")
self.method_template = env.get_template("templates/method.jinja")
self.pre_template = env.get_template("templates/pre.jinja")
def generate_imports(self) -> str:
return self.pre_template.render()
def generate_object(
self,
type_name: str,
properties: Dict[str, PropertyInfo],
):
# for name, info in properties.items():
# info["load_code"] = self.generate_load_code(name, info['type'], 'data', f'data["{name}"]')
return self.class_template.render(python_code_generator=self, class_name=type_name, properties=properties)
def generate_load_code(self, name: str, type_name: str, data_source: str, load_param: str) -> str:
if type_name.startswith("Optional"):
type_name = type_name.removeprefix("Optional[").removesuffix("]")
return f'({self.generate_load_code(name, type_name, data_source)} if "{name}" in {data_source} else None)'
if type_name.startswith("List"):
type_name = type_name.removeprefix("List[").removesuffix("]")
if self.is_primitive(type_name):
return f'{data_source}.get("{name}")'
return f'[{self.generate_load_code(name, type_name, data_source, 'item')} for item in {data_source}.get("{name}", [])]'
if self.is_primitive(type_name):
return f'{data_source}.get("{name}")'
return f"{type_name}.load({load_param})"
def generate_method(
self,
method_spec: MethodObject,
url: ParseResult,
params: Dict[str, str],
return_type: str,
) -> str:
function_name = method_spec.name.lower().replace(".", "_")
method_name = method_spec.name
method_result = self.type_to_method_result(return_type)
method_description = ""
if method_spec.description:
method_description = method_spec.description.replace("'", " ")
method_description = method_description.replace("\n", "\n# ")
method_example = ""
if method_spec.examples and len(method_spec.examples) > 0:
method_example = json.dumps(method_spec.examples[0], indent=4)
method_example.replace("\n", "\n#")
method_code = self.method_template.render(
python_code_generator=self,
base_url=f"{url.scheme}://{url.netloc}",
url_path=url.path,
function_name=function_name,
method_name=method_name,
method_params=params,
method_result=method_result,
return_type=return_type,
method_description=method_description,
method_example=method_example,
)
return method_code
def string_primitive(self) -> str:
return STRING_PRIMITIVE
def integer_primitive(self) -> str:
return INT_PRIMITIVE
def number_primitive(self) -> str:
return FLOAT_PRIMITIVE
def null_primitive(self) -> str:
return NONE_PRIMITIVE
def bool_primitive(self) -> str:
return BOOL_PRMITIVE
def array_of_type(self, type_name: str) -> str:
return f"List[{type_name}]"
def generate_multitype(self, types: List[str]) -> str:
if len(types) > 2:
raise Exception("only a type and null are supported with anyOf/allOf keyword")
if len(types) == 1:
return types[0]
if types[0] == NONE_PRIMITIVE:
return f"Optional[{types[1]}]"
if types[1] == NONE_PRIMITIVE:
return f"Optional[{types[0]}]"
raise Exception("only a type and null are supported with anyOf/allOf keyword")
def encapsulate_types(self, path: List[str], types: List[SchemaObject | ReferenceObject]) -> str:
raise Exception("no support for allOf keyword")
def generate_enum(self, enum: List[Any], type_name: str) -> str:
if all(isinstance(elem, str) for elem in enum):
# enum of strings
return self.enum_template.render(
enum=enum,
type_name=type_name,
number_to_words=inflector.number_to_words,
)
elif all(isinstance(elem, int) for elem in enum):
# enum of integers
return self.enum_template.render(
is_integer=True,
enum=enum,
type_name=type_name,
number_to_words=inflector.number_to_words,
)
else:
raise Exception(f"failed to generate enum code for: {enum}")
def type_to_method_result(self, type_name: str) -> str:
return type_name
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 is_primitive(self, type_name: str) -> bool:
return type_name in [STRING_PRIMITIVE, INT_PRIMITIVE, FLOAT_PRIMITIVE, BOOL_PRMITIVE] or any(
type_name.startswith(end) for end in ["List", "Optional", "Union"]
)
def get_pydantic_field_params(self, prop_info: PropertyInfo) -> str:
field_str = ""
if prop_info.type_name.startswith("Optional"):
field_str = "None"
else:
field_str = "..."
if prop_info.description:
field_str += f', description="{prop_info.description}"'
if prop_info.example:
if isinstance(prop_info.example, str):
example_formatted = f'"{prop_info.example}"'
else:
example_formatted = prop_info.example
field_str += f", examples=[{example_formatted}]"
return f"Field({field_str})"
# main()
if __name__ == "__main__":
import yaml
from heroserver.openrpc.generator.generator import ClientGenerator
with open("/root/code/git.threefold.info/projectmycelium/hero_server/generatorexamples/mycelium_openrpc.yaml", "r") as file:
data = yaml.safe_load(file)
# print(data)
spec_object = OpenRPCSpec.load(data)
python_code_generator = PythonCodeGenerator()
generator = ClientGenerator(
spec_object,
python_code_generator,
"/tmp/python_client.py",
)
generator.generate_client()

View File

@@ -0,0 +1,4 @@
class {{ class_name }}(BaseModel):
{% for prop_name, prop_info in properties.items() -%}
{{ prop_name }}: {{prop_info.type_name}} = {{python_code_generator.get_pydantic_field_params(prop_info)}}
{% endfor %}

View File

@@ -0,0 +1,18 @@
{% if is_integer %}
class {{ type_name }}(Enum):
{% for elem in enum -%}
{{ number_to_words(elem) }} = {{ elem }}
{% endfor %}
{% else -%}
class {{ type_name }}(str, Enum):
{% for elem in enum -%}
{{ elem.upper() }} = '{{ elem }}'
{% endfor %}
{% endif %}
{# @classmethod
def load(cls, data: Dict[str, Any]) -> "{{type_name}}":
return cls(
{% for elem in enum -%}
{{elem}} = data.get('{{elem}}'),
{% endfor %}
) #}

View File

@@ -0,0 +1,30 @@
{% if method_example != "" -%}
# Example:
# {{ method_example }}
{% endif -%}
def {{ function_name }}({{ python_code_generator.get_method_params(method_params) }}){% if method_result %} -> {{ method_result }}{% endif %}:
{% if method_description != "" -%}
"""
{{ method_description }}
"""
{% endif -%}
url = "{{base_url}}"
headers = {"content-type": "application/json"}
params = {
{% for param_name, param_type in method_params.items() -%}
'{{ param_name }}': {{ param_name }},
{% endfor -%}
}
response = requests.post(url, json={"jsonrpc": "2.0", "id": 0, 'method': '{{ method_name }}', 'params': params}, headers=headers).json()
{% if return_type -%}
{% if python_code_generator.is_primitive(return_type) -%}
return response['result']
{% else -%}
return {{return_type}}(response['result'])
{% endif -%}
{% else -%}
response.raise_for_status()
{% endif -%}

View File

@@ -0,0 +1,5 @@
from typing import List, Optional, Union, Any, Dict
from pydantic import BaseModel, Field
from enum import Enum
import requests

View File

@@ -0,0 +1,205 @@
import os
from pathlib import Path
from typing import Dict, Union
from jinja2 import Environment, FileSystemLoader
from heroserver.openrpc.generator.model_generator import ModelGenerator
from heroserver.openrpc.generator.vlang.vlang_code_generator import VlangGenerator
from heroserver.openrpc.model.common import ContentDescriptorObject, ReferenceObject
from heroserver.openrpc.model.methods import MethodObject
from heroserver.openrpc.model.openrpc_spec import OpenRPCSpec
script_dir = os.path.dirname(os.path.abspath(__file__))
env = Environment(loader=FileSystemLoader(script_dir))
def get_actor_executor_name(actor: str) -> str:
return f"{''.join([part.title() for part in actor.split('_')])}Executor"
class ActorGenerator:
def __init__(self, actor: str, spec: OpenRPCSpec, dir: Path) -> None:
self.spec = spec
self.actor = actor
self.dir = dir
self.model_generator = ModelGenerator(spec, VlangGenerator())
self.executor_template = env.get_template("templates/executor.jinja")
self.pre_template = env.get_template("templates/pre.jinja")
self.internal_crud_methods_template = env.get_template("templates/internal_crud_methods.jinja")
self.internal_actor_method_template = env.get_template("templates/internal_actor_method.jinja")
def generate(self):
self.generate_models()
self.generate_crud()
self.generate_internal_actor_methods()
self.generate_executor()
def generate_models(self):
pre = self.pre_template.render(module_name="myhandler", imports=[])
code = self.model_generator.generate_models()
path = self.dir.joinpath(f"{self.actor}_models.v")
with open(path, "w") as file:
file.write(f"{pre}\n\n{code}\n")
def generate_crud(self):
imports = self.pre_template.render(
module_name="myhandler",
imports=["json", "freeflowuniverse.crystallib.baobab.backend"],
)
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,
actor_executor_name=get_actor_executor_name(self.actor),
)
+ "\n\n"
)
path = self.dir.joinpath(f"{self.actor}_crud.v")
with open(path, "w") as file:
file.write(f"{imports}\n\n{methods}")
def generate_internal_actor_methods(self):
pre = self.pre_template.render(module_name="myhandler", imports=[])
for method in self.spec.methods:
function_name = method.name.lower().replace(".", "_") + "_internal"
file_path = self.dir.joinpath(f"{self.actor}_{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.get_method_return_type(method)
method_params = ", ".join([f"{param.name} {self.get_param_type(method.name, param)}" for param in method.params])
code = self.internal_actor_method_template.render(
function_name=function_name,
method_params=method_params,
return_type=return_type,
actor_executor_name=get_actor_executor_name(self.actor),
)
with open(file_path, "w") as file:
file.write(f"{pre}\n\n{code}")
def generate_executor(self):
pre = self.pre_template.render(
module_name="myhandler",
imports=[
"x.json2",
"json",
"freeflowuniverse.crystallib.clients.redisclient",
"freeflowuniverse.crystallib.baobab.backend",
"freeflowuniverse.crystallib.rpc.jsonrpc",
],
)
code = self.executor_template.render(
generator=self,
actor_executor_name=get_actor_executor_name(self.actor),
methods=self.spec.methods,
)
path = self.dir.joinpath(f"{self.actor}_executor.v")
with open(path, "w") as file:
file.write(f"{pre}\n\n{code}")
def get_param_type(
self,
method_name: str,
param: Union[ContentDescriptorObject, ReferenceObject],
) -> str:
type_name = self.model_generator.jsonschema_to_type(["methods", method_name, "params", param.name], param.schema)
return type_name
def get_method_return_type(self, method: MethodObject) -> str:
if not method.result:
return ""
path = ["methods", method.name, "result"]
schema = method.result
if isinstance(method.result, ContentDescriptorObject):
schema = method.result.schema
return self.model_generator.jsonschema_to_type(path, schema)
def is_primitive(self, type_name: str) -> bool:
return self.model_generator.lang_code_generator.is_primitive(type_name)
def get_method_params_as_args(self, method: MethodObject) -> str:
return ", ".join([param.name for param in method.params])
class Generator:
def generate_handler(self, specs_dir: Path, output_dir: Path):
output_dir.mkdir(parents=True, exist_ok=True)
handler_template = env.get_template("templates/handler.jinja")
handler_test_template = env.get_template("templates/handler_test.jinja")
pre_template = env.get_template("templates/pre.jinja")
actors = []
method_names = []
pre = pre_template.render(
module_name="myhandler",
imports=[
"freeflowuniverse.crystallib.clients.redisclient",
"freeflowuniverse.crystallib.baobab.backend",
"freeflowuniverse.crystallib.rpc.jsonrpc",
],
)
code = ""
for item in specs_dir.iterdir():
if not item.is_dir():
continue
actors.append(item.name)
data = parser(path=item.as_posix())
openrpc_spec = OpenRPCSpec.load(data)
actor_generator = ActorGenerator(item.name, openrpc_spec, output_dir)
actor_generator.generate()
for method in openrpc_spec.methods:
method_names.append(f"{item.name}.{method.name}")
code = handler_template.render(actors=actors, get_actor_executor_name=get_actor_executor_name)
handler_path = output_dir.joinpath("handler.v")
with open(handler_path, "w") as file:
file.write(f"{pre}\n\n{code}")
handler_test_path = output_dir.joinpath("handler_test.v")
with open(handler_test_path, "w") as file:
file.write(handler_test_template.render(method_names=method_names))
if __name__ == "__main__":
from heroserver.openrpc.parser.parser import parser
generator = Generator()
path = "~/code/git.threefold.info/projectmycelium/hero_server/generatorexamples/example1/specs"
generator.generate_handler(Path(path), Path("/tmp/myhandler"))
# vlang_code_generator = VlangGenerator()
# generator = ClientGenerator(
# spec_object,
# vlang_code_generator,
# "/tmp/v_client_new.v",
# )
# generator.generate_client()

View File

@@ -0,0 +1,9 @@
pub enum {{ type_name }}{
{% for elem in enum -%}
{% if is_integer -%}
{{ number_to_words(elem) }} = {{ elem }}
{% else -%}
{{ elem }}
{% endif -%}
{% endfor %}
}

View File

@@ -0,0 +1,77 @@
pub struct {{ actor_executor_name }}{
pub mut:
db &backend.Backend
redis &redisclient.Redis
}
pub fn (mut executor {{ actor_executor_name }}) execute(rpc_msg_id string, rpc_msg_method string, rpc_msg_params_str string) {
raw_params := json2.raw_decode(rpc_msg_params_str) or{
executor.return_error(rpc_msg_id, jsonrpc.invalid_params)
return
}
params_arr := raw_params.arr()
match rpc_msg_method {
{%- for method in methods %}
'{{method.name}}' {
{%- for param in method.params %}
{%- if generator.is_primitive(generator.get_param_type(method.name, param))%}
{{param.name}} := params_arr[{{loop.index0}}] as {{generator.get_param_type(method.name, param)}}
{%- else %}
{{param.name}} := json.decode({{generator.get_param_type(method.name, param)}}, params_arr[{{loop.index0}}].json_str()) or {
executor.return_error(rpc_msg_id, jsonrpc.invalid_request)
return
}
{%- endif %}
{%- endfor %}
{%- if generator.get_method_return_type(method) == 'none' %}
executor.{{method.name}}_internal({{generator.get_method_params_as_args(method)}}) or {
executor.return_error(rpc_msg_id, jsonrpc.InnerJsonRpcError{
code: 32000
message: '${err}'
})
return
}
response := jsonrpc.JsonRpcResponse[string]{
jsonrpc: '2.0.0'
id: rpc_msg_id
result: ''
}
{%- else %}
result := executor.{{method.name}}_internal({{generator.get_method_params_as_args(method)}}) or {
executor.return_error(rpc_msg_id, jsonrpc.InnerJsonRpcError{
code: 32000
message: '${err}'
})
return
}
response := jsonrpc.JsonRpcResponse[{{generator.get_method_return_type(method)}}]{
jsonrpc: '2.0.0'
id: rpc_msg_id
result: result
}
{%- endif %}
// put response in response queue
executor.redis.lpush(rpc_msg_id, response.to_json()) or {
println('failed to push response for ${rpc_msg_id} to redis queue: ${err}')
}
}
{%- endfor %}
else {
executor.return_error(rpc_msg_id, jsonrpc.method_not_found)
return
}
}
}
pub fn (mut executor {{actor_executor_name}}) return_error(rpc_msg_id string, error jsonrpc.InnerJsonRpcError){
response := jsonrpc.new_jsonrpcerror(rpc_msg_id, error)
executor.redis.lpush(rpc_msg_id, response.to_json()) or {
println('failed to push response for ${rpc_msg_id} to redis queue: ${err}')
}
}

View File

@@ -0,0 +1,50 @@
struct Handler {
pub mut:
db &backend.Backend
redis &redisclient.Redis
{% for actor in actors %}
{{actor}}_executor {{get_actor_executor_name(actor)}}
{%- endfor %}
}
pub fn new(db_config backend.BackendConfig, redis_addr string) !Handler{
db := backend.new(db_config)!
mut redis_client := redisclient.new([redis_addr])!
redis_client.selectdb(0)!
return Handler{
db: &db
redis: &redis_client
{%- for actor in actors %}
{{actor}}_executor: {{get_actor_executor_name(actor)}}{
db: &db
redis: &redis_client
}
{%- endfor %}
}
}
// handle handles an incoming JSON-RPC encoded message and returns an encoded response
pub fn (mut handler Handler) handle(id string, method string, params_str string) {
actor := method.all_before('.')
method_name := method.all_after('.')
match actor {
{%- for actor in actors %}
'{{ actor }}' {
spawn (&handler.{{actor}}_executor).execute(id, method_name, params_str)
}
{%- endfor %}
else {
handler.return_error(id, jsonrpc.method_not_found)
return
}
}
}
pub fn (mut handler Handler) return_error(rpc_msg_id string, error jsonrpc.InnerJsonRpcError){
response := jsonrpc.new_jsonrpcerror(rpc_msg_id, error)
handler.redis.lpush(rpc_msg_id, response.to_json()) or {
println('failed to push response for ${rpc_msg_id} to redis queue: ${err}')
}
}

View File

@@ -0,0 +1,31 @@
module myhandler
import x.json2
import rand
import freeflowuniverse.crystallib.baobab.backend
fn test_handler(){
db_config := backend.BackendConfig{
name: 'myhandler'
secret: 'secret'
reset: true
db_type: .postgres
}
mut handler := new(db_config, '127.0.0.1:6379')!
{% for method_name in method_names %}
do_request(mut handler, '{{method_name}}')!
{%- endfor %}
}
fn do_request(mut handler Handler, method_name string) ! {
// TODO: edit input parameters
mut params := []json2.Any{}
params << "objid"
params << "blabla_name"
params_str := json2.Any(params).json_str()
id := rand.string(6)
handler.handle(rand.string(6), method_name, json2.Any(params).json_str())
println('request id: ${id}')
}

View File

@@ -0,0 +1,7 @@
pub fn (mut executor {{ actor_executor_name }}) {{function_name}}({{method_params}}) !{{return_type}}{
// 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)
panic('implement')
}

View File

@@ -0,0 +1,28 @@
pub fn (mut executor {{ actor_executor_name }}) {{variable_name}}_get_internal(id string) !{{type_name}}{
json_str := executor.db.indexer.get_json(id, backend.RootObject{
name: '{{type_name}}'
})!
return json.decode({{type_name}}, json_str)!
}
pub fn (mut executor {{ actor_executor_name }}) {{variable_name}}_set_internal({{variable_name}} {{type_name}}) !{
if {{variable_name}}.oid != ''{
executor.db.indexer.set(backend.RootObject{
id: {{variable_name}}.oid
name: '{{type_name}}'
})!
}
executor.db.indexer.new(backend.RootObject{
name: '{{type_name}}'
})!
}
pub fn (mut executor {{ actor_executor_name }}) {{variable_name}}_delete_internal(id string) !{
executor.db.indexer.delete(id, backend.RootObject{
name: '{{type_name}}'
})!
}

View File

@@ -0,0 +1,5 @@
pub struct {{method_param_struct_name}}{
{% for param_name, param_type in params.items()%}
{{param_name}} {{param_type}}
{%- endfor %}
}

View File

@@ -0,0 +1,75 @@
{% if method_example -%}
/*
Example:
{{ method_example }}
*/
{% endif -%}
{% if method_description -%}
/*
{{ method_description }}
*/
{% endif -%}
pub fn {{ function_name }}({{ vlang_code_generator.get_method_params(method_params) }}) {{ method_result }}{
mut conn := httpconnection.new(
name: 'openrpc_client'
url: '{{ base_url }}'
)!
mut params := map[string]json2.Any{}
{% for param_name, param_type in method_params.items() -%}
{% if vlang_code_generator.is_primitive(param_type) %}
params["{{ param_name }}"] = {{ param_name }}
{% elif vlang_code_generator.is_vlang_array(param_type) %}
mut any_arr := []json2.Any{}
for item in {{ param_name }}{
{% if vlang_code_generator.is_primitive(param_type[2:]) %}
any_arr << item
{% else %}
any_arr << json2.raw_decode(json2.encode(item))!
{% endif %}
}
params["{{ param_name }}"] = json2.Any(any_arr)
{%else %}
params["{{ param_name }}"] = json2.raw_decode(json2.encode({{ param_name }}))!
{% endif %}
{% endfor -%}
mut payload := map[string]json2.Any{}
payload['jsonrpc'] = "2.0"
payload['id'] = 0
payload['method'] = '{{ method_name }}'
payload['params'] = params
response := conn.send(method: .post, data: json2.encode(payload){% if url_path -%}, prefix: '{{ url_path }}' {% endif -%})!
if !response.is_ok() {
return error('failed to make rpc request: (${response.code}) ${response.data}')
}
{% if return_type != 'none' %}
mp := json2.raw_decode(response.data)!.as_map()
res := mp['result'] or {
return error('invalid jsonrpc result: ${response.data}')
}
if res is json2.Null{
return error('not found')
}
{% if vlang_code_generator.is_primitive(return_type) %}
return res as {{return_type}}
{% elif vlang_code_generator.is_vlang_array(return_type) %}
mut res_arr := {{return_type}}
for item in res.arr() {
{% if vlang_code_generator.is_primitive(return_type[2:]) %}
res_arr << item as {{return_type}}
{% else %}
res_arr << json2.decode[{{return_type[2:]}}](item.json_str())!
{% endif %}
}
return res_arr
{%else %}
return json2.decode[{{return_type}}](res.json_str())!
{% endif -%}
{% endif %}
}

View File

@@ -0,0 +1,5 @@
module {{module_name}}
{% for item in imports %}
import {{item}}
{%- endfor %}

View File

@@ -0,0 +1,10 @@
@[params]
pub struct {{ type_name }}{
pub mut:
{%- for property_name, property_info in properties.items() %}
{%- if property_info.description %}
// {{ property_info.description }}
{%- endif %}
{{ property_name }} {{ property_info.type_name }}
{%- endfor %}
}

View File

@@ -0,0 +1,164 @@
import json
import os
from typing import Any, Dict, List
from urllib.parse import ParseResult
import inflect
from jinja2 import Environment, FileSystemLoader
from heroserver.openrpc.generator.lang_code_generator import LangCodeGenerator, PropertyInfo
from heroserver.openrpc.model.common import (
ReferenceObject,
SchemaObject,
)
from heroserver.openrpc.model.methods import MethodObject
from heroserver.openrpc.model.openrpc_spec import (
OpenRPCSpec,
)
script_dir = os.path.dirname(os.path.abspath(__file__))
env = Environment(loader=FileSystemLoader(script_dir))
inflector = inflect.engine()
class VlangGenerator(LangCodeGenerator):
def __init__(self) -> None:
self.struct_template = env.get_template("templates/struct.jinja")
self.enum_template = env.get_template("templates/enum.jinja")
self.methods_template = env.get_template("templates/methods.jinja")
self.pre_template = env.get_template("templates/pre.jinja")
def generate_imports(self) -> str:
return self.pre_template.render()
def generate_object(
self,
type_name: str,
properties: Dict[str, PropertyInfo],
):
return self.struct_template.render(type_name=type_name, properties=properties)
def generate_method(
self,
method_spec: MethodObject,
url: ParseResult,
params: Dict[str, str],
return_type: str,
) -> str:
function_name = method_spec.name.lower().replace(".", "_")
method_name = method_spec.name
method_result = self.type_to_method_result(return_type)
method_description = ""
if method_spec.description:
method_description = method_spec.description.replace("'", " ")
method_example = ""
if method_spec.examples and len(method_spec.examples) > 0:
method_example = json.dumps(method_spec.examples[0], indent=4)
method_code = self.methods_template.render(
vlang_code_generator=self,
base_url=f"{url.scheme}://{url.netloc}",
url_path=url.path,
function_name=function_name,
method_name=method_name,
method_params=params,
method_result=method_result,
return_type=return_type,
method_description=method_description,
method_example=method_example,
)
return method_code
def string_primitive(self) -> str:
return "string"
def integer_primitive(self) -> str:
return "i64"
def number_primitive(self) -> str:
return "f64"
def null_primitive(self) -> str:
return "none"
def bool_primitive(self) -> str:
return "bool"
def array_of_type(self, type_name: str) -> str:
return f"[]{type_name}"
def generate_multitype(self, types: List[str]) -> str:
if len(types) > 2:
raise Exception("only a type and null are supported with anyOf/allOf keyword")
if len(types) == 1:
return types[0]
if types[0] == "none":
return f"?{types[1]}"
if types[1] == "none":
return f"?{types[0]}"
raise Exception("only a type and null are supported with anyOf/allOf keyword")
def encapsulate_types(self, path: List[str], types: List[SchemaObject | ReferenceObject]) -> str:
raise Exception("no support for allOf keyword")
def generate_enum(self, enum: List[Any], type_name: str) -> str:
if all(isinstance(elem, str) for elem in enum):
# enum of strings
return self.enum_template.render(
enum=enum,
type_name=type_name,
number_to_words=inflector.number_to_words,
)
elif all(isinstance(elem, int) for elem in enum):
# enum of integers
return self.enum_template.render(
is_integer=True,
enum=enum,
type_name=type_name,
number_to_words=inflector.number_to_words,
)
else:
raise Exception(f"failed to generate enum code for: {enum}")
def type_to_method_result(self, type_name: str) -> str:
if type_name == "none":
type_name = ""
if type_name.startswith("?"):
type_name = type_name[1:]
return "!" + type_name
def is_primitive(self, type: str) -> bool:
return type in ["u64", "f64", "i64", "int", "bool", "string"]
def is_vlang_array(self, type: str) -> bool:
return type.startswith("[]")
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()])
# main()
if __name__ == "__main__":
from heroserver.openrpc.generator.generator import ClientGenerator
from heroserver.openrpc.parser.parser import parser
data = parser(path="~/code/git.threefold.info/projectmycelium/hero_server/lib/openrpclib/parser/examples")
spec_object = OpenRPCSpec.load(data)
vlang_code_generator = VlangGenerator()
generator = ClientGenerator(
spec_object,
vlang_code_generator,
"/tmp/v_client_new.v",
)
generator.generate_client()

View File

@@ -0,0 +1,46 @@
import argparse
from pathlib import Path
from heroserver.openrpc.generator.rest_server.python.rest_server_generator import (
RestServerGenerator,
)
from heroserver.openrpc.model.openrpc_spec import OpenRPCSpec
from heroserver.openrpc.parser.parser import parser
def do(specs_dir: Path, output: Path):
for item in specs_dir.iterdir():
if not item.is_dir():
continue
actor_name = item.name
actor_output_path = output.joinpath(actor_name)
actor_output_path.mkdir(parents=True, exist_ok=True)
print(f"item: {item.as_posix()}")
# if item.as_posix() == "generatorexamples/example1/specs/storymanager":
# continue
data = parser(path=item.as_posix())
# print(f"data: {data}")
spec_object = OpenRPCSpec.load(data)
server_generator = RestServerGenerator(spec_object, actor_output_path)
server_generator.generate()
if __name__ == "__main__":
arg_parser = argparse.ArgumentParser(description="Hero server and client generator tool.")
arg_parser.add_argument(
"--specs",
type=str,
required=True,
help="specs directory",
)
arg_parser.add_argument(
"--output",
type=str,
required=True,
help="output directory",
)
args = arg_parser.parse_args()
do(Path(args.specs), Path(args.output))

View File

@@ -0,0 +1,90 @@
import argparse
import json
import os
from jinja2 import Environment, FileSystemLoader
from ....openrpc.tools import get_pydantic_type, get_return_type, topological_sort
script_dir = os.path.dirname(os.path.abspath(__file__))
def generate_models(openrpc_spec: dict) -> str:
schema_dict = openrpc_spec["components"]["schemas"]
sorted_classes = topological_sort(schema_dict)
env = Environment(loader=FileSystemLoader(script_dir), trim_blocks=True, lstrip_blocks=True)
template = env.get_template("templates/mdbook/structs.jinja")
model_code = template.render(
sorted_classes=sorted_classes,
schema_dict=schema_dict,
get_pydantic_type=get_pydantic_type,
)
return model_code
def generate_model(model_name: str, schema: dict) -> str:
env = Environment(loader=FileSystemLoader(script_dir))
template = env.get_template("templates/vlang/struct.jinja")
model_code = template.render(model_name=model_name, schema=schema, get_pydantic_type=get_pydantic_type)
return model_code
def generate_api_methods(openrpc_spec: dict) -> str:
env = Environment(loader=FileSystemLoader(script_dir), trim_blocks=True, lstrip_blocks=True)
template = env.get_template("templates/mdbook/methods.jinja")
code = template.render(
spec=openrpc_spec,
methods=openrpc_spec.get("methods", []),
get_return_type=get_return_type,
get_pydantic_type=get_pydantic_type,
)
return code
def main() -> None:
parser = argparse.ArgumentParser(description="Generate API code from OpenRPC specification")
parser.add_argument(
"-s",
"--spec",
help="Path to the specs (expressed in our own V format)",
default="~/code/git.threefold.info/projectmycelium/hero_server/generatorexamples/example1/specs",
)
parser.add_argument(
"-o",
"--output",
default="/tmp/generator/mdbook",
help="Output file path (default: /tmp/generator/mdbook)",
)
args = parser.parse_args()
spec_file = os.path.expanduser(args.spec)
output_dir = os.path.expanduser(args.output)
if not os.path.isfile(spec_file):
print(f"Error: OpenRPC specification file '{spec_file}' does not exist.")
return
with open(spec_file) as file:
openrpc_spec = json.load(file)
code_models = generate_models(openrpc_spec)
code_methods = generate_api_methods(openrpc_spec)
os.makedirs(os.path.dirname(output_dir), exist_ok=True)
# Write the generated code to a file
with open(f"{output_dir}/models.md", "w") as file:
file.write(code_models)
with open(f"{output_dir}/methods.md", "w") as file:
file.write(code_methods)
print(f"Generated API code has been written to {output_dir}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,16 @@
## Methods
{% for method in methods %}
- {{ method['name'] }}: {{ method.get('description', '') }}
- Parameters:
{% for param in method.get('params', []) %}
{{ param['name'] }}: {{ get_pydantic_type(param['schema'])}}
{% endfor %}
- Return Type:
{{ get_return_type(method['result']) }}
- Example:
{{ method.get('examples', [{}])[0] }}
{% endfor %}

View File

@@ -0,0 +1,9 @@
# Classes
{% for class_name in sorted_classes %}
- {{ schema_dict[class_name]['title'] }}
{% for prop_name, prop in schema_dict[class_name]['properties'].items() %}
- {{ prop_name }} ({{ get_pydantic_type(prop)}}): {{ prop['description'] }}
{% endfor %}
{% endfor %}

View File

@@ -0,0 +1,170 @@
from typing import Dict, List, Set
from heroserver.openrpc.generator.code.lang_code_generator import (
LangCodeGenerator,
PropertyInfo,
)
from heroserver.openrpc.model.common import (
ContentDescriptorObject,
ReferenceObject,
SchemaObject,
)
from heroserver.openrpc.model.openrpc_spec import OpenRPCSpec
class ModelGenerator:
def __init__(self, spec: OpenRPCSpec, lang_code_generator: LangCodeGenerator) -> None:
self.spec = spec
self.lang_code_generator = lang_code_generator
self.processed_objects: Dict[str, Dict[str, str]] = {}
self.ordered_objects: List[str] = []
self.used_names: Set[str] = set()
def generate_models(self):
if not self.spec.components:
return ""
schemas = self.spec.components.schemas
schemas_path = ["components", "schemas"]
for name, schema in schemas.items():
self.jsonschema_to_type(
path=schemas_path + [name],
jsonschema=schema,
)
objects_code = ""
for val in self.ordered_objects:
if val == "":
continue
objects_code = f"{objects_code}{val}\n\n"
print(f"debugzo4 {objects_code}")
return objects_code
def jsonschema_to_type(self, path: List[str], jsonschema: SchemaObject | ReferenceObject) -> str:
if isinstance(jsonschema, ReferenceObject):
ref: str = jsonschema.ref
ref_schema = self.spec.ref_to_schema(ref)
ref_path = ref.split("/")[1:]
if isinstance(ref_schema, ContentDescriptorObject):
# TODO: implement
raise Exception("unimplemented")
# return self.content_descriptor_to_type(ref_path, ref_schema)
return self.jsonschema_to_type(ref_path, ref_schema)
path_str = "/".join([item.lower() for item in path])
if path_str in self.processed_objects:
return self.processed_objects[path_str]["name"]
type_name = self.type_name_from_path(path)
description = getattr(jsonschema, "description", None)
if jsonschema.enum:
enum = jsonschema.enum
type_code = self.lang_code_generator.generate_enum(enum, type_name)
if self.lang_code_generator.is_primitive(type_code):
return type_code
self.add_object(path_str, type_code, type_name)
return type_name
if jsonschema.type:
match jsonschema.type:
case "string":
return self.lang_code_generator.string_primitive()
case "integer":
return self.lang_code_generator.integer_primitive()
case "number":
return self.lang_code_generator.number_primitive()
case "array":
if isinstance(jsonschema.items, List):
raise Exception("array of different item types is not supported")
item_type_name = self.jsonschema_to_type(path + ["item"], jsonschema.items)
return self.lang_code_generator.array_of_type(item_type_name)
case "boolean":
return self.lang_code_generator.bool_primitive()
case "object":
# to prevent cyclic dependencies
self.add_object(path_str, "", type_name)
properties: Dict[str, PropertyInfo] = {}
for (
property_name,
property_schema,
) in jsonschema.properties.items():
schema = property_schema
new_path = path + ["properties", property_name]
if isinstance(property_schema, ReferenceObject):
schema = self.spec.ref_to_schema(property_schema.ref)
new_path = property_schema.ref.split("/")[1:]
property_info = PropertyInfo(
name=property_name,
type_name=self.jsonschema_to_type(new_path, schema),
description=schema.description,
example=schema.example,
)
properties[property_name] = property_info
type_code = self.lang_code_generator.generate_object(type_name, properties)
self.add_object(path_str, type_code, type_name)
return type_name
case "null":
return self.lang_code_generator.null_primitive()
case _:
raise Exception(f"type {jsonschema.type} is not supported")
if jsonschema.anyOf:
type_names = []
for i, item in enumerate(jsonschema.anyOf):
type_names.append(self.jsonschema_to_type(path + [f"anyOf{i}"], item))
return self.lang_code_generator.generate_multitype(type_names)
# self.add_object(path_str, type_code, type_code)
# return type_code
elif jsonschema.oneOf:
type_names = []
for i, item in enumerate(jsonschema.oneOf):
type_names.append(self.jsonschema_to_type(path + [f"oneOf{i}"], item))
return self.lang_code_generator.generate_multitype(type_names)
# self.add_object(path_str, type_code, type_code)
# return type_code
elif jsonschema.allOf:
return self.lang_code_generator.encapsulate_types(jsonschema.allOf)
# self.add_object(path_str, type_code, type_code)
# return type_name
raise Exception(f"type {jsonschema.type} is not supported")
def add_object(self, path_str: str, type_code: str, type_name: str):
self.used_names.add(type_name)
self.processed_objects[path_str] = {
"code": type_code,
"name": type_name,
}
print(f"debugzo21 {self.processed_objects[path_str]}")
self.ordered_objects.append(type_code)
def type_name_from_path(self, path: List[str]) -> str:
type_name = ""
for item in reversed(path):
type_name += item.title() if item.islower() else item
if type_name not in self.used_names:
return type_name
raise Exception(f"failed to generate unique name from path: {path}")

View File

@@ -0,0 +1,14 @@
## example how to use
```python
import heroserver.openrpc.generator
openrpc_spec = generator.openrpc_spec(
path="~/code/git.threefold.info/projectmycelium/hero_server/generatorexamples/example1/specs"
)
print(openrpc_spec)
```

View File

@@ -0,0 +1,28 @@
from typing import Union
from fastapi import FastAPI
from vm_manager__vm_start import vm_start
app = FastAPI()
#VM WOULD BE AN OBJECT of e.g. a virtual machine description
@app.get("/$circleguid/vm_manager/vm")
def vm_get()-> VM:
return {...}
@app.post("/$circleguid/vm_manager/vm")
def vm_set()-> bool:
return True
@app.delete("/$circleguid/vm_manager/vm")
def vm_delete()-> bool:
##would use osis to delete this objecc
return True
@app.get("/$circleguid/vm_manager/vm_start/{vm_guid}")
def vm_start(vm_guid: str) -> bool:
vm_start(context=context,vm_guid=vm_guid)

View File

@@ -0,0 +1,8 @@
def vm_start(context, vm_guid: str) -> bool:
#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)
#code to be implemented e.g. using DAGU to start a vm

View File

@@ -0,0 +1,256 @@
import os
from pathlib import Path
from typing import Dict, List, Optional, Union
from jinja2 import Environment, FileSystemLoader
from heroserver.openrpc.generator.code.python.python_code_generator import PythonCodeGenerator
from heroserver.openrpc.generator.model.model_generator import ModelGenerator
# Fix the issue by ensuring that the 'object' variable is properly defined and has the expected attributes.
# The following code will ensure that 'object' is a valid SchemaObject before calling 'print_items'.
from heroserver.openrpc.model.common import ContentDescriptorObject, ReferenceObject, SchemaObject
from heroserver.openrpc.model.openrpc_spec import OpenRPCSpec
from heroserver.openrpc.parser.parser import parser
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:
if not isinstance(spec, OpenRPCSpec):
raise TypeError(f"Expected spec to be of type OpenRPCSpec, got {type(spec)}")
if not isinstance(dir, Path):
raise TypeError(f"Expected dir to be of type Path, got {type(dir)}")
self.model_generator = ModelGenerator(spec, PythonCodeGenerator())
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.internal_crud_mock_methods_template = env.get_template("templates/internal_crud_mock_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_mock_crud()
self.generate_internal_actor_methods()
self.generate_openapi()
self.generate_openapi_mock()
self.generate_server()
print(f"Generated API code has been written to {self.dir}")
def generate_server(self):
code = self.server_template.render()
path = self.dir.joinpath("server.py")
with open(path, "w") as file:
file.write(code)
def generate_openapi(self):
imports = self.imports_template.render(import_crud=True, import_models=True)
app_init = "app = FastAPI()\n\n"
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(".", "_")
imports += f"from {function_name}_internal import {function_name}_internal\n"
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.py")
with open(path, "w") as file:
file.write(f"{imports}\n\n{app_init}\n\n{methods}")
def generate_openapi_mock(self):
imports = self.imports_template.render(mock=True, import_crud=True, import_models=True)
app_init = "app = FastAPI()\n\n"
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(mock=True, 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(".", "_")
imports += f"from {function_name}_internal import {function_name}_internal\n"
methods += (
self.actor_method_template.render(
mock=True,
rest_server_generator=self,
function_name=function_name,
method_params=params,
method_result=return_type,
)
+ "\n\n"
)
path = self.dir.joinpath("open_api_mock.py")
with open(path, "w") as file:
file.write(f"{imports}\n\n{app_init}\n\n{methods}")
def generate_models(self):
imports = self.imports_template.render()
code = self.model_generator.generate_models()
path = self.dir.joinpath("models.py")
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.py")
with open(path, "w") as file:
file.write(f"{imports}\n\n{methods}")
def generate_mock_crud(self):
imports = self.imports_template.render(import_models=True)
imports += "from heroserver.openrpc.tools import create_example_object"
methods = ""
for path_str in self.model_generator.spec.get_root_objects().keys():
object = self.model_generator.spec.get_root_objects()[path_str]
if isinstance(object, SchemaObject):
print_items(object)
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_mock_methods_template.render(variable_name=variable_name, type_name=type_name) + "\n\n"
path = self.dir.joinpath("crud_mock.py")
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}.py")
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
def print_items(schema_object, depth=0):
print(f"prito {schema_object.items}")
indent = " " * depth
if isinstance(schema_object.items, list):
for item in schema_object.items:
print(f"{indent}Item: {item}")
if isinstance(item, SchemaObject):
print_items(item, depth + 1)
print(f"{indent}Example: {item.example}")
elif isinstance(schema_object.items, SchemaObject):
print(f"{indent}Item: {schema_object.items}")
print_items(schema_object.items, depth + 1)
print(f"{indent}Example: {schema_object.items.example}")
if __name__ == "__main__":
data = parser(path="~/code/git.threefold.info/hero/hero_server_python/baobabspecs")
spec_object = OpenRPCSpec.load(data)
server_generator = RestServerGenerator(spec_object, Path("/tmp/rest2"))
server_generator.generate()

View File

@@ -0,0 +1,7 @@
@app.post("/$circleguid/{{function_name}}")
def {{ function_name }}(circleguid: int, {{ rest_server_generator.get_method_params(method_params) }}){% if method_result %} -> {{ method_result }}{% endif %}:
{% if mock %}
return {{function_name}}_internal_mock(context, circleguid, {{', '.join(method_params.keys())}})
{% else %}
return {{function_name}}_internal(context, circleguid, {{', '.join(method_params.keys())}})
{% endif %}

View File

@@ -0,0 +1,16 @@
{% if mock %}
{% set suffix = '_mock' %}
{% else %}
{% set suffix = '' %}
{% endif %}
@app.get("/{circleguid}/{{variable_name}}_manager{{suffix}}/{{variable_name}}/{id}")
def {{variable_name}}_get(circleguid: int, id: str)-> {{type_name}}:
return {{variable_name}}_get_internal{{suffix}}(circleguid, id)
@app.post("/{circleguid}/{{variable_name}}_manager{{suffix}}/{{variable_name}}")
def {{variable_name}}_set(circleguid: int, {{variable_name}}: {{type_name}})-> bool:
return {{variable_name}}_set_internal{{suffix}}(circleguid, {{variable_name}})
@app.delete("/{circleguid}/{{variable_name}}_manager{{suffix}}/{{variable_name}}/{id}")
def {{variable_name}}_delete(circleguid: int, id: str)-> bool:
return {{variable_name}}_delete_internal{{suffix}}(circleguid, id)

View File

@@ -0,0 +1,12 @@
{% if mock %}
{% set suffix = '_mock' %}
{% else %}
{% set suffix = '' %}
{% endif %}
from fastapi import FastAPI
from pydantic import BaseModel, Field
from typing import List
from enum import Enum
{% if import_models %}from models import *{% endif %}
{% if import_crud %}from crud{{suffix}} import *{% endif %}
{% if import_openapi %}from open_api import *{% endif %}

View File

@@ -0,0 +1,9 @@
from typing import List, Optional, Dict, Union
from enum import Enum
def {{function_name}}(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)
pass

View File

@@ -0,0 +1,11 @@
def {{variable_name}}_get_internal(circleguid: int, id: str) -> {{type_name}}:
return {{type_name}}()
def {{variable_name}}_set_internal(circleguid: int, {{variable_name}}: {{type_name}})-> bool:
return True
def {{variable_name}}_delete_internal(circleguid: int, id: str)-> bool:
##would use osis to delete this objecc
return True

View File

@@ -0,0 +1,8 @@
def {{variable_name}}_get_internal_mock(circleguid: int, id: str) -> {{type_name}}:
return create_example_object({{type_name}})
def {{variable_name}}_set_internal_mock(circleguid: int, {{variable_name}}: {{type_name}})-> bool:
return True
def {{variable_name}}_delete_internal_mock(circleguid: int, id: str)-> bool:
return True

View File

@@ -0,0 +1,5 @@
import uvicorn
from open_api import app
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)

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
}
}

View File

@@ -0,0 +1,20 @@
@['/:{{function_name}}'; post]
pub fn (mut app App) {{ function_name }}() vweb.Result{
body := json2.raw_decode(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 {
app.set_status(400, 'Bad Request: ${err}')
return v_server_app.text('HTTP 400: Bad Request')
}
{% endif %}
{% endfor %}
res := {{function_name}}_internal({{', '.join(method_params.keys())}}) or {
app.set_status(500, '')
return app.text('HTTP 500: Internal Server Error')
}
return app.json(res)
}

View File

@@ -0,0 +1,6 @@
module main
pub fn {{function_name}}({{server_generator.get_method_params(method_params)}}) !{{method_result}}{
panic('to be implemented')
}

View File

@@ -0,0 +1,11 @@
module main
import vweb
struct App {}
fn main() {
app := &App{}
port := 8080
vweb.run(app, port)
}

View File

@@ -0,0 +1,49 @@
import os
from pathlib import Path
from jinja2 import Environment, FileSystemLoader
from heroserver.openrpc.model.openrpc_spec import OpenRPCSpec
script_dir = os.path.dirname(os.path.abspath(__file__))
env = Environment(loader=FileSystemLoader(script_dir))
class ServerGenerator:
def __init__(self, spec: OpenRPCSpec, dir: Path):
self.spec = spec
self.dir = dir
self.server_template = env.get_template("templates/server.jinja")
def generate(self):
self.dir.mkdir(parents=True, exist_ok=True)
self.generate_server()
self.generate_models()
self.generate_methods()
def generate_server(self):
code = self.server_template.render()
server_file_path = self.dir.joinpath("server.v")
with open(server_file_path, "w") as file:
file.write(f"{code}")
def generate_models():
pass
def generate_methods():
pass
if __name__ == "__main__":
from heroserver.openrpc.parser.parser import parser
# from heroserver.openrpc.generator.model_generator import ModelGenerator
data = parser(path="/root/code/git.threefold.info/hero_server/generatorexamples/mycelium_openrpc.yaml")
spec_object = OpenRPCSpec.load(data)
server_generator = ServerGenerator(spec_object, Path("/tmp/server3"))
server_generator.generate()