Skip to content

Commit

Permalink
Better stringify support for fielded errors
Browse files Browse the repository at this point in the history
Improved stringifying errors defined interfaces in the UDL.
  - Added message method in Kotlin and a test for it
  - Added to_s method in Ruby and a test for it
  - Swift already supported this, added tests for it
  - Python already supported this and had tests
  • Loading branch information
bendk committed Oct 14, 2022
1 parent 5c8b45a commit 058eb83
Show file tree
Hide file tree
Showing 7 changed files with 45 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
### What's changed

- Added support for exceptions in callback interface methods.
- Improved error stringifying on Kotlin and Ruby (the `message` and `to_s` methods respectively).

## v0.20.0 - (_2022-09-13_)

Expand Down
6 changes: 6 additions & 0 deletions fixtures/coverall/tests/bindings/test_coverall.kts
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,19 @@ Coveralls("test_complex_errors").use { coveralls ->
} catch(e: ComplexException.OsException) {
assert(e.code == 10.toShort())
assert(e.extendedCode == 20.toShort())
assert(e.toString() == "uniffi.coverall.ComplexException\$OsException: code=10, extendedCode=20") {
"Unexpected ComplexError.OsError.toString() value: ${e.toString()}"
}
}

try {
coveralls.maybeThrowComplex(2)
throw RuntimeException("Expected method to throw exception")
} catch(e: ComplexException.PermissionDenied) {
assert(e.reason == "Forbidden")
assert(e.toString() == "uniffi.coverall.ComplexException\$PermissionDenied: reason=Forbidden") {
"Unexpected ComplexError.PermissionDenied.toString() value: ${e.toString()}"
}
}

try {
Expand Down
2 changes: 2 additions & 0 deletions fixtures/coverall/tests/bindings/test_coverall.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ def test_complex_errors
rescue Coverall::ComplexError::OsError => err
assert_equal err.code, 10
assert_equal err.extended_code, 20
assert_equal err.to_s, 'Coverall::ComplexError::OsError(code=10, extended_code=20)'
else
raise 'should have thrown'
end
Expand All @@ -134,6 +135,7 @@ def test_complex_errors
coveralls.maybe_throw_complex(2)
rescue Coverall::ComplexError::PermissionDenied => err
assert_equal err.reason, "Forbidden"
assert_equal err.to_s, 'Coverall::ComplexError::PermissionDenied(reason="Forbidden")'
else
raise 'should have thrown'
end
Expand Down
20 changes: 15 additions & 5 deletions fixtures/coverall/tests/bindings/test_coverall.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,26 @@ do {
do {
let _ = try coveralls.maybeThrowComplex(input: 1)
fatalError("should have thrown")
} catch ComplexError.OsError(let code, let extendedCode) {
assert(code == 10)
assert(extendedCode == 20)
} catch let e as ComplexError {
if case let .OsError(code, extendedCode) = e {
assert(code == 10)
assert(extendedCode == 20)
} else {
fatalError("wrong error variant: \(e)")
}
assert(String(describing: e) == "OsError(code: 10, extendedCode: 20)", "Unexpected ComplexError.OsError description: \(e)")
}

do {
let _ = try coveralls.maybeThrowComplex(input: 2)
fatalError("should have thrown")
} catch ComplexError.PermissionDenied(let reason) {
assert(reason == "Forbidden")
} catch let e as ComplexError {
if case let .PermissionDenied(reason) = e {
assert(reason == "Forbidden")
} else {
fatalError("wrong error variant: \(e)")
}
assert(String(describing: e) == "PermissionDenied(reason: \"Forbidden\")", "Unexpected ComplexError.PermissionDenied description: \(e)")
}

do {
Expand Down
9 changes: 9 additions & 0 deletions uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,4 +368,13 @@ pub mod filters {
pub fn exception_name(nm: &str) -> Result<String, askama::Error> {
Ok(oracle().error_name(nm))
}

/// Remove the "`" chars we put around function/variable names
///
/// These are used to avoid name clashes with kotlin identifiers, but sometimes you want to
/// render the name unquoted. One example is the message property for errors where we want to
/// display the name for the user.
pub fn unquote(nm: &str) -> Result<String, askama::Error> {
Ok(nm.trim_matches('`').to_string())
}
}
14 changes: 7 additions & 7 deletions uniffi_bindgen/src/bindings/kotlin/templates/ErrorTemplate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ sealed class {{ type_name }}(message: String): Exception(message){% if contains_
sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Disposable {% endif %} {
// Each variant is a nested class
{% for variant in e.variants() -%}
{% if !variant.has_fields() -%}
class {{ variant.name()|exception_name }} : {{ type_name }}()
{% else %}
class {{ variant.name()|exception_name }}(
{%- let variant_name = variant.name()|exception_name %}
class {{ variant_name }}(
{% for field in variant.fields() -%}
val {{ field.name()|var_name }}: {{ field|type_name}}{% if loop.last %}{% else %}, {% endif %}
{% endfor -%}
) : {{ type_name }}()
{%- endif %}
) : {{ type_name }}() {
override val message
get() = "{%- for field in variant.fields() %}{{ field.name()|var_name|unquote }}=${ {{field.name()|var_name }} }{% if !loop.last %}, {% endif %}{% endfor %}"
}
{% endfor %}

companion object ErrorHandler : CallStatusErrorHandler<{{ type_name }}> {
Expand All @@ -36,7 +36,7 @@ sealed class {{ type_name }}: Exception(){% if contains_object_references %}, Di
override fun destroy() {
when(this) {
{%- for variant in e.variants() %}
is {{ type_name }}.{{ variant.name()|class_name }} -> {
is {{ type_name }}.{{ variant.name()|exception_name }} -> {
{%- if variant.has_fields() %}
{% call kt::destroy_fields(variant) %}
{% else -%}
Expand Down
6 changes: 5 additions & 1 deletion uniffi_bindgen/src/bindings/ruby/templates/ErrorTemplate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,12 @@ def initialize({% for field in variant.fields() %}{{ field.name()|var_name_rb }}
end
{%- if variant.has_fields() %}

attr_reader {% for field in variant.fields() %}:{{ field.name() }}{% if !loop.last %}, {% endif %}{% endfor %}
attr_reader {% for field in variant.fields() %}:{{ field.name()|var_name_rb }}{% if !loop.last %}, {% endif %}{% endfor %}
{% endif %}

def to_s
"#{self.class.name}({% for field in variant.fields() %}{{ field.name()|var_name_rb }}=#{@{{ field.name()|var_name_rb }}.inspect}{% if !loop.last %}, {% endif %}{% endfor %})"
end
end
{%- endfor %}
{% endif %}
Expand Down

0 comments on commit 058eb83

Please sign in to comment.