def walk_array(self, d, name): typ = self.walk(d["items"], name=name) name = go.goname(name) array = self.resolve_file(d).newtype(go.goname(name), type=typ.slice, comment=d.get("description")) if self.is_pointer(d): array = array.pointer return array
def emit_enum(name, schema, m): typename = go.goname(name) m.stmt('// {} : {}'.format(typename, schema.get("description", "").split("\n", 1)[0])) m.stmt("type {} string".format(typename)) m.sep() with m.const_group() as cm: for e in schema["enum"]: itemname = '{}{}{}'.format( typename, go.goname(e), "Reversed" if e.startswith("-") else "" ) cm('// {} :'.format(itemname)) cm('{} = "{}"'.format(itemname, e))
def emit_enum(name, schema, m): typename = go.goname(name) m.stmt('// {} : {}'.format(typename, schema.get("description", "").split("\n", 1)[0])) m.stmt("type {} string".format(typename)) m.sep() with m.const_group() as cm: for e in schema["enum"]: itemname = '{}{}{}'.format(typename, go.goname(e), "Reversed" if e.startswith("-") else "") cm('// {} :'.format(itemname)) cm('{} = "{}"'.format(itemname, e))
def walk_object(self, d, name): name = go.goname(name) struct = self.resolve_file(d).struct(name, comment=d.get("description")) for name, prop in (d.get("properties") or {}).items(): typ = self.walk(prop, name=name) if getattr(typ, "fullname", "") == struct.fullname: typ = typ.pointer struct.define_field(go.goname(name), typ, comment=prop.get("description"), tag=self.resolve_tag(name)) if self.is_pointer(d): struct = struct.pointer return struct
def run() -> Module: r = get_resolver() m = Module(indent="\t") classes = [Person, Person2] for item in walk(classes): m.stmt(f"type {goname(item.cls.__name__)} struct {{") with m.scope(): for name, typeinfo, _metadata in item.fields: metadata = t.cast(Metadata, _metadata) if metadata.get("default") == MISSING: metadata.pop("default") try: gotype = r.resolve_gotype( typeinfo.normalized) # todo: pointer except KeyError: gotype = goname(typeinfo.normalized.__name__) if metadata.get("pointer", False): gotype = f"*{gotype}" if metadata.get("inline", False): m.append(gotype) else: m.append(f"{goname(name)} {gotype}") if metadata: m.stmt(f" // {metadata}") else: m.stmt("") m.stmt("}") m.sep() return m
def emit_union(m: Module, item: Item, *, resolver: Resolver) -> Definition: typename = goname(item.type_.__name__) kind_typename = typename + "Kind" # type <typename> { # Kind string `json:"$kind"` # ... # } m.stmt(f"type {typename} struct {{") with m.scope(): m.stmt(f'Kind {kind_typename} `json:"$kind"` // discriminator') for subtype in item.args: gotype: str = resolver.resolve_gotype(subtype) m.append(f"{gotype} *{gotype}") m.stmt( f' `json:"{untitleize(str(gotype)).rstrip("_")},omitempty"`') m.stmt("}") m.sep() # UnmarshalJSON discriminator_field = ("$kind", typeinfo.typeinfo(str), metadata()) discriminator_field[-1]["_override_type"] = kind_typename pseudo_fields = [(sub_type.__name__, typeinfo.typeinfo(sub_type), metadata(required=False)) for sub_type in item.args] pseudo_item = Item( type_=item.type_, fields=[discriminator_field] + pseudo_fields, args=[], ) unmarshalJSON_definition = emit_unmarshalJSON(m, pseudo_item, resolver=resolver) m.sep() # one-of validation assert unmarshalJSON_definition.code_module is not None this = m.symbol(f"{item.type_.__name__[0].lower()}") maperr_pkg = m.import_("github.com/podhmo/maperr") sm = unmarshalJSON_definition.code_module sm.stmt("// one-of?") sm.stmt("{") with sm.scope(): for go_name, info, _ in pseudo_item.fields[1:]: with sm.if_( f'{this}.Kind == "{go_name}" && {this}.{go_name} == nil'): sm.stmt( f'err = err.Add("{go_name}", {maperr_pkg}.Message{{Text: "treated as {go_name}, but no data"}})' ) sm.stmt("}") # enums emit_enums(m, item.type_, resolver=resolver, name=kind_typename) definition = Definition(name=typename, code_module=None) return definition
def walk_enum(self, d, typ, name): name = go.goname(name) enum = self.resolve_file(d).enum(name, typ, comment=d.get("description")) for x in d["enum"]: enum.define_member(x, x) return enum
def resolve_gotype( self, typ: t.Type[t.Any], *, _none_type: t.Type[t.Any] = type(None), ) -> str: """e.g. str -> 'string' """ if hasattr(typ, "__origin__"): origin = typ.__origin__ args = typing_get_args(typ) if origin == dict: k = self.resolve_gotype(args[0]) v = self.resolve_gotype(args[1]) return f"map[{k}]{v}" elif origin == list or origin == tuple: v = self.resolve_gotype(args[0]) return f"[]{v}" elif origin == GoPointer or ( origin == t.Union and len(args) == 2 and _none_type in args ): v = self.resolve_gotype(args[0]) return f"*{v}" else: name = resolve_name_maybe(typ) if name is not None: return name raise RuntimeError(f"unexpected origin {origin!r}") gotype = self.gotype_map.get(typ) if gotype is not None: return gotype pkg = get_gopackage(typ) prefix = "" if pkg is not None: prefix = f"{self.m.import_(pkg)}." py_clsname = getattr(typ, "__qualname__", typ.__name__) if ( "<locals>" in py_clsname ): # HACK: for the type defined in closure. (e.g. t.NewType) py_clsname = typ.__name__ if "." in py_clsname: typename = "_".join([goname(x) for x in py_clsname.split(".")]) else: typename = goname(py_clsname) return f"{prefix}{typename}"
def emit_object(self, d, name): m = self.m.submodule() structname = go.goname(name) m.comment("{} : {}".format(structname, d.get("description", ""))) with m.type_(structname, "struct"): for name, prop in d["properties"].items(): if "description" in prop: m.comment("{} : {}".format(go.goname(name), prop.get("description", ""))) if "$ref" in prop: typename = self.emit_ref(prop["$ref"]) if self.nullable[prop["$ref"]]: typename = "*{}".format(typename) else: typename = self.resolve_type(prop) if prop.get("x-nullable", False): typename = "*{}".format(typename) m.stmt('{} {}{}`'.format(go.goname(name), typename, self.resolve_tag(name))) return structname
def emit_object(self, d, name): m = self.m.submodule() structname = go.goname(name) m.comment("{} :{}".format(structname, d.get("description", ""))) with m.type_(structname, "struct"): for name, prop in d["properties"].items(): if "description" in prop: m.comment("{} :{}".format(go.goname(name), prop.get("description", ""))) if "$ref" in prop: typename = self.emit_ref(prop["$ref"]) if self.nullable[prop["$ref"]]: typename = "*{}".format(typename) else: typename = self.resolve_type(prop) if prop.get("x-nullable", False): typename = "*{}".format(typename) m.stmt('{} {}{}`'.format(go.goname(name), typename, self.resolve_tag(name))) return structname
def emit_array(self, d, name): m = self.m.submodule() arrname = go.goname(name) item = d["items"] if "$ref" in item: typename = self.emit_ref(item["$ref"]) else: typename = self.resolve_type(item) m.comment("{} :{}".format(arrname, d.get("description", ""))) m.stmt("type {} []{}".format(arrname, typename)) m.sep() return arrname
def emit_array(self, d, name): m = self.m.submodule() arrname = go.goname(name) item = d["items"] if "$ref" in item: typename = self.emit_ref(item["$ref"]) else: typename = self.resolve_type(item) m.comment("{} :{}".format(arrname, d.get("description", ""))) m.stmt("type {} []{}".format(arrname, typename)) m.sep() return arrname
def emit_union(w: walker.Walker, item: walker.Item) -> runtime.Definition: m = w.m ctx = w.ctx resolver = w.resolver typename = goname(item.name) kind_typename = typename + "Kind" # type <typename> { # Kind string `json:"$kind"` # ... # } m.stmt(f"type {typename} struct {{") with m.scope(): m.stmt(f'Kind {kind_typename} `json:"$kind"` // discriminator') for subtype in item.args: gotype: str = resolver.resolve_gotype(subtype) m.append(f"{gotype} *{gotype}") m.stmt( f' `json:"{untitleize(str(gotype)).rstrip("_")},omitempty"`') m.stmt("}") m.sep() # UnmarshalJSON pseudo_item = ctx.create_pseudo_item(item, discriminator_name=kind_typename) unmarshalJSON_definition = emit_unmarshalJSON(w, pseudo_item) m.sep() # one-of validation assert unmarshalJSON_definition.code_module is not None this = m.symbol(f"{item.name[0].lower()}") maperr_pkg = m.import_("github.com/podhmo/maperr") sm = unmarshalJSON_definition.code_module sm.stmt("// one-of?") sm.stmt("{") with sm.scope(): for go_name, info, _ in pseudo_item.fields[1:]: with sm.if_( f'{this}.Kind == "{go_name}" && {this}.{go_name} == nil'): sm.stmt( f'err = err.Add("{go_name}", {maperr_pkg}.Message{{Text: "treated as {go_name}, but no data"}})' ) sm.stmt("}") # enums emit_enums(w, item.type_, name=kind_typename) return runtime.Definition(name=typename, code_module=None)
def emit_simple_type_definition(ctx: Context, cls: t.Type[t.Any], *, m=None) -> None: """ e.g. // Team ... type Team struct { Name string `json:"name"` Members [][]Person `json:"members"` } """ m = m or ctx.m typename = goname(cls.__name__) m.stmt(f"// {typename} ...") m.stmt(f"type {typename} struct {{") with m.scope(): for name, info, metata in ctx.w.for_type(cls).walk(): m.stmt(f"""{goname(name)} {detect_gotype(info)} `json:"{name}"`""") m.stmt("}")
def detect_gotype(info: typeinfo.TypeInfo) -> str: if isinstance(info, typeinfo.Container): if info.container in ("list", "tuple", "set"): assert len(info.args) == 1 return f"[]{detect_gotype(info.args[0])}" elif info.container == "dict": assert len(info.args) == 2 return f"map[{detect_gotype(info.args[0])}]{detect_gotype(info.args[1])}" else: # Atom typ = info.underlying if issubclass(typ, str): return "string" elif issubclass(typ, bool): return "bool" elif issubclass(typ, int): return "int" # todo: int64 or ... something elif issubclass(typ, float): return "float" return goname(info.normalized.__name__)
def write_parse_method(self, enum, file, m): fmt = m.import_("fmt") @file.func(goname("Parse" + titleize(enum.name)), args=enum.type(enum.shortname), returns=enum, comment="parse", nostore=True) def parse(m): s = enum.shortname with m.switch(s) as sw: for name, _, value, _ in enum.members.values(): with sw.case(tostring(value)): sw.return_(enum.varname(name)) with sw.default(): sw.stmt('panic({})'.format( fmt.Sprintf( "unexpected {} %v, in parse()".format(enum.name), _noencoded(s)))) return m self.writer.write_function(parse, file, m)
def emit(classes: t.List[t.Type[t.Any]]) -> Module: m = gofile("main") r = get_resolver(m) for item in walk(classes): this = m.symbol(f"{item.type_.__name__[0].lower()}") this_type = f"{r.resolve_gotype(item.type_)}" this_type_pointer = f"*{this_type}" # func (ob *Ob) UnmarshalJSON(b []byte) error { b = m.symbol("b") m.stmt( f"func ({this} {this_type_pointer}) UnmarshalJSON({b} []byte) error {{" ) with m.scope(): # var err *maperr.Error err = m.symbol("err") maperr_pkg = m.import_("github.com/podhmo/maperr") m.stmt(f"var {err} *{maperr_pkg}.Error") m.sep() # var inner struct { # ... # } m.stmt("// loading internal data") inner = m.symbol("inner") m.stmt(f"var {inner} struct {{") with m.scope(): for name, typeinfo, metadata in item.fields: if name.startswith("_"): continue # xxx: gotype: str = r.resolve_gotype(typeinfo.raw) m.append(f'{goname(name)} *{gotype} `json:"{name}"`') m.stmt("// required" if metadata["required"] else "") m.stmt("}") # if rawErr := json.Unmarshal(b, &inner); rawErr != nil { # ... # } json_pkg = m.import_("encoding/json") raw_err = m.symbol("rawErr") with m.if_( f"{raw_err} := {json_pkg}.Unmarshal(b, &{inner}); {raw_err} != nil" ): m.return_(err.addSummary(raw_err.Error())) m.sep() # if <field> != nil { # ob.<field> = *<field> # } else { # m.add(<field>, "required") # } m.stmt("// binding field value and required check") for name, typeinfo, metadata in item.fields: field = m.symbol(goname(name)) with m.if_(f"{inner}.{field} != nil"): m.stmt(f"{this}.{field} = *{inner}.{field}") if metadata["required"]: with m.else_(): m.stmt(f'{err} = err.Add("{name}", "required")') m.sep() # return err.Untyped() m.return_(err.Untyped()) m.stmt("}") return m
def enum(self, name, type, comment=None): name = goname(name) enum = Enum(name, file=self, type=type, comment=comment) self.enums[name] = enum return enum
def newtype(self, name, type, comment=None): name = goname(name) newtype = Newtype(name, file=self, type=type, comment=comment) self.newtypes[name] = newtype return newtype
def varname(self, name): return goname("{}{}".format(self.name, titleize(name)))
def emit_unmarshalJSON(w: walker.Walker, item: walker.Item) -> runtime.Definition: m = w.m ctx = w.ctx resolver = w.resolver this = m.symbol(f"{item.name[0].lower()}") this_type = f"{resolver.resolve_gotype(item.type_)}" this_type_pointer = f"*{this_type}" # func (ob *Ob) UnmarshalJSON(b []byte) error { b = m.symbol("b") m.stmt( f"func ({this} {this_type_pointer}) UnmarshalJSON({b} []byte) error {{" ) with m.scope(): # var err *maperr.Error err = m.symbol("err") maperr_pkg = m.import_("github.com/podhmo/maperr") m.stmt(f"var {err} *{maperr_pkg}.Error") m.sep() # var inner struct { # ... # } m.stmt("// loading internal data") inner = m.symbol("inner") m.stmt(f"var {inner} struct {{") with m.scope(): for name, info, metadata in item.fields: if name.startswith("_"): continue # xxx: raw_type = ctx.raw_type_map.get(info) or info.raw if has_reference(info): json_pkg = m.import_("encoding/json") gotype = str(json_pkg.RawMessage) else: gotype = resolver.resolve_gotype(raw_type) m.append(f'{goname(name)} *{gotype} `json:"{name}"`') m.stmt("// required" if metadata["required"] else "") m.stmt("}") # if rawErr := json.Unmarshal(b, &inner); rawErr != nil { # ... # } json_pkg = m.import_("encoding/json") raw_err = m.symbol("rawErr") with m.if_( f"{raw_err} := {json_pkg}.Unmarshal(b, &{inner}); {raw_err} != nil" ): m.return_(err.AddSummary(raw_err.Error())) m.sep() # if <field> != nil { # ob.<field> = *<field> # } else { # m.add(<field>, "required") # } rawerr = m.symbol("rawerr") m.stmt("// binding field value and required check") with m.block(): for name, info, metadata in item.fields: field = m.symbol(goname(name)) with m.if_(f"{inner}.{field} != nil"): if has_reference(info): if info.is_optional or info in ctx.raw_type_map: # pointer raw_type = ctx.raw_type_map.get(info) or info.raw level = max(1, _unwrap_pointer_type(raw_type)[1]) gotype = resolver.resolve_gotype(info.type_) # NOTE: tricky code # # when *X (1 level), generated code: # ob.<attr> = &X{} # when **X (2 level), generated code: # v0 := &X{} # ob.<attr> = &v0 # when ***X (3 level), generated code: # v0 := &X{} # v1 := &v0 # ob.<attr> = &v1 syms = [(":=", f"{gotype}{{}}")] for i in range(level - 1): syms.append((":=", f"v{i}")) syms.append(("=", f"{this}.{goname(name)}")) for i in range(1, len(syms)): _, rhs = syms[i - 1] op, lhs = syms[i] m.stmt(f"{lhs} {op} &{rhs}") ref = f"{this}.{field}" elif (info.is_container and info.args and info.container_type != "union"): gotype = resolver.resolve_gotype(info.type_) m.stmt(f"{this}.{goname(name)} = {gotype}{{}}") ref = f"&{this}.{field}" else: ref = f"&{this}.{field}" with m.if_( f"{rawerr} := json.Unmarshal(*{inner}.{field}, {ref}); {rawerr} != nil" ): m.stmt( f'{err} = {err}.Add("{name}", {maperr_pkg}.Message{{Error: {rawerr}}})' ) else: m.stmt(f"{this}.{field} = *{inner}.{field}") if metadata["required"]: with m.else_(): m.stmt( f'{err} = err.Add("{name}", {maperr_pkg}.Message{{Text: "required"}})' ) m.sep() # NOTE: for injecting code from extrnal area code_module = m.submodule("", newline=False) # return err.Untyped() m.return_(err.Untyped()) m.stmt("}") return runtime.Definition(name="UnmarshalJSON", code_module=code_module)
def emit_unmarshalJSON(m: Module, item: Item, *, resolver: Resolver) -> Definition: this = m.symbol(f"{item.type_.__name__[0].lower()}") this_type = f"{resolver.resolve_gotype(item.type_)}" this_type_pointer = f"*{this_type}" # func (ob *Ob) UnmarshalJSON(b []byte) error { b = m.symbol("b") m.stmt( f"func ({this} {this_type_pointer}) UnmarshalJSON({b} []byte) error {{" ) with m.scope(): # var err *maperr.Error err = m.symbol("err") maperr_pkg = m.import_("github.com/podhmo/maperr") m.stmt(f"var {err} *{maperr_pkg}.Error") m.sep() # var inner struct { # ... # } m.stmt("// loading internal data") inner = m.symbol("inner") m.stmt(f"var {inner} struct {{") with m.scope(): for name, info, metadata in item.fields: if name.startswith("_"): continue # xxx: if "_override_type" in metadata: gotype: str = metadata["_override_type"] elif has_class_object(info): json_pkg = m.import_("encoding/json") gotype: str = str(json_pkg.RawMessage) else: gotype: str = resolver.resolve_gotype(info.raw) m.append(f'{goname(name)} *{gotype} `json:"{name}"`') m.stmt("// required" if metadata["required"] else "") m.stmt("}") # if rawErr := json.Unmarshal(b, &inner); rawErr != nil { # ... # } json_pkg = m.import_("encoding/json") raw_err = m.symbol("rawErr") with m.if_( f"{raw_err} := {json_pkg}.Unmarshal(b, &{inner}); {raw_err} != nil" ): m.return_(err.AddSummary(raw_err.Error())) m.sep() # if <field> != nil { # ob.<field> = *<field> # } else { # m.add(<field>, "required") # } rawerr = m.symbol("rawerr") m.stmt("// binding field value and required check") with m.block(): for name, info, metadata in item.fields: field = m.symbol(goname(name)) with m.if_(f"{inner}.{field} != nil"): if has_class_object(info): # pointer if info.is_optional: gotype: str = resolver.resolve_gotype( info.normalized) m.stmt(f"{this}.{goname(name)} = &{gotype}{{}}") ref = f"{this}.{field}" elif hasattr(info, "args"): # xxx gotype: str = resolver.resolve_gotype( info.normalized) m.stmt(f"{this}.{goname(name)} = {gotype}{{}}") ref = f"&{this}.{field}" else: ref = f"&{this}.{field}" with m.if_( f"{rawerr} := json.Unmarshal(*{inner}.{field}, {ref}); {rawerr} != nil" ): m.stmt( f'{err} = {err}.Add("{name}", {maperr_pkg}.Message{{Error: {rawerr}}})' ) else: m.stmt(f"{this}.{field} = *{inner}.{field}") if metadata["required"]: with m.else_(): m.stmt( f'{err} = err.Add("{name}", {maperr_pkg}.Message{{Text: "required"}})' ) m.sep() # NOTE: for injecting code from extrnal area code_module = m.submodule("", newline=False) # return err.Untyped() m.return_(err.Untyped()) m.stmt("}") return Definition(name="UnmarshalJSON", code_module=code_module)
# if rawErr := json.Unmarshal(b, &inner); rawErr != nil { # ... # } json_pkg = m.import_("encoding/json") raw_err = m.symbol("rawErr") with m.if_( f"{raw_err} := {json_pkg}.Unmarshal(b, &{inner}); {raw_err} != nil" ): m.return_(err.addSummary(raw_err.Error())) m.sep() # if <field> != nil { # ob.<field> = *<field> # } else { # m.add(<field>, "required") # } m.stmt("// binding field value and required check") for name, typeinfo, metadata in item.fields: field = m.symbol(goname(name)) with m.if_(f"{field} != nil"): m.stmt(f"{this}.{field} = *{field}") with m.else_(): m.stmt(f'{err} = err.Add("{name}", "required")') m.sep() # return err.Untyped() m.return_(err.Untyped()) m.stmt("}") print(m)