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

View File

@@ -0,0 +1,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%}
}