from __future__ import annotations from typing import Optional, List from dataclasses import dataclass from sys import stderr import util @dataclass class Field: name: str typeid: str # full type, e.g. Vec doc: str inner_type: str # inner type, e.g. if typeid is 'Vec', then inner_type is 'i32' deserializer: Optional[int] = None # depth of vectorness of i64 typ: Optional[Type] = None rename: Optional[str] = None def __post_init__(self): self.maybe_enable_optional() self.doc = util.escape_doc(self.doc) if self.name == 'type': self.name = 'type_' self.rename = 'type' def maybe_enable_optional(self): optional_heuristics = { 'may be null', 'only available to bots', 'bots only', 'or null' } for s in optional_heuristics: if s in self.doc: if self.deserializer is not None: raise ValueError("i64 cannot be optional") self.typeid = f"Option<{self.typeid}>" break def initialize_with_type(self, typ: Type): self.typ = typ class Type: def __init__(self, name: str, inner_doc: Optional[str]=None): self.name = name self.doc = util.escape_doc(inner_doc or '') self.exclude = False @property def is_excluded(self) -> bool: return self.exclude def render_as_decl(self): ''' Fully declare self, e.g. #[doc = ...] // for everything #[derive([De]Serialize)] // for types and classes only #[serde(tag = "@type")] // for classes only #[serde(rename_all = "camelCase)] // classes only ''' raise NotImplementedError() def render_as_param(self, param_name: str): ''' Render self as param, e.g. param_name: self.name ''' raise NotImplementedError() def render_as_field(self, field_name: str): ''' Render self as field, e.g. #[doc = ...] // for types only #[serde(rename = ...)] // for types and classes #[serde(deserialize_with = ...)] // for types only field_name: self.name, ''' raise NotImplementedError() def render_as_variant(self): ''' Just print `self.name(self.name),` ''' raise NotImplementedError() def rusty_name(self) -> str: ''' Sometimes, name should convert case for rust ''' raise NotImplementedError() class Enum(Type): def __init__(self, name, doc): Type.__init__(self, name, doc) self.deps = list() def render_as_decl(self): print(f'#[doc = "{self.doc}"]') print('#[derive(Serialize, Deserialize, Clone, Debug)]') print(f"pub enum {self.rusty_name()} {{") for dep in self.deps: if len(dep.deps) > 0: print(f"{dep.name}({dep.name}),") else: print(f"{dep.name},") print(f"}}") def render_as_param(self, param_name: str): print(f"{param_name}: {self.rusty_name()},") def render_as_field(self, field_name: str): print(f'#[serde(rename = {self.name})]') print(f'inline serde rename for {self.name}', file=stderr) print(f"{field_name}: {self.rusty_name()},") def rusty_name(self) -> str: return self.name[0].upper() + self.name[1:] def add_member(self, member: Struct): self.deps.append(member) @property def is_excluded(self): return self.exclude or self.name in util.ENUM_EXCLUDE_ALWAYS class Struct(Type): def __init__(self, name: str, doc: str, deserializer: Optional[str]=None): Type.__init__(self, name, doc) self.deserializer = deserializer self.deps: List[Field] = list() def render_as_decl(self): print(f'#[doc = "{self.doc}"]') print('#[derive(Serialize, Deserialize, Clone, Debug)]') print(f'pub struct {self.rusty_name()} {{') for field in self.deps: print(f'#[doc = "{field.doc}"]') if field.rename is not None: print(f"#[serde(rename = \"{field.rename}\")]") if field.deserializer is not None and not field.typ.is_excluded: print(f'#[serde(deserialize_with="deserialize_i64_{field.deserializer}")]') if field.typ.is_excluded: print(f'pub {field.name}: SerdeJsonValue,') else: print(f'pub {field.name}: {field.typeid},') print(f'}}') def render_as_variant(self): print('//rename?') print(f'{self.name}({self.name}),') def rusty_name(self) -> str: return self.name[0].upper() + self.name[1:] def add_field(self, field: Field): self.deps.append(field) @property def is_excluded(self): return self.exclude or self.name in util.STRUCT_EXCLUDE_ALWAYS class Scalar(Type): def render_as_decl(self): pass def render_as_variant(self): raise TypeError("Attempt to use scalar as variant") def rusty_name(self): return util.convert_scalar_name(self.name) def add_field(self, name: str, doc: str, typ: Type): raise TypeError("Attempt to add dependency to scalar") @property def is_excluded(self): return False class Method: def __init__(self, orig_name: str, ret: Type, docs: str): self.name = util.to_snake_case(orig_name) self.orig_name = orig_name self.params: List[Field] = list() self.ret = ret self.doc = util.escape_doc(docs) def add_param(self, field: Field): self.params.append(field) def render_as_decl(self): actual_ret = 'SerdeJsonValue' if self.ret.is_excluded else self.ret.name print(f'#[doc="{self.doc}"]') if len(self.params) > 0: print(f'#[doc=" \\n\\n"]') # ensure newline print(f'#[doc="parameters: "]') for param in self.params: print(f'#[doc=" * `{param.name}`: {param.doc}"]') if self.ret.is_excluded: print(f'#[doc=" \\n\\n"]') # ensure newline print(f'#[doc="Return type: `{self.ret.name}`"]') print(f'fn {self.name}(&self,') for param in self.params: typeid = 'SerdeJsonValue' if param.typ.is_excluded else param.typeid print(f'{param.name}: {typeid},') print(f') -> ResponseFuture<{actual_ret}> {{') print(f'self.send(json!({{') for param in self.params: name = param.rename or param.name print(f'"{param.name}": {name},') print(f'"@type": "{self.orig_name}"') print(f'}}))') print(f'}}')