diff --git a/fixtures/type-limits/src/lib.rs b/fixtures/type-limits/src/lib.rs index 89b8638b9b..d232725d9a 100644 --- a/fixtures/type-limits/src/lib.rs +++ b/fixtures/type-limits/src/lib.rs @@ -41,5 +41,8 @@ fn take_string(v: String) -> String { assert!(str::from_utf8(v.as_bytes()).is_ok()); v } +fn take_bytes(v: Vec) -> Vec { + v +} uniffi::include_scaffolding!("type-limits"); diff --git a/fixtures/type-limits/src/type-limits.udl b/fixtures/type-limits/src/type-limits.udl index 95ab877e0a..ff79144f6c 100644 --- a/fixtures/type-limits/src/type-limits.udl +++ b/fixtures/type-limits/src/type-limits.udl @@ -13,4 +13,5 @@ namespace uniffi_type_limits { f64 take_f64(f64 v); string take_string(string v); + bytes take_bytes(bytes v); }; diff --git a/fixtures/type-limits/tests/bindings/test_type_limits.py b/fixtures/type-limits/tests/bindings/test_type_limits.py index f767d3ddff..16fa2a2f3a 100644 --- a/fixtures/type-limits/tests/bindings/test_type_limits.py +++ b/fixtures/type-limits/tests/bindings/test_type_limits.py @@ -195,11 +195,48 @@ def test_special_floats(self): self.assertTrue(math.isnan(take_f32(math.nan))) self.assertTrue(math.isnan(take_f64(math.nan))) + def test_non_string(self): + self.assertRaises(TypeError, lambda: take_string(None)) + self.assertRaises(TypeError, lambda: take_string(False)) + self.assertRaises(TypeError, lambda: take_string(True)) + self.assertRaises(TypeError, lambda: take_string(0)) + self.assertRaises(TypeError, lambda: take_string(0.0)) + self.assertRaises(TypeError, lambda: take_string(b"")) + + class A: + def __str__(self): + return "" + + self.assertRaises(TypeError, lambda: take_string(A())) + def test_strings(self): self.assertRaises(ValueError, lambda: take_string("\ud800")) # surrogate self.assertEqual(take_string(""), "") self.assertEqual(take_string("愛"), "愛") self.assertEqual(take_string("💖"), "💖") + def test_non_bytes(self): + self.assertRaises(TypeError, lambda: take_bytes(None)) + self.assertRaises(TypeError, lambda: take_bytes(False)) + self.assertRaises(TypeError, lambda: take_bytes(True)) + self.assertRaises(TypeError, lambda: take_bytes(0)) + self.assertRaises(TypeError, lambda: take_bytes(0.0)) + self.assertRaises(TypeError, lambda: take_bytes("")) + + class A: + def __str__(self): + return "" + + self.assertRaises(TypeError, lambda: take_bytes(A())) + + def test_bytes(self): + self.assertEqual(take_bytes(b""), b"") + self.assertEqual(take_bytes(b"\xff"), b"\xff") # invalid utf-8 byte + self.assertEqual(take_bytes(b"\xed\xa0\x80"), b"\xed\xa0\x80") # surrogate + self.assertEqual(take_bytes("愛".encode()), "愛".encode()) + self.assertEqual(take_bytes("💖".encode()), "💖".encode()) + self.assertEqual(take_bytes("愛".encode("utf-16-le")), b"\x1b\x61") + self.assertEqual(take_bytes("💖".encode("utf-16-le")), b"\x3d\xd8\x96\xdc") + if __name__ == "__main__": unittest.main() diff --git a/fixtures/type-limits/tests/bindings/test_type_limits.rb b/fixtures/type-limits/tests/bindings/test_type_limits.rb index f76161a0d8..43c30ae944 100644 --- a/fixtures/type-limits/tests/bindings/test_type_limits.rb +++ b/fixtures/type-limits/tests/bindings/test_type_limits.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: true - # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. */ @@ -214,11 +212,48 @@ def test_special_floats assert(UniffiTypeLimits.take_f32(Float::NAN).nan?) assert(UniffiTypeLimits.take_f64(Float::NAN).nan?) end + class NonString + end + def test_non_string + assert_raise TypeError do UniffiTypeLimits.take_string(nil) end + assert_raise TypeError do UniffiTypeLimits.take_string(false) end + assert_raise TypeError do UniffiTypeLimits.take_string(true) end + assert_raise TypeError do UniffiTypeLimits.take_string(0) end + assert_raise TypeError do UniffiTypeLimits.take_string(0.0) end + assert_raise TypeError do UniffiTypeLimits.take_string(NonString.new) end + end + class StringLike + def to_str + "💕" + end + end def test_strings assert_raise Encoding::InvalidByteSequenceError do UniffiTypeLimits.take_string("\xff") end # invalid byte assert_raise Encoding::InvalidByteSequenceError do UniffiTypeLimits.take_string("\xed\xa0\x80") end # surrogate assert_equal(UniffiTypeLimits.take_string(""), "") assert_equal(UniffiTypeLimits.take_string("愛"), "愛") assert_equal(UniffiTypeLimits.take_string("💖"), "💖") + assert_equal(UniffiTypeLimits.take_string("愛".encode(Encoding::UTF_16LE)), "愛") + assert_equal(UniffiTypeLimits.take_string("💖".encode(Encoding::UTF_16LE)), "💖") + assert_equal(UniffiTypeLimits.take_string("💖"), "💖") + assert_equal(UniffiTypeLimits.take_string(StringLike.new), "💕") + end + def test_non_bytes + assert_raise TypeError do UniffiTypeLimits.take_bytes(nil) end + assert_raise TypeError do UniffiTypeLimits.take_bytes(false) end + assert_raise TypeError do UniffiTypeLimits.take_bytes(true) end + assert_raise TypeError do UniffiTypeLimits.take_bytes(0) end + assert_raise TypeError do UniffiTypeLimits.take_bytes(0.0) end + assert_raise TypeError do UniffiTypeLimits.take_string(NonString.new) end + end + def test_bytes + assert_equal(UniffiTypeLimits.take_bytes(""), "".force_encoding(Encoding::BINARY)) + assert_equal(UniffiTypeLimits.take_bytes("\xff"), "\xff".force_encoding(Encoding::BINARY)) # invalid utf-8 byte + assert_equal(UniffiTypeLimits.take_bytes("\xed\xa0\x80"), "\xed\xa0\x80".force_encoding(Encoding::BINARY)) # surrogate + assert_equal(UniffiTypeLimits.take_bytes("愛"), "愛".force_encoding(Encoding::BINARY)) + assert_equal(UniffiTypeLimits.take_bytes("💖"), "💖".force_encoding(Encoding::BINARY)) + assert_equal(UniffiTypeLimits.take_bytes("愛".encode(Encoding::UTF_16LE)), "\x1b\x61".force_encoding(Encoding::BINARY)) + assert_equal(UniffiTypeLimits.take_bytes("💖".encode(Encoding::UTF_16LE)), "\x3d\xd8\x96\xdc".force_encoding(Encoding::BINARY)) + assert_equal(UniffiTypeLimits.take_bytes(StringLike.new), "\xf0\x9f\x92\x95".force_encoding(Encoding::BINARY)) end end diff --git a/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py b/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py index 7288c3be98..a70471ea2a 100644 --- a/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py +++ b/uniffi_bindgen/src/bindings/python/templates/BytesHelper.py @@ -8,5 +8,9 @@ def read(buf): @staticmethod def write(value, buf): + try: + memoryview(value) + except TypeError: + raise TypeError("a bytes-like object is required, not {!r}".format(type(value).__name__)) buf.writeI32(len(value)) buf.write(value) diff --git a/uniffi_bindgen/src/bindings/python/templates/StringHelper.py b/uniffi_bindgen/src/bindings/python/templates/StringHelper.py index f205f7c746..944c210541 100644 --- a/uniffi_bindgen/src/bindings/python/templates/StringHelper.py +++ b/uniffi_bindgen/src/bindings/python/templates/StringHelper.py @@ -1,4 +1,10 @@ class FfiConverterString: + @staticmethod + def check(value): + if not isinstance(value, str): + raise TypeError("argument must be str, not {}".format(type(value).__name__)) + return value + @staticmethod def read(buf): size = buf.readI32() @@ -9,6 +15,7 @@ def read(buf): @staticmethod def write(value, buf): + value = FfiConverterString.check(value) utf8Bytes = value.encode("utf-8") buf.writeI32(len(utf8Bytes)) buf.write(utf8Bytes) @@ -20,6 +27,7 @@ def lift(buf): @staticmethod def lower(value): + value = FfiConverterString.check(value) with RustBuffer.allocWithBuilder() as builder: builder.write(value.encode("utf-8")) return builder.finalize() diff --git a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs index 2e3103ad93..7b8d00175d 100644 --- a/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs +++ b/uniffi_bindgen/src/bindings/ruby/gen_ruby/mod.rs @@ -227,7 +227,8 @@ mod filters { Type::Float32 | Type::Float64 => nm.to_string(), Type::Boolean => format!("{nm} ? true : false"), Type::Object { .. } | Type::Enum(_) | Type::Record(_) => nm.to_string(), - Type::String | Type::Bytes => format!("{ns}::uniffi_utf8({nm})"), + Type::String => format!("{ns}::uniffi_utf8({nm})"), + Type::Bytes => format!("{ns}::uniffi_bytes({nm})"), Type::Timestamp | Type::Duration => nm.to_string(), Type::CallbackInterface(_) => panic!("No support for coercing callback interfaces yet"), Type::Optional(t) => format!("({nm} ? {} : nil)", coerce_rb(nm, ns, t)?), diff --git a/uniffi_bindgen/src/bindings/ruby/templates/Helpers.rb b/uniffi_bindgen/src/bindings/ruby/templates/Helpers.rb index c36f9430b1..49fa247a77 100644 --- a/uniffi_bindgen/src/bindings/ruby/templates/Helpers.rb +++ b/uniffi_bindgen/src/bindings/ruby/templates/Helpers.rb @@ -6,7 +6,13 @@ def self.uniffi_in_range(i, type_name, min, max) end def self.uniffi_utf8(v) + raise TypeError, "no implicit conversion of #{v} into String" unless v.respond_to?(:to_str) v = v.to_str.encode(Encoding::UTF_8) raise Encoding::InvalidByteSequenceError, "not a valid UTF-8 encoded string" unless v.valid_encoding? v end + +def self.uniffi_bytes(v) + raise TypeError, "no implicit conversion of #{v} into String" unless v.respond_to?(:to_str) + v.to_str +end diff --git a/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb b/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb index 160b1b9e25..80b5954683 100644 --- a/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb +++ b/uniffi_bindgen/src/bindings/ruby/templates/RustBufferBuilder.rb @@ -116,7 +116,7 @@ def write_String(v) {% when Type::Bytes -%} def write_Bytes(v) - v = v.to_s + v = {{ ci.namespace()|class_name_rb }}::uniffi_bytes(v) pack_into 4, 'l>', v.bytes.size write v end