{# Data enum definition with IntoValue/TryConvert via JSON #}
{% if has_data %}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
{% if serde_tag %}#[serde(tag = "{{ serde_tag }}")]
{% endif %}{% if serde_untagged %}#[serde(untagged)]
{% endif %}{% if serde_rename_all %}#[serde(rename_all = "{{ serde_rename_all }}")]
{% endif %}pub enum {{ enum_name }} {
{% for variant in variants %}
{% if variant.serde_rename %} #[serde(rename = "{{ variant.serde_rename }}")]
{% endif %}{% if variant.fields %}
{%- if variant.is_tuple and serde_untagged %} {{ variant.name }}({% for field in variant.fields %}{{ field.field_type }}{{ ", " if not loop.last }}{% endfor %}),
{%- else %} {{ variant.name }} { {% for field in variant.fields %}{{ field.name }}: {{ field.field_type }}{{ ", " if not loop.last }}{% endfor %} },
{%- endif %}
{% else %} {{ variant.name }},
{% endif %}{% endfor %}}
impl Default for {{ enum_name }} {
fn default() -> Self { Self::{{ default_variant }}{{ first_variant_default }} }
}
impl magnus::IntoValue for {{ enum_name }} {
fn into_value_with(self, handle: &Ruby) -> magnus::Value {
match serde_json::to_value(&self) {
Ok(v) => json_to_ruby(handle, v),
Err(_) => handle.qnil().into_value_with(handle),
}
}
}
impl magnus::TryConvert for {{ enum_name }} {
fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
// For data enums with fields (e.g., PageAction), try to deserialize from JSON first.
// For unit enums or when passed as a string, fall back to string-based conversion.
let json_str: String = if let Ok(s) = <String as magnus::TryConvert>::try_convert(val) {
s
} else {
val.funcall::<_, _, String>("to_json", ()).map_err(|e| {
magnus::Error::new(unsafe { Ruby::get_unchecked() }.exception_type_error(),
format!("no implicit conversion into {{ enum_name }}: {}", e))
})?
};
// Try deserializing as JSON first (handles JSON strings like "\"markdown\"" or "{\"click\":{\"selector\":\"...\"}}\"")
// If that fails, try treating it as a plain string value and wrap in quotes
// If both fail, try as Custom variant (for untagged enum support)
serde_json::from_str(&json_str)
.or_else(|_| serde_json::from_str(&format!("\"{json_str}\"")))
.or_else(|_| {
// Try as a JSON string for Custom variant (untagged enums accept any remaining value)
match serde_json::to_value(&json_str) {
Ok(val) => serde_json::from_value(val),
Err(e) => Err(e),
}
})
.map_err(|e| magnus::Error::new(unsafe { Ruby::get_unchecked() }.exception_type_error(), e.to_string()))
}
}
unsafe impl IntoValueFromNative for {{ enum_name }} {}
unsafe impl TryConvertOwned for {{ enum_name }} {}
{%- if has_data %}
{# Variant accessor methods for internally-tagged data enums #}
{%- if serde_tag %}
impl {{ enum_name }} {
{%- for variant in variants %}
{%- if variant.fields | length == 1 and variant.fields[0].name == "_0" %}
{# Internally-tagged tuple variant converted to struct variant with _0 field #}
pub fn {{ variant.snake_name }}(&self) -> Option<&{{ variant.fields[0].field_type }}> {
match self {
Self::{{ variant.name }} { _0 } => Some(_0),
_ => None,
}
}
{%- endif %}
{%- endfor %}
}
{%- endif %}
{%- endif %}
{# Unit enum definition with Symbol-based conversion #}
{% else %}
#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
{% if serde_rename_all %}#[serde(rename_all = "{{ serde_rename_all }}")]
{% endif %}pub enum {{ enum_name }} {
{% for variant in variants %}
{% if variant.serde_rename %} #[serde(rename = "{{ variant.serde_rename }}")]
{% endif %} {{ variant.name }},
{% endfor %}}
impl Default for {{ enum_name }} {
fn default() -> Self { Self::{{ default_variant }} }
}
impl magnus::IntoValue for {{ enum_name }} {
fn into_value_with(self, handle: &Ruby) -> magnus::Value {
let sym = match self {
{% for variant in variants %} {{ enum_name }}::{{ variant.name }} => "{{ variant.snake_name }}",
{% endfor %} };
handle.to_symbol(sym).into_value_with(handle)
}
}
impl magnus::TryConvert for {{ enum_name }} {
fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
let s: String = magnus::TryConvert::try_convert(val)?;
// Accept the serde wire name (snake_case), the PascalCase Rust variant name,
// and a lowercase fallback so fixtures written in any of those styles work.
match s.as_str() {
{% for variant in variants %} "{{ variant.snake_name }}"{% if variant.name != variant.snake_name %} | "{{ variant.name }}"{% endif %} => Ok({{ enum_name }}::{{ variant.name }}),
{% endfor %} other => Err(magnus::Error::new(
unsafe { Ruby::get_unchecked() }.exception_arg_error(),
format!("invalid {{ enum_name }} value: {other}"),
)),
}
}
}
unsafe impl IntoValueFromNative for {{ enum_name }} {}
unsafe impl TryConvertOwned for {{ enum_name }} {}
{% endif %}