Skip to content

Commit

Permalink
Merge pull request #12 from kraken-tech/tweak-type-error-handling
Browse files Browse the repository at this point in the history
Tweak type error handling
  • Loading branch information
seddonym authored Sep 25, 2024
2 parents 6d2a1f3 + cc4be14 commit 9e6877a
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 29 deletions.
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,21 @@ repos:
hooks:
- id: ruff-lint
name: "lint Python code with ruff"
entry: "ruff check"
entry: "ruff check --fix"
language: system
types: [python]
require_serial: true

- id: ruff-format
name: "check Python formatting with ruff"
entry: "ruff format --check"
entry: "ruff format"
language: system
types: [python]
require_serial: true

- id: cargo-format
name: "check Rust formatting"
entry: "cargo fmt -- --check"
entry: "cargo fmt --"
language: system
types: [ rust ]
require_serial: true
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

- Raise TypeError if variable key is not a string.
- Fall back to displaying variable name if there is a type issue with a variable.

## [0.1.0a3] - 2024-09-25

- Limited Fluent variable types to strings and integers.
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,13 @@ bundle = rustfluent.Bundle(

`str`: the translated message.

If there is a problem with a passed variable (e.g. it is of the wrong type or an integer that is larger than a
signed long integer), then the name of the variable will be used instead.

#### Raises

- `ValueError` if the message could not be found or has no translation available.
- `TypeError` if a passed variable is not an instance of `str` or `int`, or if the `int` overflows a signed long
integer (i.e. it's not in the range -2,147,483,648 to 2,147,483,647).
- `TypeError` if a passed variable name (i.e. a key in the `variables` dict) is not a string.

## Contributing

Expand Down
37 changes: 25 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,25 +85,38 @@ impl Bundle {

if let Some(variables) = variables {
for variable in variables {
let key: String = variable.0.to_string();
// Make sure the variable key is a Python string,
// raising a TypeError if not.
let python_key = variable.0;
if !python_key.is_instance_of::<PyString>() {
return Err(PyTypeError::new_err(format!(
"Variable key not a str, got {}.",
python_key
)));
}
let key = python_key.to_string();
// Set the variable value as a string or integer,
// raising a TypeError if not.
let python_value = variable.1;
if python_value.is_instance_of::<PyString>() {
args.set(key, python_value.to_string());
} else if python_value.is_instance_of::<PyInt>() {
let int_value: i32 = match python_value.extract() {
Ok(value) => value,
match python_value.extract::<i32>() {
Ok(int_value) => {
args.set(key, int_value);
}
_ => {
return Err(PyTypeError::new_err(format!(
"Integer variable was too large: {}.",
python_value
)));
// The Python integer overflowed i32.
// Fall back to displaying the variable key as its value.
let fallback_value = key.clone();
args.set(key, fallback_value);
}
};
args.set(key, int_value);
}
} else {
return Err(PyTypeError::new_err(
"Expected a string, integer or float.".to_string(),
));
// The variable value was of an unsupported type.
// Fall back to displaying the variable key as its value.
let fallback_value = key.clone();
args.set(key, fallback_value);
}
}
}
Expand Down
35 changes: 23 additions & 12 deletions tests/test_python_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,24 +50,35 @@ def test_variables_of_different_types(description, identifier, variables, expect
assert result == expected


def test_large_python_integers_fail_sensibly():
@pytest.mark.parametrize(
"key",
(
object(),
34.3,
10,
),
)
def test_invalid_variable_keys_raise_type_error(key):
bundle = fluent.Bundle("en", [str(data_dir / "en.ftl")])

with pytest.raises(
TypeError, match=re.escape("Integer variable was too large: 1000000000000.")
):
bundle.get_translation(
"hello-user",
variables={"user": 1_000_000_000_000},
)
with pytest.raises(TypeError, match="Variable key not a str, got"):
bundle.get_translation("hello-user", variables={key: "Bob"})


@pytest.mark.parametrize("user", (object(), 34.3))
def test_invalid_type_raises_type_error(user):
@pytest.mark.parametrize(
"value",
(
object(),
34.3,
1_000_000_000_000, # Larger than signed long integer.
),
)
def test_invalid_variable_values_use_key_instead(value):
bundle = fluent.Bundle("en", [str(data_dir / "en.ftl")])

with pytest.raises(TypeError):
bundle.get_translation("hello-user", variables={"user": user})
result = bundle.get_translation("hello-user", variables={"user": value})

assert result == f"Hello, {BIDI_OPEN}user{BIDI_CLOSE}"


def test_fr_basic():
Expand Down

0 comments on commit 9e6877a

Please sign in to comment.