...
This commit is contained in:
177
_archive/openrpc/generator/code/golang/golang_code_generator.py
Normal file
177
_archive/openrpc/generator/code/golang/golang_code_generator.py
Normal 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()
|
@@ -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 %}
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
package {{package_name}}
|
||||
{% for item in imports %}
|
||||
import "{{item}}"
|
||||
{%- endfor %}
|
||||
|
@@ -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%}
|
||||
}
|
97
_archive/openrpc/generator/code/lang_code_generator.py
Normal file
97
_archive/openrpc/generator/code/lang_code_generator.py
Normal 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
|
205
_archive/openrpc/generator/code/python/python_code_generator.py
Normal file
205
_archive/openrpc/generator/code/python/python_code_generator.py
Normal 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()
|
@@ -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 %}
|
18
_archive/openrpc/generator/code/python/templates/enum.jinja
Normal file
18
_archive/openrpc/generator/code/python/templates/enum.jinja
Normal 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 %}
|
||||
) #}
|
@@ -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 -%}
|
@@ -0,0 +1,5 @@
|
||||
from typing import List, Optional, Union, Any, Dict
|
||||
from pydantic import BaseModel, Field
|
||||
from enum import Enum
|
||||
import requests
|
||||
|
205
_archive/openrpc/generator/code/vlang/handler_generator.py
Normal file
205
_archive/openrpc/generator/code/vlang/handler_generator.py
Normal 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()
|
@@ -0,0 +1,9 @@
|
||||
pub enum {{ type_name }}{
|
||||
{% for elem in enum -%}
|
||||
{% if is_integer -%}
|
||||
{{ number_to_words(elem) }} = {{ elem }}
|
||||
{% else -%}
|
||||
{{ elem }}
|
||||
{% endif -%}
|
||||
{% endfor %}
|
||||
}
|
@@ -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}')
|
||||
}
|
||||
}
|
@@ -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}')
|
||||
}
|
||||
}
|
@@ -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}')
|
||||
}
|
@@ -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')
|
||||
}
|
@@ -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}}'
|
||||
})!
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,5 @@
|
||||
pub struct {{method_param_struct_name}}{
|
||||
{% for param_name, param_type in params.items()%}
|
||||
{{param_name}} {{param_type}}
|
||||
{%- endfor %}
|
||||
}
|
@@ -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 %}
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
module {{module_name}}
|
||||
{% for item in imports %}
|
||||
import {{item}}
|
||||
{%- endfor %}
|
||||
|
10
_archive/openrpc/generator/code/vlang/templates/struct.jinja
Normal file
10
_archive/openrpc/generator/code/vlang/templates/struct.jinja
Normal 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 %}
|
||||
}
|
164
_archive/openrpc/generator/code/vlang/vlang_code_generator.py
Normal file
164
_archive/openrpc/generator/code/vlang/vlang_code_generator.py
Normal 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()
|
Reference in New Issue
Block a user