From 10f69a32ba7dfe127f35c7f04ea9b69206cb01fa Mon Sep 17 00:00:00 2001 From: evanweible-wf Date: Sat, 6 Mar 2021 14:52:00 -0800 Subject: [PATCH 1/6] Migrate to null safety --- CHANGELOG.md | 4 ++++ Dockerfile | 2 +- lib/fluri.dart | 14 +++++++------- pubspec.yaml | 7 +++---- test/fluri_test.dart | 6 +++--- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c97ac1..888aecb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.0.0 + +- Stable null safe release. + ## 1.3.0 - Add a `removeQueryParam()` method. diff --git a/Dockerfile b/Dockerfile index b915d84..de35360 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ FROM google/dart:2 WORKDIR /build/ ADD pubspec.yaml . -RUN pub get +RUN dart pub get FROM scratch diff --git a/lib/fluri.dart b/lib/fluri.dart index 0565f30..ecd10c1 100644 --- a/lib/fluri.dart +++ b/lib/fluri.dart @@ -96,8 +96,8 @@ class Fluri extends FluriMixin { /// Construct a new [Fluri] instance. /// /// A starting [uri] may be supplied which will be parsed by [Uri.parse]. - Fluri([String uri]) { - this.uri = Uri.parse(uri != null ? uri : ''); + Fluri([String? uri]) { + this.uri = Uri.parse(uri ?? ''); } /// Construct a new [Fluri] instance from another [Fluri] instance. @@ -142,7 +142,7 @@ class FluriMixin { /// The full URI. Uri get uri => _uri; - set uri(Uri uri) { + set uri(Uri? uri) { _uri = uri ?? Uri.parse(''); } @@ -234,7 +234,7 @@ class FluriMixin { void removeQueryParam(String param) { _uri = _uri.replace(queryParameters: { for (final key in queryParametersAll.keys) - if (key != param) key: List.of(queryParametersAll[key]), + if (key != param) key: List.of(queryParametersAll[key]!), }); } @@ -271,11 +271,11 @@ class FluriMixin { // Add the param value(s) while eliminating duplicates. // Throw an ArgumentError if any value is invalid. if (value is String && !newQueryParameters.containsValue(value)) { - newQueryParameters[key].add(value); + newQueryParameters[key]!.add(value); } else if (value is Iterable) { for (var v in value) { - if (!newQueryParameters[key].contains(v)) { - newQueryParameters[key].add(v); + if (!newQueryParameters[key]!.contains(v)) { + newQueryParameters[key]!.add(v); } } } else { diff --git a/pubspec.yaml b/pubspec.yaml index fe3ce47..fd66232 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,9 +1,8 @@ name: fluri -version: 1.3.0 +version: 2.0.0 description: Fluri is a fluent URI library built to make URI mutation easy. homepage: https://github.com/Workiva/fluri environment: - sdk: ">=2.3.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dev_dependencies: - dependency_validator: ^1.4.0 - test: ^1.9.2 + test: ^1.16.5 diff --git a/test/fluri_test.dart b/test/fluri_test.dart index ca54ae1..d914dde 100644 --- a/test/fluri_test.dart +++ b/test/fluri_test.dart @@ -241,7 +241,7 @@ void main() { const url = 'http://example.com/path/to/resource?limit=10&format=list#test'; group('Fluri', () { - Fluri fluri; + late Fluri fluri; setUp(() { fluri = Fluri(url); @@ -294,8 +294,8 @@ void main() { }); group('FluriMixin', () { - ExtendingClass extender; - MixingClass mixer; + late ExtendingClass extender; + late MixingClass mixer; setUp(() { extender = ExtendingClass(url); From c8c1d5cffd10c7d78d9aa5b021c72c7e0dd808fa Mon Sep 17 00:00:00 2001 From: evanweible-wf Date: Sat, 6 Mar 2021 14:52:33 -0800 Subject: [PATCH 2/6] Move from Travis CI to GitHub actions --- .github/workflows/dart_ci.yaml | 31 +++++++++++++++++++++++++++++++ .travis.yml | 26 -------------------------- 2 files changed, 31 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/dart_ci.yaml delete mode 100644 .travis.yml diff --git a/.github/workflows/dart_ci.yaml b/.github/workflows/dart_ci.yaml new file mode 100644 index 0000000..ce66af3 --- /dev/null +++ b/.github/workflows/dart_ci.yaml @@ -0,0 +1,31 @@ +name: Dart CI + +on: + push: + branches: + - 'master' + - 'test_consume_*' + pull_request: + branches: + - '*' + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + sdk: [ stable, beta, dev ] + steps: + - uses: actions/checkout@v2 + - uses: dart-lang/setup-dart@v1 + with: + sdk: ${{ matrix.sdk }} + - name: Install dependencies + run: dart pub get + - name: Check formatting + run: dart format --output=none --set-exit-if-changed . + if: ${{ matrix.sdk }} == 'stable' + - name: Analyze project source + run: dart analyze + - name: Run tests + run: dart test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5eb5e1c..0000000 --- a/.travis.yml +++ /dev/null @@ -1,26 +0,0 @@ -language: dart - -# Re-use downloaded pub packages everywhere. -cache: - directories: - - $HOME/.pub-cache - -jobs: - include: - - dart: 2.3.0 - name: "SDK: 2.3.0" - script: - - dartanalyzer . - - pub run test - - dart: stable - name: "SDK: stable" - script: - - dartanalyzer . - - dartfmt -n --set-exit-if-changed . - - pub run dependency_validator - - pub run test - - dart: dev - name: "SDK: dev" - script: - - dartanalyzer . - - pub run test From d32555e1e51d0c7ad7745cf89412d28eea5e8ab0 Mon Sep 17 00:00:00 2001 From: evanweible-wf Date: Sat, 6 Mar 2021 14:52:42 -0800 Subject: [PATCH 3/6] Use workiva_analysis_options --- analysis_options.yaml | 573 +----------------------------------------- pubspec.yaml | 1 + 2 files changed, 2 insertions(+), 572 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index eca95d4..f09d80b 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,572 +1 @@ -# analysis_options.yaml docs: https://www.dartlang.org/guides/language/analysis-options -analyzer: - # Strong mode is required. Applies to the current project. - strong-mode: - # When compiling to JS, both implicit options apply to the current - # project and all dependencies. They are useful to find possible - # Type fixes or areas for explicit typing. - implicit-casts: true - implicit-dynamic: true - -# ALL lint rules are included. Unused lints should be commented -# out with a reason. An up to date list of all options is here -# http://dart-lang.github.io/linter/lints/options/options.html -# Descriptions of each rule is here http://dart-lang.github.io/linter/lints/ -# -# To ignore a lint rule on a case by case basic in code just add a comment -# above it or at the end of the line like: // ignore: -# example: // ignore: invalid_assignment, const_initialized_with_non_constant_value -# -# More info about configuring analysis_options.yaml files -# https://www.dartlang.org/guides/language/analysis-options#excluding-lines-within-a-file -linter: - rules: - # Declare method return types. - # recommendation: recommended - # reason: React component render() method can return either ReactElement or false - # 0 issues - - always_declare_return_types - - # Separate the control structure expression from its statement. - # recommendation: optional - # 0 issues - - always_put_control_body_on_new_line - - # Put @required named parameters first. - # recommendation: recommended - # 0 issues - - always_put_required_named_parameters_first - - # Use @required. - # recommendation: recommended - # 0 issues - - always_require_non_null_named_parameters - - # Specify type annotations. - # recommendation: recommended - # FIXME: 55 lint(s) - # - always_specify_types - - # Annotate overridden members. - # recommendation: required - # 0 issues - - annotate_overrides - - # Avoid annotating with dynamic when not required. - # recommendation: optional - # FIXME: 1 lint(s) - # - avoid_annotating_with_dynamic - - # Avoid using `as`. - # recommendation: optional - # 0 issues - - avoid_as - - # Avoid bool literals in conditional expressions. - # recommendation: optional - # 0 issues - - avoid_bool_literals_in_conditional_expressions - - # Avoid catches without on clauses. - # recommendation: optional - # 0 issues - - avoid_catches_without_on_clauses - - # Don't explicitly catch Error or types that implement it. - # recommendation: optional - # 0 issues - - avoid_catching_errors - - # Avoid defining a class that contains only static members. - # recommendation: recommended - # 0 issues - - avoid_classes_with_only_static_members - - # Avoid empty else statements. - # recommendation: required - # 0 issues - - avoid_empty_else - - # Avoid using `forEach` with a function literal. - # recommendation: recommended - # reason: Use for (x in y) or forEach(someFunc) instead - # 0 issues - - avoid_function_literals_in_foreach_calls - - # Don't explicitly initialize variables to null. - # recommendation: recommended - # 0 issues - - avoid_init_to_null - - # Don't check for null in custom == operators. - # recommendation: recommended - # 0 issues - - avoid_null_checks_in_equality_operators - - # Avoid positional boolean parameters. - # recommendation: recommended - # 0 issues - - avoid_positional_boolean_parameters - - # Avoid private typedef functions. - # recommendation: optional - # 0 issues - - avoid_private_typedef_functions - - # Avoid relative imports for files in `lib/`. - # recommendation: recommended - # reason: JS compilation will be faster without relative imports. Use package imports. - # 0 issues - - avoid_relative_lib_imports - - # Don't rename parameters of overridden methods. - # recommendation: optional - # 0 issues - - avoid_renaming_method_parameters - - # Avoid return types on setters. - # recommendation: required - # 0 issues - - avoid_return_types_on_setters - - # Avoid returning null from members whose return type is bool, double, int, or num. - # recommendation: optional - # 0 issues - - avoid_returning_null - - # Avoid returning this from methods just to enable a fluent interface. - # recommendation: recommended - # 0 issues - - avoid_returning_this - - # Avoid setters without getters. - # recommendation: recommended - # 0 issues - - avoid_setters_without_getters - - # Avoid single cascade in expression statements. - # recommendation: optional - # 0 issues - - avoid_single_cascade_in_expression_statements - - # Avoid slow async `dart:io` methods. - # recommendation: recommended - # 0 issues - - avoid_slow_async_io - - # Avoid types as parameter names. - # recommendation: optional - # 0 issues - - avoid_types_as_parameter_names - - # Avoid annotating types for function expression parameters. - # recommendation: optional - # 0 issues - - avoid_types_on_closure_parameters - - # Avoid defining unused paramters in constructors. - # recommendation: recommended - # 0 issues - - avoid_unused_constructor_parameters - - # Await only futures. - # recommendation: required - # 0 issues - - await_only_futures - - # Name types using UpperCamelCase. - # recommendation: optional - # 0 issues - - camel_case_types - - # Cancel instances of dart.async.StreamSubscription. - # recommendation: required - # 0 issues - - cancel_subscriptions - - # Cascade consecutive method invocations on the same reference. - # recommendation: optional - # 0 issues - - cascade_invocations - - # Close instances of `dart.core.Sink`. - # recommendation: required - # 0 issues - - close_sinks - - # Only reference in scope identifiers in doc comments. - # recommendation: optional - # 0 issues - - comment_references - - # Prefer using lowerCamelCase for constant names. - # recommendation: optional - # 0 issues - - constant_identifier_names - - # Avoid control flow in finally blocks. - # recommendation: required - # 0 issues - - control_flow_in_finally - - # Adhere to Effective Dart Guide directives sorting conventions. - # recommendation: required - # 0 issues - - directives_ordering - - # Avoid empty catch blocks. - # recommendation: recommended - # 0 issues - - empty_catches - - # Use `;` instead of `{}` for empty constructor bodies. - # recommendation: recommended - # 0 issues - - empty_constructor_bodies - - # Avoid empty statements. - # recommendation: required - # 0 issues - - empty_statements - - # Always override `hashCode` if overriding `==`. - # recommendation: required - # 0 issues - - hash_and_equals - - # Don't import implementation files from another package. - # recommendation: required - # 0 issues - - implementation_imports - - # Conditions should not unconditionally evaluate to `true` or to `false`. - # recommendation: required - # 0 issues - - invariant_booleans - - # Invocation of Iterable.contains with references of unrelated types. - # recommendation: required - # 0 issues - - iterable_contains_unrelated_type - - # Join return statement with assignment when possible. - # recommendation: optional - # 0 issues - - join_return_with_assignment - - # Name libraries and source files using `lowercase_with_underscores`. - # recommendation: recommended - # 0 issues - - library_names - - # Use `lowercase_with_underscores` when specifying a library prefix. - # recommendation: recommended - # 0 issues - - library_prefixes - - # Invocation of `remove` with references of unrelated types. - # recommendation: required - # 0 issues - - list_remove_unrelated_type - - # Boolean expression composed only with literals. - # recommendation: required - # 0 issues - - literal_only_boolean_expressions - - # Don't use adjacent strings in list. - # recommendation: required - # 0 issues - - no_adjacent_strings_in_list - - # Don't use more than one case with same value. - # recommendation: required - # 0 issues - - no_duplicate_case_values - - # Name non-constant identifiers using lowerCamelCase. - # recommendation: recommended - # 0 issues - - non_constant_identifier_names - - # Omit type annotations for local variables. - # recommendation: avoid - # reason: Conflicts with always_specify_types. Recommend commenting this one out. - # 0 issues - # - omit_local_variable_types - - # Avoid defining a one-member abstract class when a simple function will do. - # recommendation: optional - # 0 issues - - one_member_abstracts - - # Only throw instances of classes extending either Exception or Error. - # recommendation: required - # 0 issues - - only_throw_errors - - # Don't override fields. - # recommendation: optional - # 0 issues - - overridden_fields - - # Provide doc comments for all public APIs. - # recommendation: optional - # 0 issues - - package_api_docs - - # Use `lowercase_with_underscores` for package names. - # recommendation: recommended - # 0 issues - - package_names - - # Prefix library names with the package name and a dot-separated path. - # recommendation: recommended - # 0 issues - - package_prefixed_library_names - - # Don't reassign references to parameters of functions or methods. - # recommendation: optional - # 0 issues - - parameter_assignments - - # Use adjacent strings to concatenate string literals. - # recommendation: optional - # 0 issues - - prefer_adjacent_string_concatenation - - # Prefer putting asserts in initializer list. - # recommendation: optional - # 0 issues - - prefer_asserts_in_initializer_lists - - # Use collection literals when possible. - # recommendation: optional - # 0 issues - - prefer_collection_literals - - # Prefer using `??=` over testing for null. - # recommendation: optional - # 0 issues - - prefer_conditional_assignment - - # Prefer const with constant constructors. - # recommendation: optional - # 0 issues - - prefer_const_constructors - - # Prefer declare const constructors on `@immutable` classes. - # recommendation: optional - # 0 issues - - prefer_const_constructors_in_immutables - - # Prefer const over final for declarations. - # recommendation: recommended - # 0 issues - - prefer_const_declarations - - # Prefer const literals as parameters of constructors on @immutable classes. - # recommendation: optional - # 0 issues - - prefer_const_literals_to_create_immutables - - # Prefer defining constructors instead of static methods to create instances. - # recommendation: optional - # 0 issues - - prefer_constructors_over_static_methods - - # Use contains for `List` and `String` instances. - # recommendation: optional - # 0 issues - - prefer_contains - - # Prefer equal for default values. - # recommendation: optional - # FIXME: 1 lint(s) - # - prefer_equal_for_default_values - - # Use => for short members whose body is a single return statement. - # recommendation: optional - # 0 issues - - prefer_expression_function_bodies - - # Private field could be final. - # recommendation: optional - # 0 issues - - prefer_final_fields - - # Prefer final for variable declaration if reference is not reassigned. - # recommendation: optional - # reason: Generates a lot of lint since people use var a lot for local variables. - # 0 issues - - prefer_final_locals - - # Use `forEach` to only apply a function to all the elements. - # recommendation: optional - # 0 issues - - prefer_foreach - - # Use a function declaration to bind a function to a name. - # recommendation: optional - # 0 issues - - prefer_function_declarations_over_variables - - # Use initializing formals when possible. - # recommendation: recommended - # 0 issues - - prefer_initializing_formals - - # Use interpolation to compose strings and values. - # recommendation: optional - # 0 issues - - prefer_interpolation_to_compose_strings - - # Use `isEmpty` for Iterables and Maps. - # recommendation: optional - # 0 issues - - prefer_is_empty - - # Use `isNotEmpty` for Iterables and Maps. - # recommendation: optional - # 0 issues - - prefer_is_not_empty - - # Prefer single quotes where they won't require escape sequences. - # recommendation: optional - # 0 issues - - prefer_single_quotes - - # Prefer typing uninitialized variables and fields. - # recommendation: optional - # 0 issues - - prefer_typing_uninitialized_variables - - # Document all public members. - # recommendation: optional - # reason: Can get annoying for React component lifecycle methods, constructors. - # 0 issues - - public_member_api_docs - - # Property getter recursively returns itself. - # recommendation: optional - # 0 issues - - recursive_getters - - # Prefer using /// for doc comments. - # recommendation: recommended - # 0 issues - - slash_for_doc_comments - - # Sort constructor declarations before method declarations. - # recommendation: optional - # 0 issues - - sort_constructors_first - - # Sort unnamed constructor declarations first. - # recommendation: optional - # 0 issues - - sort_unnamed_constructors_first - - # Test type arguments in operator ==(Object other). - # recommendation: required - # 0 issues - - test_types_in_equals - - # Avoid `throw` in finally block. - # recommendation: required - # 0 issues - - throw_in_finally - - # Type annotate public APIs. - # recommendation: recommended - # reason: React component render() method can return either ReactElement or false. Use overrides. - # 0 issues - - type_annotate_public_apis - - # Don't type annotate initializing formals. - # recommendation: optional - # 0 issues - - type_init_formals - - # Await future-returning functions inside async function bodies. - # recommendation: recommended - # 0 issues - - unawaited_futures - - # Avoid using braces in interpolation when not needed. - # recommendation: optional - # 0 issues - - unnecessary_brace_in_string_interps - - # Avoid wrapping fields in getters and setters just to be "safe". - # recommendation: optional - # 0 issues - - unnecessary_getters_setters - - # Don't create a lambda when a tear-off will do. - # recommendation: optional - # 0 issues - - unnecessary_lambdas - - # Avoid null in null-aware assignment. - # recommendation: optional - # 0 issues - - unnecessary_null_aware_assignments - - # Avoid using `null` in `if null` operators. - # recommendation: optional - # 0 issues - - unnecessary_null_in_if_null_operators - - # Don't override a method to do a super method invocation with the same parameters. - # recommendation: optional - # 0 issues - - unnecessary_overrides - - # Unnecessary parenthesis can be removed. - # recommendation: optional - # 0 issues - - unnecessary_parenthesis - - # Avoid using unnecessary statements. - # recommendation: required - # 0 issues - - unnecessary_statements - - # Don't access members with `this` unless avoiding shadowing. - # recommendation: recommended - # 0 issues - - unnecessary_this - - # Equality operator `==` invocation with references of unrelated types. - # recommendation: required - # reason: Comparing references of a type where neither is a subtype of the other most likely will return false and might not reflect programmer's intent. - # 0 issues - - unrelated_type_equality_checks - - # Use rethrow to rethrow a caught exception. - # recommendation: optional - # 0 issues - - use_rethrow_when_possible - - # Use a setter for operations that conceptually change a property. - # recommendation: optional - # 0 issues - - use_setters_to_change_properties - - # Use string buffer to compose strings. - # recommendation: optional - # 0 issues - - use_string_buffers - - # Start the name of the method with to/_to or as/_as if applicable. - # recommendation: optional - # 0 issues - - use_to_and_as_if_applicable - - # Use valid regular expression syntax. - # recommendation: required - # 0 issues - - valid_regexps - - +include: package:workiva_analysis_options/v1.yaml diff --git a/pubspec.yaml b/pubspec.yaml index fd66232..b98add8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,3 +6,4 @@ environment: sdk: '>=2.12.0 <3.0.0' dev_dependencies: test: ^1.16.5 + workiva_analysis_options: ^1.1.0 From f69cd5dfc381552250a85fcb718b4577cbea4013 Mon Sep 17 00:00:00 2001 From: evanweible-wf Date: Thu, 1 Apr 2021 15:07:05 -0700 Subject: [PATCH 4/6] Fix formatting if condition --- .github/workflows/dart_ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dart_ci.yaml b/.github/workflows/dart_ci.yaml index ce66af3..6e7437f 100644 --- a/.github/workflows/dart_ci.yaml +++ b/.github/workflows/dart_ci.yaml @@ -24,7 +24,7 @@ jobs: run: dart pub get - name: Check formatting run: dart format --output=none --set-exit-if-changed . - if: ${{ matrix.sdk }} == 'stable' + if: ${{ matrix.sdk == 'stable' }} - name: Analyze project source run: dart analyze - name: Run tests From a7da91d812d1702f82c4877c27272e90df4d63b2 Mon Sep 17 00:00:00 2001 From: evanweible-wf Date: Thu, 8 Apr 2021 08:28:57 -0700 Subject: [PATCH 5/6] Add test to verify dedupe bug in updateQuery --- test/fluri_test.dart | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/fluri_test.dart b/test/fluri_test.dart index d914dde..ee705aa 100644 --- a/test/fluri_test.dart +++ b/test/fluri_test.dart @@ -202,6 +202,26 @@ void commonFluriTests(FluriMixin getFluri()) { unorderedEquals(['binary', 'json', 'text'])); }); + test('should eliminate duplicate values when updating query parameters', + () { + expect( + (getFluri() + ..updateQuery({ + 'dedupe': ['foo', 'bar'] + }) + ..updateQuery({ + 'dedupe': ['foo', 'baz'] + }, mergeValues: true)) + .queryParametersAll, + containsPair('dedupe', ['foo', 'bar', 'baz'])); + expect( + (getFluri() + ..updateQuery({'dedupe': 'foobaz'}) + ..updateQuery({'dedupe': 'foobaz'}, mergeValues: true)) + .queryParametersAll, + containsPair('dedupe', ['foobaz'])); + }); + test( 'should throw if invalid param value is given when setting a single query parameter', () { From 51910d8ab42d9ac56d9b5cb5b9811a888e9f4685 Mon Sep 17 00:00:00 2001 From: evanweible-wf Date: Thu, 8 Apr 2021 08:29:14 -0700 Subject: [PATCH 6/6] Fix dedupe logic in updateQuery by using Sets --- lib/fluri.dart | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/fluri.dart b/lib/fluri.dart index ecd10c1..72f380c 100644 --- a/lib/fluri.dart +++ b/lib/fluri.dart @@ -253,11 +253,11 @@ class FluriMixin { void updateQuery( Map*/ > queryParametersToUpdate, {bool mergeValues = false}) { - final newQueryParameters = >{}; + final newQueryParameters = >{}; // Copy the current query param values. queryParametersAll.forEach((key, value) { - newQueryParameters[key] = List.from(value); + newQueryParameters[key] = value.toSet(); }); // Update the query using the given params. @@ -265,19 +265,15 @@ class FluriMixin { // Initialize or reset the value list if it either does not already exist, // or if we're not merging values. if (!mergeValues || !newQueryParameters.containsKey(key)) { - newQueryParameters[key] = []; + newQueryParameters[key] = {}; } // Add the param value(s) while eliminating duplicates. // Throw an ArgumentError if any value is invalid. - if (value is String && !newQueryParameters.containsValue(value)) { + if (value is String) { newQueryParameters[key]!.add(value); } else if (value is Iterable) { - for (var v in value) { - if (!newQueryParameters[key]!.contains(v)) { - newQueryParameters[key]!.add(v); - } - } + newQueryParameters[key]!.addAll(value); } else { throw ArgumentError('Query parameter "$key" has value "$value" ' 'which is not a String or an Iterable.');