refactor(eventsub): use variants for sum types (#5930)
This commit is contained in:
@@ -17,6 +17,7 @@ def _generate_implementation(header_path: str, items: list[Struct | Enum]) -> st
|
||||
doc = f"""// WARNING: This file is automatically generated. Any changes will be lost.
|
||||
#include "{header_path}"
|
||||
#include "twitch-eventsub-ws/chrono.hpp" // IWYU pragma: keep
|
||||
#include "twitch-eventsub-ws/detail/variant.hpp" // IWYU pragma: keep
|
||||
#include "twitch-eventsub-ws/detail/errors.hpp"
|
||||
#include <boost/json.hpp>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
from dataclasses import dataclass
|
||||
|
||||
import logging
|
||||
|
||||
@@ -54,6 +55,19 @@ def _is_trivially_copyable(type: clang.cindex.Type) -> bool:
|
||||
return _is_chrono_like_type(type)
|
||||
|
||||
|
||||
def _has_no_fields(type: clang.cindex.Type) -> bool:
|
||||
for _ in type.get_fields():
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@dataclass
|
||||
class VariantType:
|
||||
name: str
|
||||
trivial: bool
|
||||
empty: bool
|
||||
|
||||
|
||||
class Member:
|
||||
def __init__(
|
||||
self,
|
||||
@@ -68,6 +82,8 @@ class Member:
|
||||
self.type_name = type_name
|
||||
self.tag: Optional[str] = None
|
||||
self.trivial = trivial
|
||||
self.variant_types: list[VariantType] | None = None
|
||||
self.variant_fallback: str | None = None
|
||||
|
||||
self.dont_fail_on_deserialization: bool = False
|
||||
|
||||
@@ -124,7 +140,8 @@ class Member:
|
||||
overwrite_member_type = MemberType.OPTIONAL_VECTOR
|
||||
case other:
|
||||
log.warning(f"Vector cannot be added on top of other member type: {other}")
|
||||
|
||||
case "variant":
|
||||
overwrite_member_type = MemberType.VARIANT
|
||||
case other:
|
||||
log.warning(f"Unhandled template type: {other}")
|
||||
|
||||
@@ -144,8 +161,28 @@ class Member:
|
||||
|
||||
member.apply_comment_commands(comment_commands)
|
||||
|
||||
if member.member_type == MemberType.VARIANT:
|
||||
member.apply_variant(node.type, namespace)
|
||||
|
||||
return member
|
||||
|
||||
def apply_variant(self, type: clang.cindex.Type, namespace: tuple[str, ...]):
|
||||
self.variant_types = []
|
||||
for idx in range(type.get_num_template_arguments()):
|
||||
inner = type.get_template_argument_type(idx)
|
||||
name = get_type_name(inner, namespace)
|
||||
if name == "std::string" or name == "String":
|
||||
assert not self.variant_fallback
|
||||
self.variant_fallback = name
|
||||
continue
|
||||
self.variant_types.append(
|
||||
VariantType(
|
||||
name=name,
|
||||
trivial=_is_trivially_copyable(inner),
|
||||
empty=_has_no_fields(inner),
|
||||
)
|
||||
)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, self.__class__):
|
||||
return False
|
||||
|
||||
@@ -6,3 +6,4 @@ class MemberType(Enum):
|
||||
VECTOR = 2
|
||||
OPTIONAL = 3
|
||||
OPTIONAL_VECTOR = 4
|
||||
VARIANT = 5
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
{% if not field.tag -%}
|
||||
static_assert(false, "{{field.name}} doesn't have a json_tag");
|
||||
{%- endif %}
|
||||
const auto *jv{{field.name}}Tag = root.if_contains("{{field.tag}}");
|
||||
if (jv{{field.name}}Tag == nullptr)
|
||||
{
|
||||
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
|
||||
}
|
||||
|
||||
auto {{field.name}}TagRes = boost::json::try_value_to<boost::json::string>(*jv{{field.name}}Tag);
|
||||
if ({{field.name}}TagRes.has_error())
|
||||
{
|
||||
return {{field.name}}TagRes.error();
|
||||
}
|
||||
std::string_view {{field.name}}Tag = *{{field.name}}TagRes;
|
||||
decltype(std::declval<{{struct.full_name}}>().{{field.name}}) {{field.name}};
|
||||
{%- for type in field.variant_types %}
|
||||
|
||||
{%- if not loop.first %}else{% endif %} if ({{field.name}}Tag == {{type.name}}::TAG)
|
||||
{
|
||||
{%- if type.empty -%}
|
||||
{{field.name}}.emplace<{{type.name}}>();
|
||||
{%- else -%}
|
||||
const auto *{{field.name}}Val = root.if_contains(detail::fieldFor<{{type.name}}>());
|
||||
if (!{{field.name}}Val)
|
||||
{
|
||||
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
|
||||
}
|
||||
auto {{field.name}}{{type.name}} = boost::json::try_value_to<{{type.name}}>(*{{field.name}}Val);
|
||||
if ({{field.name}}{{type.name}}.has_error())
|
||||
{
|
||||
return {{field.name}}{{type.name}}.error();
|
||||
}
|
||||
{% if type.trivial -%}
|
||||
{{field.name}}.emplace<{{type.name}}>({{field.name}}{{type.name}}.value());
|
||||
{%- else -%}
|
||||
{{field.name}}.emplace<{{type.name}}>(std::move({{field.name}}{{type.name}}.value()));
|
||||
{%- endif %}
|
||||
{%- endif -%}
|
||||
}
|
||||
{%- endfor -%}
|
||||
else
|
||||
{
|
||||
{%- if field.variant_fallback -%}
|
||||
{{field.name}}.emplace<{{field.variant_fallback}}>({{field.name}}Tag);
|
||||
{%- else -%}
|
||||
EVENTSUB_BAIL_HERE(error::Kind::UnknownVariant);
|
||||
{%- endif -%}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
.{{field.name}} = std::move({{field.name}}),
|
||||
@@ -1,5 +1,5 @@
|
||||
boost::json::result_for<{{struct.full_name}}, boost::json::value>::type tag_invoke(
|
||||
boost::json::try_value_to_tag<{{struct.full_name}}> /* tag */, const boost::json::value &jvRoot)
|
||||
boost::json::try_value_to_tag<{{struct.full_name}}> /* tag */, const boost::json::value &{%- if struct.members|length -%}jvRoot{%- else -%}/* jvRoot */{%- endif -%})
|
||||
{
|
||||
{% if struct.inner_root %}
|
||||
if (!jvRoot.is_object())
|
||||
@@ -38,6 +38,8 @@ boost::json::result_for<{{struct.full_name}}, boost::json::value>::type tag_invo
|
||||
{% include 'field-optional.tmpl' indent content %}
|
||||
{%- elif field.member_type == MemberType.OPTIONAL_VECTOR -%}
|
||||
{% include 'field-optional-vector.tmpl' indent content %}
|
||||
{%- elif field.member_type == MemberType.VARIANT -%}
|
||||
{% include 'field-variant.tmpl' indent content %}
|
||||
{%- endif -%}
|
||||
{% endfor %}
|
||||
|
||||
@@ -51,6 +53,8 @@ boost::json::result_for<{{struct.full_name}}, boost::json::value>::type tag_invo
|
||||
{% include 'initializer-optional.tmpl' indent content %}
|
||||
{%- elif field.member_type == MemberType.OPTIONAL_VECTOR -%}
|
||||
{% include 'initializer-optional-vector.tmpl' indent content %}
|
||||
{%- elif field.member_type == MemberType.VARIANT -%}
|
||||
{% include 'initializer-variant.tmpl' indent content %}
|
||||
{%- endif -%}
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
@@ -68,6 +68,8 @@ class Walker:
|
||||
if type is None:
|
||||
# Skip nodes without a type
|
||||
return False
|
||||
if node.storage_class == clang.cindex.StorageClass.STATIC:
|
||||
return False
|
||||
|
||||
# log.debug(f"{struct}: {type.spelling} {node.spelling} ({type.kind})")
|
||||
if struct:
|
||||
|
||||
Reference in New Issue
Block a user