Skip to content

Commit

Permalink
🐛 fix isNonNullishPrimitive method to enable encoding Enums (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
techouse committed Apr 4, 2024
1 parent 75ce9e5 commit b166e56
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 31 deletions.
11 changes: 7 additions & 4 deletions lib/src/qs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ final class QS {
dynamic input, [
DecodeOptions options = const DecodeOptions(),
]) {
assert(
input is String? || input is Map?,
'The input must be a String or a Map',
);
if (!(input is String? || input is Map?)) {
throw ArgumentError.value(
input,
'input',
'The input must be a String or a Map',
);
}

if (input?.isEmpty ?? true) {
return Map.of({});
Expand Down
56 changes: 35 additions & 21 deletions lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -225,29 +225,17 @@ final class Utils {
Encoding charset = utf8,
Format? format = Format.rfc3986,
}) {
assert(
value is String ||
value is StringBuffer ||
value is ByteBuffer ||
value is num ||
value is bool ||
value is BigInt ||
value is Uri ||
value == null,
'$value is not a String, num, bool, BigInt, Uri, or null',
);

final String? str = value is ByteBuffer
? charset.decode(value.asUint8List())
: value?.toString();

if (str == null || str.isEmpty) {
if (str?.isEmpty ?? true) {
return '';
}

if (charset == latin1) {
// ignore: deprecated_member_use_from_same_package
return Utils.escape(str, format: format).replaceAllMapped(
return Utils.escape(str!, format: format).replaceAllMapped(
RegExp(r'%u[0-9a-f]{4}', caseSensitive: false),
(Match match) =>
'%26%23${int.parse(match.group(0)!.substring(2), radix: 16)}%3B',
Expand All @@ -256,7 +244,7 @@ final class Utils {

final StringBuffer buffer = StringBuffer();

for (int i = 0; i < str.length; ++i) {
for (int i = 0; i < str!.length; ++i) {
int c = str.codeUnitAt(i);

switch (c) {
Expand Down Expand Up @@ -413,12 +401,38 @@ final class Utils {
static dynamic apply<T>(dynamic val, T Function(T) fn) =>
val is Iterable ? val.map((item) => fn(item)) : fn(val);

static bool isNonNullishPrimitive(dynamic val, [bool skipNulls = false]) =>
(val is String && (skipNulls ? val.isNotEmpty : true)) ||
val is num ||
val is bool ||
val is BigInt ||
(val is Uri && (skipNulls ? val.toString().isNotEmpty : true));
static bool isNonNullishPrimitive(dynamic val, [bool skipNulls = false]) {
if (val is String) {
return skipNulls ? val.isNotEmpty : true;
}

if (val is num ||
val is BigInt ||
val is bool ||
val is Enum ||
val is DateTime ||
val is Duration) {
return true;
}

if (val is Uri) {
return skipNulls ? val.toString().isNotEmpty : true;
}

if (val is Object) {
if (val is Iterable ||
val is Map ||
val is Symbol ||
val is Record ||
val is Future ||
val is Undefined) {
return false;
}
return true;
}

return false;
}

static bool isEmpty(dynamic val) =>
val == null ||
Expand Down
8 changes: 8 additions & 0 deletions test/fixtures/dummy_enum.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
enum DummyEnum {
lorem,
ipsum,
dolor;

@override
String toString() => name;
}
120 changes: 116 additions & 4 deletions test/unit/encode_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ import 'dart:convert' show Encoding, latin1, utf8;
import 'dart:typed_data' show ByteBuffer, Uint8List;

import 'package:euc/jis.dart';
import 'package:qs_dart/src/enums/format.dart';
import 'package:qs_dart/src/enums/list_format.dart';
import 'package:qs_dart/src/models/encode_options.dart';
import 'package:qs_dart/src/qs.dart';
import 'package:qs_dart/qs_dart.dart';
import 'package:qs_dart/src/utils.dart';
import 'package:test/test.dart';

import '../fixtures/data/empty_test_cases.dart';
import '../fixtures/dummy_enum.dart';

void main() {
group('encode', () {
Expand Down Expand Up @@ -3005,4 +3003,118 @@ void main() {
);
});
});

group('encode non-Strings', () {
test('encodes a null value', () {
expect(QS.encode({'a': null}), equals('a='));
});

test('encodes a boolean value', () {
expect(QS.encode({'a': true}), equals('a=true'));
expect(QS.encode({'a': false}), equals('a=false'));
});

test('encodes a number value', () {
expect(QS.encode({'a': 0}), equals('a=0'));
expect(QS.encode({'a': 1}), equals('a=1'));
expect(QS.encode({'a': 1.1}), equals('a=1.1'));
});

test('encodes a buffer value', () {
expect(QS.encode({'a': utf8.encode('test').buffer}), equals('a=test'));
});

test('encodes a date value', () {
final DateTime now = DateTime.now();
final String str = 'a=${Uri.encodeComponent(now.toIso8601String())}';
expect(QS.encode({'a': now}), equals(str));
});

test('encodes a Duration', () {
final Duration duration = Duration(
days: 1,
hours: 2,
minutes: 3,
seconds: 4,
milliseconds: 5,
microseconds: 6);
final String str = 'a=${Uri.encodeComponent(duration.toString())}';
expect(QS.encode({'a': duration}), equals(str));
});

test('encodes a BigInt', () {
final BigInt bigInt = BigInt.from(1234567890123456789);
final String str = 'a=${Uri.encodeComponent(bigInt.toString())}';
expect(QS.encode({'a': bigInt}), equals(str));
});

test('encodes a list value', () {
expect(
QS.encode({
'a': [1, 2, 3]
}),
equals('a%5B0%5D=1&a%5B1%5D=2&a%5B2%5D=3'));
});

test('encodes a map value', () {
expect(
QS.encode({
'a': {'b': 'c'}
}),
equals('a%5Bb%5D=c'));
});

test('encodes a Uri', () {
expect(
QS.encode({'a': Uri.parse('https://example.com?foo=bar&baz=qux')}),
equals('a=https%3A%2F%2Fexample.com%3Ffoo%3Dbar%26baz%3Dqux'),
);
});

test('encodes a map with a null map as a child', () {
final Map<String, dynamic> obj = {
'a': {},
};
obj['a']['b'] = 'c';
expect(QS.encode(obj), equals('a%5Bb%5D=c'));
});

test('encodes a map with an enum as a child', () {
final Map<String, dynamic> obj = {
'a': DummyEnum.lorem,
'b': 'foo',
'c': 1,
'd': 1.234,
'e': true,
};
expect(
QS.encode(obj),
equals('a=lorem&b=foo&c=1&d=1.234&e=true'),
);
});

// does not encode
// Symbol
test('does not encode a Symbol', () {
expect(QS.encode({'a': #a}), equals(''));
});

// Record
test('does not encode a Record', () {
expect(QS.encode({'a': ('b', 'c')}), equals(''));

({int a, String b}) rec = (a: 1, b: 'a');
expect(QS.encode({'a': rec}), equals(''));
});

// Future
test('does not encode a Future', () {
expect(QS.encode({'a': Future.value('b')}), equals(''));
});

// Undefined
test('does not encode a Undefined', () {
expect(QS.encode({'a': const Undefined()}), equals(''));
});
});
}
30 changes: 28 additions & 2 deletions test/unit/utils_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import 'dart:convert' show latin1, utf8;

import 'package:qs_dart/src/enums/format.dart';
import 'package:qs_dart/src/models/undefined.dart';
import 'package:qs_dart/qs_dart.dart';
import 'package:qs_dart/src/utils.dart';
import 'package:test/test.dart';

import '../fixtures/dummy_enum.dart';

void main() {
group('Utils', () {
test('encode', () {
Expand All @@ -24,6 +25,31 @@ void main() {
Utils.encode('foo(bar)', format: Format.rfc1738),
equals('foo(bar)'),
);
expect(DummyEnum.lorem, isA<Enum>());
expect(Utils.encode(DummyEnum.lorem), equals('lorem'));
expect(
Utils.encode({
'foo': 'bar',
'baz': [
{'a': 'b'},
{'c': DummyEnum.dolor},
],
}),
equals(
'%7Bfoo%3A%20bar%2C%20baz%3A%20%5B%7Ba%3A%20b%7D%2C%20%7Bc%3A%20dolor%7D%5D%7D',
),
);
expect(
Utils.encode({
'filters': {
'name': 'foo',
'example': DummyEnum.lorem,
}
}),
equals(
'%7Bfilters%3A%20%7Bname%3A%20foo%2C%20example%3A%20lorem%7D%7D',
),
);
});

test('decode', () {
Expand Down

0 comments on commit b166e56

Please sign in to comment.