Skip to content

Commit be11320

Browse files
committed
Add support for component variant type
1 parent f623c89 commit be11320

File tree

4 files changed

+139
-3
lines changed

4 files changed

+139
-3
lines changed

ext/src/ruby_api/component/convert.rs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,16 @@ use magnus::{prelude::*, value, Error, IntoValue, RArray, RClass, RHash, RString
99
use wasmtime::component::{Type, Val};
1010

1111
define_rb_intern!(
12+
// For Component::Result
1213
OK => "ok",
1314
ERROR => "error",
1415
IS_ERROR => "error?",
1516
IS_OK => "ok?",
17+
18+
// For Component::Variant
19+
NEW => "new",
20+
NAME => "name",
21+
VALUE => "value",
1622
);
1723

1824
pub(crate) fn component_val_to_rb(val: Val, _store: &StoreContextValue) -> Result<Value, Error> {
@@ -54,7 +60,18 @@ pub(crate) fn component_val_to_rb(val: Val, _store: &StoreContextValue) -> Resul
5460
}
5561
Ok(array.into_value())
5662
}
57-
Val::Variant(_kind, _val) => not_implemented!("Variant not implemented"),
63+
Val::Variant(kind, val) => {
64+
let ruby = Ruby::get().unwrap();
65+
let payload = match val {
66+
Some(val) => component_val_to_rb(*val, _store)?,
67+
None => ruby.qnil().into_value(),
68+
};
69+
70+
variant_class(&ruby).funcall(
71+
NEW.into_id_with(&ruby),
72+
(kind.into_value_with(&ruby), payload),
73+
)
74+
}
5875
Val::Enum(kind) => Ok(kind.as_str().into_value()),
5976
Val::Option(val) => match val {
6077
Some(val) => Ok(component_val_to_rb(*val, _store)?),
@@ -168,7 +185,42 @@ pub(crate) fn rb_to_component_val(
168185

169186
Ok(Val::Tuple(vals))
170187
}
171-
Type::Variant(_variant) => not_implemented!("Variant not implemented"),
188+
Type::Variant(variant) => {
189+
let ruby = Ruby::get().unwrap();
190+
191+
let name: RString = value.funcall(NAME.into_id_with(&ruby), ())?;
192+
let name = name.to_string()?;
193+
194+
let case = variant
195+
.cases()
196+
.find(|case| case.name == name.as_str())
197+
.ok_or_else(|| {
198+
error!(
199+
"invalid variant case \"{}\", valid cases: {:?}",
200+
name,
201+
RArray::from_iter(variant.cases().map(|c| c.name))
202+
)
203+
})?;
204+
205+
let payload_rb: Value = value.funcall(VALUE.into_id_with(&ruby), ())?;
206+
let payload_val = match (&case.ty, payload_rb.is_nil()) {
207+
(Some(ty), _) => rb_to_component_val(payload_rb, _store, ty)
208+
.map(|val| Some(Box::new(val)))
209+
.map_err(|e| e.append(format!(" (variant value for \"{}\")", &name))),
210+
211+
// case doesn't have payload and Variant#value *is nil*
212+
(None, true) => Ok(None),
213+
214+
// case doesn't have payload and Variant#value *is not nil*
215+
(None, false) => err!(
216+
"expected no value for variant case \"{}\", got {}",
217+
&name,
218+
payload_rb.inspect()
219+
),
220+
}?;
221+
222+
Ok(Val::Variant(name, payload_val))
223+
}
172224
Type::Enum(_) => {
173225
let rstring = RString::try_convert(value)?;
174226
rstring.to_string().map(Val::Enum)
@@ -235,6 +287,12 @@ fn result_class(ruby: &Ruby) -> RClass {
235287
ruby.get_inner(&RESULT_CLASS)
236288
}
237289

290+
fn variant_class(ruby: &Ruby) -> RClass {
291+
static VARIANT_CLASS: Lazy<RClass> =
292+
Lazy::new(|ruby| component_namespace(ruby).const_get("Variant").unwrap());
293+
ruby.get_inner(&VARIANT_CLASS)
294+
}
295+
238296
pub fn init(ruby: &Ruby) -> Result<(), Error> {
239297
// Warm up
240298
let _ = result_class(ruby);
@@ -243,5 +301,10 @@ pub fn init(ruby: &Ruby) -> Result<(), Error> {
243301
let _ = IS_ERROR;
244302
let _ = IS_OK;
245303

304+
let _ = result_class(ruby);
305+
let _ = NEW;
306+
let _ = NAME;
307+
let _ = VALUE;
308+
246309
Ok(())
247310
}

lib/wasmtime/component.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,51 @@ class UncheckedResult < Wasmtime::Error; end
8282
# `.error` is used over `.new`.
8383
private :initialize
8484
end
85+
86+
# Represents a value for component model's variant case.
87+
# A variant case has a name that uniquely identify the case within the
88+
# variant and optionally a value.
89+
#
90+
# @example Constructing variants
91+
# # Given the following variant:
92+
# # variant filter {
93+
# # all,
94+
# # none,
95+
# # lt(u32),
96+
# # }
97+
#
98+
# Variant.new("all")
99+
# Variant.new("none")
100+
# Variant.new("lt", 100)
101+
class Variant
102+
# The name of the variant case
103+
# @return [String]
104+
attr_reader :name
105+
106+
# The optional payload of the variant case
107+
# @return [Object]
108+
attr_reader :value
109+
110+
# @param name [String] the name of variant case
111+
# @param value [Object] the optional payload of the variant case
112+
def initialize(name, value = nil)
113+
@name = name
114+
@value = value
115+
end
116+
117+
def ==(other)
118+
eql?(other)
119+
end
120+
121+
def eql?(other)
122+
self.class == other.class &&
123+
name == other.name &&
124+
value == other.value
125+
end
126+
127+
def hash
128+
[self.class, @name, @value].hash
129+
end
130+
end
85131
end
86132
end

spec/unit/component/convert_spec.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ module Component
2828
["list", [1, 2, 2**32 - 1]], # list<u32>
2929
["record", {"x" => 1, "y" => 2}],
3030
["tuple", [1, "foo"]], # tuple<u32, string>
31-
# TODO variant
31+
["variant", Variant.new("all"), Variant.new("lt", 12)],
3232
["enum", "l"],
3333
["option", 0, nil], # option<u32>
3434
["result", Result.ok(1), Result.error(2)], # result<u32, u32>
@@ -84,6 +84,8 @@ module Component
8484
["record", {"x" => 1}, /struct field missing: y/],
8585
["record", nil, /no implicit conversion of NilClass into Hash/],
8686
["tuple", nil, /no implicit conversion of NilClass into Array/],
87+
["variant", Variant.new("no"), /invalid variant case "no", valid cases: \["all", "none", "lt"\]/],
88+
["variant", Variant.new("lt", "nah"), /(variant value for "lt")/],
8789
["enum", "no", /enum variant name `no` is not valid/],
8890
["result", nil, /undefined method `ok\?/],
8991
["result-unit", Result.ok(""), /expected nil for result<_, E> ok branch/],

spec/unit/component/variant_spec.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
require "spec_helper"
2+
3+
module Wasmtime
4+
module Component
5+
RSpec.describe Variant do
6+
it "has name" do
7+
expect(Variant.new("a", 1).name).to eq("a")
8+
end
9+
10+
it "has value" do
11+
expect(Variant.new("a", 1).value).to eq(1)
12+
end
13+
14+
it "behaves like a value object" do
15+
expect(Variant.new("a", 1)).to eq(Variant.new("a", 1))
16+
expect(Variant.new("a", 1).hash).to eq(Variant.new("a", 1).hash)
17+
18+
expect(Variant.new("a")).not_to eq(Variant.new("b"))
19+
expect(Variant.new("a", 1)).not_to eq(Variant.new("a", 2))
20+
expect(Variant.new("a").hash).not_to eq(Variant.new("b").hash)
21+
expect(Variant.new("a", 1).hash).not_to eq(Variant.new("a", 2).hash)
22+
end
23+
end
24+
end
25+
end

0 commit comments

Comments
 (0)