diff --git a/cw_core/lib/parse_fixed.dart b/cw_core/lib/parse_fixed.dart index 321320ef2..1323b6c76 100644 --- a/cw_core/lib/parse_fixed.dart +++ b/cw_core/lib/parse_fixed.dart @@ -1,23 +1,51 @@ +/// Parses the string [value] as a fixed-point decimal literal and returns its +/// [BigInt] value. +/// +/// The number of fractional digits is determined by [decimals]. +/// +/// Returns `null` if the input [value] is not a valid fixed-point literal +/// (e.g., non-numeric characters, too many fractional digits). +/// +/// Like [parseFixed], except that this function returns `null` for invalid inputs +/// instead of throwing. +BigInt? tryParseFixed(String value, int decimals) { + try { + return parseFixed(value, decimals); + } on FormatException catch (_) { + return null; + } +} + +/// Parses the string [value] as a fixed-point decimal literal and returns its +/// [BigInt] value. +/// +/// The number of fractional digits is determined by [decimals]. +/// +/// Throws a [FormatException] if the input [value] is not a valid fixed-point literal +/// (e.g., non-numeric characters, too many fractional digits). +/// +/// Rather than throwing and immediately catching the [FormatException], +/// instead use [tryParseFixed] to handle a potential parsing error. BigInt parseFixed(String value, int decimals) { final multiplier = getMultiplier(decimals); final negative = value.startsWith("-"); if (negative) value = value.substring(1); - if (value == ".") throw Exception("missing value, value, $value"); + if (value == ".") throw FormatException("missing value, value, $value"); if (value.startsWith(".")) value = "0$value"; final comps = value.split("."); if (comps.length > 2) { - throw Exception("too many decimal points, value, $value"); + throw FormatException("too many decimal points, value, $value"); } var whole = comps.isNotEmpty ? comps[0] : "0"; var fraction = (comps.length == 2 ? comps[1] : "0").padRight(decimals, "0"); if (fraction.length > multiplier.length - 1) { - throw Exception( + throw FormatException( "fractional component exceeds decimals, underflow, parseFixed"); } diff --git a/cw_core/test/parse_fixed_test.dart b/cw_core/test/parse_fixed_test.dart index 5ce520166..c102618e1 100644 --- a/cw_core/test/parse_fixed_test.dart +++ b/cw_core/test/parse_fixed_test.dart @@ -34,5 +34,64 @@ void main() { test('should parse -.00002 as -20', () => expect(parseFixed("-.00002", 6), BigInt.from(-20))); }); + + group('parseFixed, failing', () { + test('should fail to parse .000.0010, too many fractional digits', + () => expect(() => parseFixed(".000.0010", 6), throwsFormatException)); + + test('should fail to parse .0000010, fractional component exceeds decimals', + () => expect(() => parseFixed(".0000010", 6), throwsFormatException)); + + test('should fail to parse `.`, missing value', + () => expect(() => parseFixed(".", 6), throwsFormatException)); + }); + }); + + group('tryParseFixed', () { + group('tryParseFixed, positive', () { + test('should parse 1.000001 as 1000001', + () => expect(tryParseFixed("1.000001", 6), BigInt.from(1000001))); + + test('should parse 1 as 1000000', () => expect(tryParseFixed("1", 6), BigInt.from(1000000))); + + test( + 'should parse 1. as 1000000', () => expect(tryParseFixed("1.", 6), BigInt.from(1000000))); + + test('should parse 1.1 as 1100000', + () => expect(tryParseFixed("1.1", 6), BigInt.from(1100000))); + + test('should parse 01.1 as 1100000', + () => expect(tryParseFixed("01.1", 6), BigInt.from(1100000))); + + test('should parse 1100000 as 11000000', + () => expect(tryParseFixed("1100000", 1), BigInt.from(11000000))); + }); + + group('tryParseFixed, negative', () { + test('should parse -1.000001 as -1000001', + () => expect(tryParseFixed("-1.000001", 6), BigInt.from(-1000001))); + + test('should parse -1 as 1000000', + () => expect(tryParseFixed("-1", 6), BigInt.from(-1000000))); + }); + + group('tryParseFixed, no leading 0', () { + test('should parse .000001 as 1', () => expect(tryParseFixed(".000001", 6), BigInt.from(1))); + + test('should parse .00002 as 20', () => expect(tryParseFixed(".00002", 6), BigInt.from(20))); + + test('should parse -.00002 as -20', + () => expect(tryParseFixed("-.00002", 6), BigInt.from(-20))); + }); + + group('tryParseFixed, return `null`', () { + test('should parse .000.0010 as null, too many fractional digits', + () => expect(tryParseFixed(".000.0010", 6), isNull)); + + test('should parse .0000010 as null, fractional component exceeds decimals', + () => expect(tryParseFixed(".0000010", 6), isNull)); + + test('should parse . as `null`, missing value', () => expect(tryParseFixed(".", 6), isNull)); + }); }); }