diff --git a/src/Fable.Transforms/Beam/Replacements.fs b/src/Fable.Transforms/Beam/Replacements.fs index 11b28ecf4..69ce006d5 100644 --- a/src/Fable.Transforms/Beam/Replacements.fs +++ b/src/Fable.Transforms/Beam/Replacements.fs @@ -1768,6 +1768,14 @@ let private convert | "FromBase64String", [ arg ] -> Helper.LibCall(com, "fable_convert", "from_base64", t, [ arg ], ?loc = r) |> Some + | "ToHexString", ([ _ ] | [ _; _; _ ]) -> + Helper.LibCall(com, "fable_convert", "to_hex_string", t, args, ?loc = r) |> Some + | "ToHexStringLower", ([ _ ] | [ _; _; _ ]) -> + Helper.LibCall(com, "fable_convert", "to_hex_string_lower", t, args, ?loc = r) + |> Some + | "FromHexString", [ arg ] -> + Helper.LibCall(com, "fable_convert", "from_hex_string", t, [ arg ], ?loc = r) + |> Some | _ -> None /// Beam-specific List module replacements. diff --git a/src/Fable.Transforms/Dart/Replacements.fs b/src/Fable.Transforms/Dart/Replacements.fs index ce6705fd0..8e551f28a 100644 --- a/src/Fable.Transforms/Dart/Replacements.fs +++ b/src/Fable.Transforms/Dart/Replacements.fs @@ -2828,6 +2828,13 @@ let convert (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) ( ?loc = r ) |> Some + | "ToHexString" + | "ToHexStringLower" -> + Helper.LibCall(com, "String", Naming.lowerFirst i.CompiledName, t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "FromHexString" when List.isSingle args -> + Helper.LibCall(com, "String", "fromHexString", t, args, i.SignatureArgTypes, ?loc = r) + |> Some | _ -> None let console (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) = diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index 0f9ae8899..537e0f631 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -3026,6 +3026,13 @@ let convert (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) ( Helper.LibCall(com, "String", (Naming.lowerFirst i.CompiledName), t, args, i.SignatureArgTypes, ?loc = r) |> Some + | "ToHexString" + | "ToHexStringLower" -> + Helper.LibCall(com, "Convert", Naming.lowerFirst i.CompiledName, t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "FromHexString" when List.isSingle args -> + Helper.LibCall(com, "Convert", "fromHexString", t, args, i.SignatureArgTypes, ?loc = r) + |> Some | _ -> None let console (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr option) (args: Expr list) = diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 232c79896..f55b2b823 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -3432,6 +3432,13 @@ let bitConvert (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option Helper.LibCall(com, "BitConverter", memberName, t, args, i.SignatureArgTypes, ?loc = r) |> Some +// The library decodes into a Uint8Array regardless of the typedArrays option +let private decodedBytes (com: ICompiler) r t expr = + if com.Options.TypedArrays then + expr + else + toArray r t expr + let convert (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) (args: Expr list) = match i.CompiledName with | "ToSByte" @@ -3456,7 +3463,19 @@ let convert (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) ( if not (List.isSingle args) then WarningCodes.base64ArgumentsIgnored |> addWarningWithCode com ctx.InlinePath r - Helper.LibCall(com, "String", (Naming.lowerFirst i.CompiledName), t, args, i.SignatureArgTypes, ?loc = r) + let expr = + Helper.LibCall(com, "String", (Naming.lowerFirst i.CompiledName), t, args, i.SignatureArgTypes, ?loc = r) + + match i.CompiledName with + | "FromBase64String" -> decodedBytes com r t expr |> Some + | _ -> Some expr + | "ToHexString" + | "ToHexStringLower" -> + Helper.LibCall(com, "String", Naming.lowerFirst i.CompiledName, t, args, i.SignatureArgTypes, ?loc = r) + |> Some + | "FromHexString" when List.isSingle args -> + Helper.LibCall(com, "String", "fromHexString", t, args, i.SignatureArgTypes, ?loc = r) + |> decodedBytes com r t |> Some | _ -> None diff --git a/src/fable-library-beam/fable_convert.erl b/src/fable-library-beam/fable_convert.erl index 8e5dd5627..e2aeebef6 100644 --- a/src/fable-library-beam/fable_convert.erl +++ b/src/fable-library-beam/fable_convert.erl @@ -10,6 +10,11 @@ to_string_with_base/3, to_base64/1, from_base64/1, + to_hex_string/1, + to_hex_string/3, + to_hex_string_lower/1, + to_hex_string_lower/3, + from_hex_string/1, boolean_parse/1, boolean_try_parse/2, int_to_string_with_format/2, @@ -28,6 +33,11 @@ -spec to_string_with_base(integer(), integer(), integer()) -> binary(). -spec to_base64(tuple() | list() | binary()) -> binary(). -spec from_base64(binary()) -> tuple(). +-spec to_hex_string(tuple() | list() | binary() | reference() | undefined) -> binary(). +-spec to_hex_string(tuple() | list() | binary() | reference() | undefined, integer(), integer()) -> binary(). +-spec to_hex_string_lower(tuple() | list() | binary() | reference() | undefined) -> binary(). +-spec to_hex_string_lower(tuple() | list() | binary() | reference() | undefined, integer(), integer()) -> binary(). +-spec from_hex_string(binary() | undefined) -> tuple(). -spec boolean_parse(binary()) -> boolean(). -spec boolean_try_parse(binary(), reference()) -> boolean(). -spec int_to_string_with_format(integer(), binary()) -> binary(). @@ -196,6 +206,67 @@ to_base64(Bin) when is_binary(Bin) -> from_base64(Str) when is_binary(Str) -> fable_utils:new_byte_array(binary_to_list(base64:decode(Str))). +to_hex_string(Bytes) -> + binary:encode_hex(hex_input(Bytes)). + +to_hex_string(Bytes, Offset, Length) -> + binary:encode_hex(hex_input(Bytes, Offset, Length)). + +%% binary:encode_hex/2 with lowercase needs OTP 26 +to_hex_string_lower(Bytes) -> + string:lowercase(to_hex_string(Bytes)). + +to_hex_string_lower(Bytes, Offset, Length) -> + string:lowercase(to_hex_string(Bytes, Offset, Length)). + +hex_input(undefined) -> + argument_null(<<"inArray">>); +hex_input({byte_array, _, _} = BA) -> + list_to_binary(fable_utils:byte_array_to_list(BA)); +hex_input(Bytes) when is_list(Bytes) -> + list_to_binary(Bytes); +hex_input(Bin) when is_binary(Bin) -> + Bin; +hex_input(Ref) when is_reference(Ref) -> + hex_input(get(Ref)). + +hex_input(Bytes, Offset, Length) -> + Bin = hex_input(Bytes), + if + Length < 0 -> argument_out_of_range(<<"length">>, <<"Non-negative number required.">>); + Offset < 0 -> argument_out_of_range(<<"offset">>, <<"Non-negative number required.">>); + Offset > byte_size(Bin) - Length -> + argument_out_of_range(<<"offset">>, <<"Specified argument was out of the range of valid values.">>); + true -> binary:part(Bin, Offset, Length) + end. + +from_hex_string(undefined) -> + argument_null(<<"s">>); +from_hex_string(Str) when byte_size(Str) rem 2 =/= 0 -> + bad_hex_string(<<"The input is not a valid hex string as its length is not a multiple of 2.">>); +from_hex_string(Str) -> + try binary:decode_hex(Str) of + Bin -> fable_utils:new_byte_array(binary_to_list(Bin)) + catch + error:badarg -> + bad_hex_string(<<"The input is not a valid hex string as it contains a non-hex character.">>) + end. + +argument_null(ParamName) -> + erlang:error(#{ + exn_type => argument_null_exception, + message => fable_utils:argument_exception_message(<<"Value cannot be null.">>, ParamName) + }). + +argument_out_of_range(ParamName, Message) -> + erlang:error(#{ + exn_type => argument_out_of_range_exception, + message => fable_utils:argument_exception_message(Message, ParamName) + }). + +bad_hex_string(Message) -> + erlang:error(#{exn_type => format_exception, message => Message}). + %% TryParse: returns bool and sets out-param via put(OutRef, Value). %% F# uses out-parameter pattern: result = TryParse(str, &outRef). try_parse_int(Bin, OutRef) when is_binary(Bin) -> diff --git a/src/fable-library-dart/String.dart b/src/fable-library-dart/String.dart index 33b593c3c..993afe2f3 100644 --- a/src/fable-library-dart/String.dart +++ b/src/fable-library-dart/String.dart @@ -1,5 +1,7 @@ // ignore_for_file: file_names, constant_identifier_names +import 'System.dart' as system; + class StringComparison { static const CurrentCulture = 0; static const CurrentCultureIgnoreCase = 1; @@ -280,3 +282,41 @@ String mapIndexed(int Function(int, int) mapping, String str) { return mapping(index, rune); })); } + +String toHexStringLower(List inArray, [int? offset, int? length]) { + offset ??= 0; + length ??= inArray.length - offset; + if (length < 0) { + throw system.ArgumentOutOfRangeException_$ctor_Z384F8060( + 'length', 'Non-negative number required.'); + } + if (offset < 0) { + throw system.ArgumentOutOfRangeException_$ctor_Z384F8060( + 'offset', 'Non-negative number required.'); + } + if (offset > inArray.length - length) { + throw system.ArgumentOutOfRangeException_$ctor_Z721C83C5('offset'); + } + return inArray + .sublist(offset, offset + length) + .map((b) => b.toRadixString(16).padLeft(2, '0')) + .join(); +} + +String toHexString(List inArray, [int? offset, int? length]) { + return toHexStringLower(inArray, offset, length).toUpperCase(); +} + +List fromHexString(String s) { + if (s.length % 2 != 0) { + throw system.FormatException_$ctor_Z721C83C5( + 'The input is not a valid hex string as its length is not a multiple of 2.'); + } + if (!RegExp(r'^[0-9A-Fa-f]*$').hasMatch(s)) { + throw system.FormatException_$ctor_Z721C83C5( + 'The input is not a valid hex string as it contains a non-hex character.'); + } + return List.generate( + s.length ~/ 2, (i) => int.parse(s.substring(2 * i, 2 * i + 2), radix: 16), + growable: false); +} diff --git a/src/fable-library-py/fable_library/convert.py b/src/fable-library-py/fable_library/convert.py new file mode 100644 index 000000000..a95a600d6 --- /dev/null +++ b/src/fable-library-py/fable_library/convert.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +from typing import SupportsInt + +from .array_ import Array +from .core import byte +from .system import ( + ArgumentNullException__ctor_Z721C83C5, + ArgumentOutOfRangeException__ctor_Z384F8060, + ArgumentOutOfRangeException__ctor_Z721C83C5, + FormatException__ctor_Z721C83C5, +) + + +_HEX_DIGITS = frozenset("0123456789abcdefABCDEF") + + +def _hex_input(in_array: Array[byte], offset: SupportsInt | None, length: SupportsInt | None) -> bytes: + if in_array is None: + raise ArgumentNullException__ctor_Z721C83C5("inArray") + data = bytes(in_array) + start = 0 if offset is None else int(offset) + count = len(data) - start if length is None else int(length) + if count < 0: + raise ArgumentOutOfRangeException__ctor_Z384F8060("length", "Non-negative number required.") + if start < 0: + raise ArgumentOutOfRangeException__ctor_Z384F8060("offset", "Non-negative number required.") + if start > len(data) - count: + raise ArgumentOutOfRangeException__ctor_Z721C83C5("offset") + return data[start : start + count] + + +def to_hex_string(in_array: Array[byte], offset: SupportsInt | None = None, length: SupportsInt | None = None) -> str: + """Convert a byte array, or length bytes of it from offset, to uppercase hex (Convert.ToHexString).""" + return _hex_input(in_array, offset, length).hex().upper() + + +def to_hex_string_lower( + in_array: Array[byte], offset: SupportsInt | None = None, length: SupportsInt | None = None +) -> str: + """Convert a byte array, or length bytes of it from offset, to lowercase hex (Convert.ToHexStringLower).""" + return _hex_input(in_array, offset, length).hex() + + +def from_hex_string(s: str) -> Array[byte]: + """Convert a hex string of either case to a byte array (Convert.FromHexString).""" + if s is None: + raise ArgumentNullException__ctor_Z721C83C5("s") + if len(s) % 2 != 0: + raise FormatException__ctor_Z721C83C5( + "The input is not a valid hex string as its length is not a multiple of 2." + ) + if not _HEX_DIGITS.issuperset(s): + raise FormatException__ctor_Z721C83C5("The input is not a valid hex string as it contains a non-hex character.") + return Array[byte](byte(x) for x in bytes.fromhex(s)) diff --git a/src/fable-library-rust/src/Convert.rs b/src/fable-library-rust/src/Convert.rs index 7aaf7b3e0..3531d764c 100644 --- a/src/fable-library-rust/src/Convert.rs +++ b/src/fable-library-rust/src/Convert.rs @@ -268,6 +268,9 @@ pub mod Convert_ { } } let chars = s.as_bytes(); + if chars.len() % 2 != 0 { + panic!("The input is not a valid hex string as its length is not a multiple of 2."); + } let bytes = chars .chunks_exact(2) .map(|x| decode(x[0]) << 4 | decode(x[1])) diff --git a/src/fable-library-ts/String.ts b/src/fable-library-ts/String.ts index d12d60b69..15a5aa2c3 100644 --- a/src/fable-library-ts/String.ts +++ b/src/fable-library-ts/String.ts @@ -1,6 +1,7 @@ import { toString as dateToString } from "./Date.ts"; import { compare as numericCompare, divide, isNumeric, isIntegral, multiply, Numeric, toExponential, toFixed, toHex, toPrecision, toRadix } from "./Numeric.ts"; import { escape } from "./RegExp.ts"; +import { ArgumentNullException_$ctor_Z721C83C5, ArgumentOutOfRangeException_$ctor_Z384F8060, ArgumentOutOfRangeException_$ctor_Z721C83C5, FormatException_$ctor_Z721C83C5 } from "./System.ts"; import { toString } from "./Types.ts"; import { Exception } from "./Util.ts"; @@ -683,6 +684,49 @@ export function fromBase64String(b64Encoded: string): number[] { return bytes as any as number[]; } +export function toHexString(inArray: ArrayLike, offset?: number, length?: number): string { + if (inArray == null) { + throw ArgumentNullException_$ctor_Z721C83C5("inArray"); + } + const start = offset ?? 0; + const count = length ?? inArray.length - start; + if (count < 0) { + throw ArgumentOutOfRangeException_$ctor_Z384F8060("length", "Non-negative number required."); + } + if (start < 0) { + throw ArgumentOutOfRangeException_$ctor_Z384F8060("offset", "Non-negative number required."); + } + if (start > inArray.length - count) { + throw ArgumentOutOfRangeException_$ctor_Z721C83C5("offset"); + } + let str = ""; + for (let i = start; i < start + count; i++) { + str += inArray[i].toString(16).padStart(2, "0"); + } + return str.toUpperCase(); +} + +export function toHexStringLower(inArray: ArrayLike, offset?: number, length?: number): string { + return toHexString(inArray, offset, length).toLowerCase(); +} + +export function fromHexString(s: string): number[] { + if (s == null) { + throw ArgumentNullException_$ctor_Z721C83C5("s"); + } + if (s.length % 2 !== 0) { + throw FormatException_$ctor_Z721C83C5("The input is not a valid hex string as its length is not a multiple of 2."); + } + if (!/^[0-9A-Fa-f]*$/.test(s)) { + throw FormatException_$ctor_Z721C83C5("The input is not a valid hex string as it contains a non-hex character."); + } + const bytes = new Uint8Array(s.length / 2); + for (let i = 0; i < bytes.length; i++) { + bytes[i] = parseInt(s.substring(2 * i, 2 * i + 2), 16); + } + return bytes as any as number[]; +} + function pad(str: string, len: number, ch?: string, isRight?: boolean) { ch = ch || " "; len = len - str.length; diff --git a/tests/Beam/EncodingTests.fs b/tests/Beam/EncodingTests.fs index de1eaed3f..5a54b9ed1 100644 --- a/tests/Beam/EncodingTests.fs +++ b/tests/Beam/EncodingTests.fs @@ -192,6 +192,55 @@ let ``test Base64 empty array works`` () = let encoded = Convert.ToBase64String(bytes) encoded |> equal "" +// --- Hex --- + +[] +let ``test Convert.ToHexString works`` () = + let bytes = [| 250uy; 251uy; 252uy; 253uy; 254uy |] + Convert.ToHexString(bytes) |> equal "FAFBFCFDFE" + Convert.ToHexString([| 0uy; 1uy; 15uy; 16uy |]) |> equal "00010F10" + Convert.ToHexString([||]) |> equal "" + Convert.ToHexString(bytes, 1, 3) |> equal "FBFCFD" + Convert.ToHexString(bytes, 5, 0) |> equal "" + Convert.ToHexString(List.toArray [ 200uy; 3uy ]) |> equal "C803" + +[] +let ``test Convert.ToHexStringLower works`` () = + let bytes = [| 250uy; 251uy; 252uy; 253uy; 254uy |] + Convert.ToHexStringLower(bytes) |> equal "fafbfcfdfe" + Convert.ToHexStringLower([| 0uy; 1uy; 15uy; 16uy |]) |> equal "00010f10" + Convert.ToHexStringLower([||]) |> equal "" + Convert.ToHexStringLower(bytes, 1, 3) |> equal "fbfcfd" + +[] +let ``test Convert.ToHexString checks its arguments`` () = + let bytes = [| 250uy; 251uy; 252uy; 253uy; 254uy |] + raisedExceptionType (fun () -> Convert.ToHexString(null: byte[])) |> equal "ArgumentNullException" + raisedExceptionType (fun () -> Convert.ToHexString((null: byte[]), 0, 0)) |> equal "ArgumentNullException" + raisedExceptionType (fun () -> Convert.ToHexString(bytes, -1, 1)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexString(bytes, 1, -1)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexString(bytes, 3, 3)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexString(bytes, 6, 0)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexStringLower(null: byte[])) |> equal "ArgumentNullException" + raisedExceptionType (fun () -> Convert.ToHexStringLower(bytes, -1, 1)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexStringLower(bytes, 3, 3)) |> equal "ArgumentOutOfRangeException" + +[] +let ``test Convert.FromHexString works`` () = + Convert.FromHexString("fafBFCFDFE") |> equal [| 250uy; 251uy; 252uy; 253uy; 254uy |] + Convert.FromHexString("0123456789abcdefABCDEF") + |> equal [| 1uy; 35uy; 69uy; 103uy; 137uy; 171uy; 205uy; 239uy; 171uy; 205uy; 239uy |] + Convert.FromHexString("") |> equal [||] + +[] +let ``test Convert.FromHexString checks its input`` () = + raisedExceptionType (fun () -> Convert.FromHexString(null: string)) |> equal "ArgumentNullException" + raisedExceptionType (fun () -> Convert.FromHexString("abc")) |> equal "FormatException" + raisedExceptionType (fun () -> Convert.FromHexString("zz")) |> equal "FormatException" + raisedExceptionType (fun () -> Convert.FromHexString("0x10")) |> equal "FormatException" + raisedExceptionType (fun () -> Convert.FromHexString("00 1")) |> equal "FormatException" + raisedExceptionType (fun () -> Convert.FromHexString("+1")) |> equal "FormatException" + // --- Guid --- [] diff --git a/tests/Beam/Util.fs b/tests/Beam/Util.fs index 28fd76e22..8a8512c09 100644 --- a/tests/Beam/Util.fs +++ b/tests/Beam/Util.fs @@ -77,7 +77,9 @@ let raisedExceptionType (f: unit -> 'a): string = "no exception" with | :? ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? ArgumentNullException -> "ArgumentNullException" | :? ArgumentException -> "ArgumentException" | :? InvalidOperationException -> "InvalidOperationException" | :? Collections.Generic.KeyNotFoundException -> "KeyNotFoundException" + | :? FormatException -> "FormatException" | e -> "Unexpected exception: " + e.Message diff --git a/tests/Dart/src/StringTests.fs b/tests/Dart/src/StringTests.fs index 62f24fa4b..ced331096 100644 --- a/tests/Dart/src/StringTests.fs +++ b/tests/Dart/src/StringTests.fs @@ -1119,6 +1119,46 @@ let tests() = String.filter (fun x -> x <> '.') "" |> equal "" + testCase "Convert.ToHexString works" <| fun () -> + let bytes = [| 250uy; 251uy; 252uy; 253uy; 254uy |] + Convert.ToHexString(bytes) + |> equal "FAFBFCFDFE" + Convert.ToHexString([| 0uy; 1uy; 15uy; 16uy |]) |> equal "00010F10" + Convert.ToHexString([||]) |> equal "" + Convert.ToHexString(bytes, 1, 3) |> equal "FBFCFD" + Convert.ToHexString(bytes, 5, 0) |> equal "" + + testCase "Convert.ToHexStringLower works" <| fun () -> + let bytes = [| 250uy; 251uy; 252uy; 253uy; 254uy |] + Convert.ToHexStringLower(bytes) + |> equal "fafbfcfdfe" + Convert.ToHexStringLower([| 0uy; 1uy; 15uy; 16uy |]) |> equal "00010f10" + Convert.ToHexStringLower([||]) |> equal "" + Convert.ToHexStringLower(bytes, 1, 3) |> equal "fbfcfd" + + testCase "Convert.ToHexString checks its arguments" <| fun () -> + let bytes = [| 250uy; 251uy; 252uy; 253uy; 254uy |] + raisedExceptionType (fun () -> Convert.ToHexString(bytes, -1, 1)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexString(bytes, 1, -1)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexString(bytes, 3, 3)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexString(bytes, 6, 0)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexStringLower(bytes, -1, 1)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexStringLower(bytes, 3, 3)) |> equal "ArgumentOutOfRangeException" + + testCase "Convert.FromHexString works" <| fun () -> + Convert.FromHexString("fafBFCFDFE") + |> equal [| 250uy; 251uy; 252uy; 253uy; 254uy |] + Convert.FromHexString("0123456789abcdefABCDEF") + |> equal [| 1uy; 35uy; 69uy; 103uy; 137uy; 171uy; 205uy; 239uy; 171uy; 205uy; 239uy |] + Convert.FromHexString("") |> equal [||] + + testCase "Convert.FromHexString checks its input" <| fun () -> + raisedExceptionType (fun () -> Convert.FromHexString("abc")) |> equal "FormatException" + raisedExceptionType (fun () -> Convert.FromHexString("zz")) |> equal "FormatException" + raisedExceptionType (fun () -> Convert.FromHexString("0x10")) |> equal "FormatException" + raisedExceptionType (fun () -> Convert.FromHexString("00 1")) |> equal "FormatException" + raisedExceptionType (fun () -> Convert.FromHexString("+1")) |> equal "FormatException" + // // TODO: Environment // #if FABLE_COMPILER // testCase "System.Environment.NewLine works" <| fun () -> diff --git a/tests/Dart/src/Tests.Util.fs b/tests/Dart/src/Tests.Util.fs index db6751fa4..fad7e0d47 100644 --- a/tests/Dart/src/Tests.Util.fs +++ b/tests/Dart/src/Tests.Util.fs @@ -29,10 +29,12 @@ let raisedExceptionType (f: unit -> 'a): string = "no exception" with | :? System.ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? System.ArgumentNullException -> "ArgumentNullException" | :? System.ArgumentException -> "ArgumentException" | :? System.InvalidOperationException -> "InvalidOperationException" | :? System.Collections.Generic.KeyNotFoundException -> "KeyNotFoundException" | :? System.IndexOutOfRangeException -> "IndexOutOfRangeException" + | :? System.FormatException -> "FormatException" | e -> "Unexpected exception: " + e.Message let private run (f: unit -> 'a) = diff --git a/tests/Js/Main/ConvertTests.fs b/tests/Js/Main/ConvertTests.fs index bf7cb8801..b04139546 100644 --- a/tests/Js/Main/ConvertTests.fs +++ b/tests/Js/Main/ConvertTests.fs @@ -2241,19 +2241,49 @@ let tests = let x = "101" Convert.ToString(101.m) |> equal x - // testCase "Convert.ToHexString works" <| fun () -> - // let bytes = [| 250uy; 251uy; 252uy; 253uy; 254uy |] - // Convert.ToHexString(bytes) - // |> equal "FAFBFCFDFE" - - // testCase "Convert.ToHexStringLower works" <| fun () -> - // let bytes = [| 250uy; 251uy; 252uy; 253uy; 254uy |] - // Convert.ToHexStringLower(bytes) - // |> equal "fafbfcfdfe" - - // testCase "Convert.FromHexString works" <| fun () -> - // Convert.FromHexString("fafBFCFDFE") - // |> equal [| 250uy; 251uy; 252uy; 253uy; 254uy |] + testCase "Convert.ToHexString works" <| fun () -> + let bytes = [| 250uy; 251uy; 252uy; 253uy; 254uy |] + Convert.ToHexString(bytes) + |> equal "FAFBFCFDFE" + Convert.ToHexString([| 0uy; 1uy; 15uy; 16uy |]) |> equal "00010F10" + Convert.ToHexString([||]) |> equal "" + Convert.ToHexString(bytes, 1, 3) |> equal "FBFCFD" + Convert.ToHexString(bytes, 5, 0) |> equal "" + + testCase "Convert.ToHexStringLower works" <| fun () -> + let bytes = [| 250uy; 251uy; 252uy; 253uy; 254uy |] + Convert.ToHexStringLower(bytes) + |> equal "fafbfcfdfe" + Convert.ToHexStringLower([| 0uy; 1uy; 15uy; 16uy |]) |> equal "00010f10" + Convert.ToHexStringLower([||]) |> equal "" + Convert.ToHexStringLower(bytes, 1, 3) |> equal "fbfcfd" + + testCase "Convert.ToHexString checks its arguments" <| fun () -> + let bytes = [| 250uy; 251uy; 252uy; 253uy; 254uy |] + raisedExceptionType (fun () -> Convert.ToHexString(null: byte[])) |> equal "ArgumentNullException" + raisedExceptionType (fun () -> Convert.ToHexString((null: byte[]), 0, 0)) |> equal "ArgumentNullException" + raisedExceptionType (fun () -> Convert.ToHexString(bytes, -1, 1)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexString(bytes, 1, -1)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexString(bytes, 3, 3)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexString(bytes, 6, 0)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexStringLower(null: byte[])) |> equal "ArgumentNullException" + raisedExceptionType (fun () -> Convert.ToHexStringLower(bytes, -1, 1)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexStringLower(bytes, 3, 3)) |> equal "ArgumentOutOfRangeException" + + testCase "Convert.FromHexString works" <| fun () -> + Convert.FromHexString("fafBFCFDFE") + |> equal [| 250uy; 251uy; 252uy; 253uy; 254uy |] + Convert.FromHexString("0123456789abcdefABCDEF") + |> equal [| 1uy; 35uy; 69uy; 103uy; 137uy; 171uy; 205uy; 239uy; 171uy; 205uy; 239uy |] + Convert.FromHexString("") |> equal [||] + + testCase "Convert.FromHexString checks its input" <| fun () -> + raisedExceptionType (fun () -> Convert.FromHexString(null: string)) |> equal "ArgumentNullException" + raisedExceptionType (fun () -> Convert.FromHexString("abc")) |> equal "FormatException" + raisedExceptionType (fun () -> Convert.FromHexString("zz")) |> equal "FormatException" + raisedExceptionType (fun () -> Convert.FromHexString("0x10")) |> equal "FormatException" + raisedExceptionType (fun () -> Convert.FromHexString("00 1")) |> equal "FormatException" + raisedExceptionType (fun () -> Convert.FromHexString("+1")) |> equal "FormatException" testCase "Convert.ToBase64String works" <| fun () -> let bytes = [| 2uy; 4uy; 6uy; 8uy; 10uy; 12uy; 14uy; 16uy; 18uy; 20uy |] diff --git a/tests/Python/TestConvert.fs b/tests/Python/TestConvert.fs index a77a50fca..ea04995e4 100644 --- a/tests/Python/TestConvert.fs +++ b/tests/Python/TestConvert.fs @@ -1060,22 +1060,54 @@ let ``test System.Convert.ToString Decimal works`` () = let x = "101" Convert.ToString(101.m) |> equal x -// [] -// let ``test Convert.ToHexString works`` () = -// let bytes = [| 250uy; 251uy; 252uy; 253uy; 254uy |] -// Convert.ToHexString(bytes) -// |> equal "FAFBFCFDFE" - -// [] -// let ``test Convert.ToHexStringLower works`` () = -// let bytes = [| 250uy; 251uy; 252uy; 253uy; 254uy |] -// Convert.ToHexStringLower(bytes) -// |> equal "fafbfcfdfe" - -// [] -// let ``test Convert.FromHexString works`` () = -// Convert.FromHexString("fafBFCFDFE") -// |> equal [| 250uy; 251uy; 252uy; 253uy; 254uy |] +[] +let ``test Convert.ToHexString works`` () = + let bytes = [| 250uy; 251uy; 252uy; 253uy; 254uy |] + Convert.ToHexString(bytes) + |> equal "FAFBFCFDFE" + Convert.ToHexString([| 0uy; 1uy; 15uy; 16uy |]) |> equal "00010F10" + Convert.ToHexString([||]) |> equal "" + Convert.ToHexString(bytes, 1, 3) |> equal "FBFCFD" + Convert.ToHexString(bytes, 5, 0) |> equal "" + +[] +let ``test Convert.ToHexStringLower works`` () = + let bytes = [| 250uy; 251uy; 252uy; 253uy; 254uy |] + Convert.ToHexStringLower(bytes) + |> equal "fafbfcfdfe" + Convert.ToHexStringLower([| 0uy; 1uy; 15uy; 16uy |]) |> equal "00010f10" + Convert.ToHexStringLower([||]) |> equal "" + Convert.ToHexStringLower(bytes, 1, 3) |> equal "fbfcfd" + +[] +let ``test Convert.ToHexString checks its arguments`` () = + let bytes = [| 250uy; 251uy; 252uy; 253uy; 254uy |] + raisedExceptionType (fun () -> Convert.ToHexString(null: byte[])) |> equal "ArgumentNullException" + raisedExceptionType (fun () -> Convert.ToHexString((null: byte[]), 0, 0)) |> equal "ArgumentNullException" + raisedExceptionType (fun () -> Convert.ToHexString(bytes, -1, 1)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexString(bytes, 1, -1)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexString(bytes, 3, 3)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexString(bytes, 6, 0)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexStringLower(null: byte[])) |> equal "ArgumentNullException" + raisedExceptionType (fun () -> Convert.ToHexStringLower(bytes, -1, 1)) |> equal "ArgumentOutOfRangeException" + raisedExceptionType (fun () -> Convert.ToHexStringLower(bytes, 3, 3)) |> equal "ArgumentOutOfRangeException" + +[] +let ``test Convert.FromHexString works`` () = + Convert.FromHexString("fafBFCFDFE") + |> equal [| 250uy; 251uy; 252uy; 253uy; 254uy |] + Convert.FromHexString("0123456789abcdefABCDEF") + |> equal [| 1uy; 35uy; 69uy; 103uy; 137uy; 171uy; 205uy; 239uy; 171uy; 205uy; 239uy |] + Convert.FromHexString("") |> equal [||] + +[] +let ``test Convert.FromHexString checks its input`` () = + raisedExceptionType (fun () -> Convert.FromHexString(null: string)) |> equal "ArgumentNullException" + raisedExceptionType (fun () -> Convert.FromHexString("abc")) |> equal "FormatException" + raisedExceptionType (fun () -> Convert.FromHexString("zz")) |> equal "FormatException" + raisedExceptionType (fun () -> Convert.FromHexString("0x10")) |> equal "FormatException" + raisedExceptionType (fun () -> Convert.FromHexString("00 1")) |> equal "FormatException" + raisedExceptionType (fun () -> Convert.FromHexString("+1")) |> equal "FormatException" [] let ``test Convert.ToBase64String works`` () = diff --git a/tests/Python/Util.fs b/tests/Python/Util.fs index 72637b7aa..9eeb542b5 100644 --- a/tests/Python/Util.fs +++ b/tests/Python/Util.fs @@ -113,8 +113,10 @@ let raisedExceptionType (f: unit -> 'a): string = "no exception" with | :? ArgumentOutOfRangeException -> "ArgumentOutOfRangeException" + | :? ArgumentNullException -> "ArgumentNullException" | :? ArgumentException -> "ArgumentException" | :? InvalidOperationException -> "InvalidOperationException" + | :? FormatException -> "FormatException" | e -> "Unexpected exception: " + e.Message let Int32Array = [|1;2|] diff --git a/tests/Rust/tests/src/ConvertTests.fs b/tests/Rust/tests/src/ConvertTests.fs index cd71ebf03..2626b50b5 100644 --- a/tests/Rust/tests/src/ConvertTests.fs +++ b/tests/Rust/tests/src/ConvertTests.fs @@ -1018,6 +1018,11 @@ let ``Convert.FromHexString works`` () = Convert.FromHexString("fafBFCFDFE") |> equal [| 250uy; 251uy; 252uy; 253uy; 254uy |] +[] +let ``Convert.FromHexString checks its input`` () = + throwsAnyError (fun () -> Convert.FromHexString("abc")) + throwsAnyError (fun () -> Convert.FromHexString("zz")) + [] let ``Convert.ToBase64String works`` () = let bytes = [| 2uy; 4uy; 6uy; 8uy; 10uy; 12uy; 14uy; 16uy; 18uy; 20uy |]