Compare commits
27
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58fec4bf48
|
||
|
|
dd380a71db
|
||
|
|
0d130ce84a
|
||
|
|
28d9aa69e7
|
||
|
|
45e737dde2
|
||
|
|
2d1826235f
|
||
|
|
f558685f11
|
||
|
|
b21aa04557
|
||
|
|
01df7a0dff
|
||
|
|
8909274543
|
||
|
|
89f94cf1ff | ||
|
|
65774010aa | ||
|
|
42810c4fb3 | ||
|
|
39d19b4c7b | ||
|
|
8a0af954bf
|
||
|
|
94b0ff01ad
|
||
|
|
741f05e1be
|
||
|
|
d0f1d07363
|
||
|
|
08ac6d8fda
|
||
|
|
9203df94a0
|
||
|
|
a7204f588f | ||
|
|
81367e5f3a
|
||
|
|
14cb3485e4
|
||
|
|
58d3b70bee
|
||
|
|
ea4a30ca00
|
||
|
|
83b75ee0e5 | ||
|
|
9e9ec9ba9b
|
+2
-1
@@ -195,4 +195,5 @@ $RECYCLE.BIN/
|
|||||||
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
|
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
*.iml
|
*.iml
|
||||||
|
google-services.json
|
||||||
+105
@@ -3,6 +3,111 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## 2022-07-12
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- There are no breaking changes in this release.
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`wyatt_analysis` - `v2.2.1`](#wyatt_analysis---v221)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `wyatt_analysis` - `v2.2.1`
|
||||||
|
|
||||||
|
- **FIX**: import right version.
|
||||||
|
|
||||||
|
|
||||||
|
## 2022-07-11
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- There are no breaking changes in this release.
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`wyatt_analysis` - `v2.2.0`](#wyatt_analysis---v220)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `wyatt_analysis` - `v2.2.0`
|
||||||
|
|
||||||
|
- **FIX**: rename to follow melos versioning and remove redundant rule.
|
||||||
|
- **FEAT**: promote experimental, update analyzer rules and add some linter rules.
|
||||||
|
|
||||||
|
|
||||||
|
## 2022-07-10
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- There are no breaking changes in this release.
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`form_bloc_example` - `v1.1.0`](#form_bloc_example---v110)
|
||||||
|
- [`wyatt_form_bloc` - `v0.0.3`](#wyatt_form_bloc---v003)
|
||||||
|
- [`authentication_bloc_example` - `v1.0.1`](#authentication_bloc_example---v101)
|
||||||
|
- [`wyatt_authentication_bloc` - `v0.2.1+1`](#wyatt_authentication_bloc---v0211)
|
||||||
|
|
||||||
|
Packages with dependency updates only:
|
||||||
|
|
||||||
|
> Packages listed below depend on other packages in this workspace that have had changes. Their versions have been incremented to bump the minimum dependency versions of the packages they depend upon in this project.
|
||||||
|
|
||||||
|
- `authentication_bloc_example` - `v1.0.1`
|
||||||
|
- `wyatt_authentication_bloc` - `v0.2.1+1`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `form_bloc_example` - `v1.1.0`
|
||||||
|
|
||||||
|
- **FEAT**: add list option validator.
|
||||||
|
- **DOCS**: add example app.
|
||||||
|
|
||||||
|
#### `wyatt_form_bloc` - `v0.0.3`
|
||||||
|
|
||||||
|
- **FEAT**: add helpers.
|
||||||
|
- **FEAT**: add metadata on inputs.
|
||||||
|
- **FEAT**: update to latest dart version.
|
||||||
|
|
||||||
|
|
||||||
|
## 2022-06-24
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- There are no breaking changes in this release.
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`wyatt_http_client` - `v1.2.0`](#wyatt_http_client---v120)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `wyatt_http_client` - `v1.2.0`
|
||||||
|
|
||||||
|
- **FEAT**: add new middleware feature.
|
||||||
|
- **FEAT**: implements doublelinked list for middlewares.
|
||||||
|
- **FEAT**: [WIP] implements middleware system.
|
||||||
|
- **FEAT**: [WIP] work on middleware feature.
|
||||||
|
|
||||||
|
|
||||||
## 2022-05-23
|
## 2022-05-23
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
## 2.2.1
|
||||||
|
|
||||||
|
- **FIX**: import right version.
|
||||||
|
|
||||||
|
## 2.2.0
|
||||||
|
|
||||||
|
- **FIX**: rename to follow melos versioning and remove redundant rule.
|
||||||
|
- **FEAT**: promote experimental, update analyzer rules and add some linter rules.
|
||||||
|
|
||||||
## 2.1.0
|
## 2.1.0
|
||||||
|
|
||||||
- **FEAT**: add experimental rules in appropriate file.
|
- **FEAT**: add experimental rules in appropriate file.
|
||||||
|
|||||||
@@ -0,0 +1,206 @@
|
|||||||
|
# Copyright (C) 2022 WYATT GROUP
|
||||||
|
# Please see the AUTHORS file for details.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
language:
|
||||||
|
strict-casts: true
|
||||||
|
strict-inference: true
|
||||||
|
strict-raw-types: true
|
||||||
|
|
||||||
|
errors:
|
||||||
|
todo: ignore
|
||||||
|
close_sinks: ignore
|
||||||
|
missing_required_param: error
|
||||||
|
missing_return: error
|
||||||
|
|
||||||
|
exclude:
|
||||||
|
- example/**
|
||||||
|
- lib/l10n/**
|
||||||
|
- lib/generated/**
|
||||||
|
- lib/generated_plugin_registrant.dart
|
||||||
|
- test/.test_coverage.dart
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
- always_declare_return_types
|
||||||
|
- always_require_non_null_named_parameters
|
||||||
|
- always_put_control_body_on_new_line
|
||||||
|
- always_put_required_named_parameters_first
|
||||||
|
- always_use_package_imports
|
||||||
|
- annotate_overrides
|
||||||
|
- avoid_bool_literals_in_conditional_expressions
|
||||||
|
- avoid_catching_errors
|
||||||
|
- avoid_double_and_int_checks
|
||||||
|
- avoid_dynamic_calls
|
||||||
|
- avoid_empty_else
|
||||||
|
- avoid_equals_and_hash_code_on_mutable_classes
|
||||||
|
- avoid_escaping_inner_quotes
|
||||||
|
- avoid_field_initializers_in_const_classes
|
||||||
|
- avoid_function_literals_in_foreach_calls
|
||||||
|
- avoid_init_to_null
|
||||||
|
- avoid_js_rounded_ints
|
||||||
|
- avoid_null_checks_in_equality_operators
|
||||||
|
- avoid_positional_boolean_parameters
|
||||||
|
- avoid_private_typedef_functions
|
||||||
|
- avoid_redundant_argument_values
|
||||||
|
- avoid_relative_lib_imports
|
||||||
|
- avoid_renaming_method_parameters
|
||||||
|
- avoid_return_types_on_setters
|
||||||
|
- avoid_returning_null
|
||||||
|
- avoid_returning_null_for_future
|
||||||
|
- avoid_returning_null_for_void
|
||||||
|
- avoid_returning_this
|
||||||
|
- avoid_setters_without_getters
|
||||||
|
- avoid_shadowing_type_parameters
|
||||||
|
- avoid_single_cascade_in_expression_statements
|
||||||
|
- avoid_slow_async_io
|
||||||
|
- avoid_type_to_string
|
||||||
|
- avoid_types_as_parameter_names
|
||||||
|
- avoid_types_on_closure_parameters
|
||||||
|
- avoid_unused_constructor_parameters
|
||||||
|
- avoid_void_async
|
||||||
|
- await_only_futures
|
||||||
|
- camel_case_extensions
|
||||||
|
- camel_case_types
|
||||||
|
- cancel_subscriptions
|
||||||
|
- cascade_invocations
|
||||||
|
- cast_nullable_to_non_nullable
|
||||||
|
- comment_references
|
||||||
|
- conditional_uri_does_not_exist
|
||||||
|
- constant_identifier_names
|
||||||
|
- control_flow_in_finally
|
||||||
|
- curly_braces_in_flow_control_structures
|
||||||
|
- depend_on_referenced_packages
|
||||||
|
- deprecated_consistency
|
||||||
|
- directives_ordering
|
||||||
|
- empty_catches
|
||||||
|
- empty_constructor_bodies
|
||||||
|
- empty_statements
|
||||||
|
- eol_at_end_of_file
|
||||||
|
- exhaustive_cases
|
||||||
|
- file_names
|
||||||
|
- flutter_style_todos
|
||||||
|
- hash_and_equals
|
||||||
|
- implementation_imports
|
||||||
|
- iterable_contains_unrelated_type
|
||||||
|
- join_return_with_assignment
|
||||||
|
- leading_newlines_in_multiline_strings
|
||||||
|
- library_names
|
||||||
|
- library_prefixes
|
||||||
|
- library_private_types_in_public_api
|
||||||
|
- lines_longer_than_80_chars
|
||||||
|
- list_remove_unrelated_type
|
||||||
|
- literal_only_boolean_expressions
|
||||||
|
- missing_whitespace_between_adjacent_strings
|
||||||
|
- no_adjacent_strings_in_list
|
||||||
|
- no_default_cases
|
||||||
|
- no_duplicate_case_values
|
||||||
|
- no_leading_underscores_for_local_identifiers
|
||||||
|
- no_runtimeType_toString
|
||||||
|
- non_constant_identifier_names
|
||||||
|
- noop_primitive_operations
|
||||||
|
- null_check_on_nullable_type_parameter
|
||||||
|
- null_closures
|
||||||
|
- one_member_abstracts
|
||||||
|
- only_throw_errors
|
||||||
|
- overridden_fields
|
||||||
|
- package_api_docs
|
||||||
|
- package_names
|
||||||
|
- package_prefixed_library_names
|
||||||
|
- parameter_assignments
|
||||||
|
- prefer_adjacent_string_concatenation
|
||||||
|
- prefer_asserts_in_initializer_lists
|
||||||
|
- prefer_asserts_with_message
|
||||||
|
- prefer_collection_literals
|
||||||
|
- prefer_conditional_assignment
|
||||||
|
- prefer_const_constructors
|
||||||
|
- prefer_const_constructors_in_immutables
|
||||||
|
- prefer_const_declarations
|
||||||
|
- prefer_const_literals_to_create_immutables
|
||||||
|
- prefer_constructors_over_static_methods
|
||||||
|
- prefer_contains
|
||||||
|
- prefer_equal_for_default_values
|
||||||
|
- prefer_expression_function_bodies
|
||||||
|
- prefer_final_fields
|
||||||
|
- prefer_final_in_for_each
|
||||||
|
- prefer_final_locals
|
||||||
|
- prefer_for_elements_to_map_fromIterable
|
||||||
|
- prefer_function_declarations_over_variables
|
||||||
|
- prefer_generic_function_type_aliases
|
||||||
|
- prefer_if_elements_to_conditional_expressions
|
||||||
|
- prefer_if_null_operators
|
||||||
|
- prefer_initializing_formals
|
||||||
|
- prefer_inlined_adds
|
||||||
|
- prefer_int_literals
|
||||||
|
- prefer_interpolation_to_compose_strings
|
||||||
|
- prefer_is_empty
|
||||||
|
- prefer_is_not_empty
|
||||||
|
- prefer_is_not_operator
|
||||||
|
- prefer_iterable_whereType
|
||||||
|
- prefer_mixin
|
||||||
|
- prefer_null_aware_operators
|
||||||
|
- prefer_null_aware_method_calls
|
||||||
|
- prefer_single_quotes
|
||||||
|
- prefer_spread_collections
|
||||||
|
- prefer_typing_uninitialized_variables
|
||||||
|
- prefer_void_to_null
|
||||||
|
- provide_deprecation_message
|
||||||
|
- recursive_getters
|
||||||
|
- require_trailing_commas
|
||||||
|
- slash_for_doc_comments
|
||||||
|
- sort_unnamed_constructors_first
|
||||||
|
- test_types_in_equals
|
||||||
|
- throw_in_finally
|
||||||
|
- tighten_type_of_initializing_formals
|
||||||
|
- type_annotate_public_apis
|
||||||
|
- type_init_formals
|
||||||
|
- unawaited_futures
|
||||||
|
- unnecessary_await_in_return
|
||||||
|
- unnecessary_brace_in_string_interps
|
||||||
|
- unnecessary_const
|
||||||
|
- unnecessary_constructor_name
|
||||||
|
- unnecessary_getters_setters
|
||||||
|
- unnecessary_lambdas
|
||||||
|
- unnecessary_late
|
||||||
|
- unnecessary_new
|
||||||
|
- unnecessary_null_aware_assignments
|
||||||
|
- unnecessary_null_checks
|
||||||
|
- unnecessary_null_in_if_null_operators
|
||||||
|
- unnecessary_nullable_for_final_variable_declarations
|
||||||
|
- unnecessary_overrides
|
||||||
|
- unnecessary_parenthesis
|
||||||
|
- unnecessary_raw_strings
|
||||||
|
- unnecessary_statements
|
||||||
|
- unnecessary_string_escapes
|
||||||
|
- unnecessary_string_interpolations
|
||||||
|
- unnecessary_this
|
||||||
|
- unrelated_type_equality_checks
|
||||||
|
- unsafe_html
|
||||||
|
- use_enums
|
||||||
|
- use_function_type_syntax_for_parameters
|
||||||
|
- use_if_null_to_convert_nulls_to_bools
|
||||||
|
- use_is_even_rather_than_modulo
|
||||||
|
- use_late_for_private_fields_and_variables
|
||||||
|
- use_named_constants
|
||||||
|
- use_raw_strings
|
||||||
|
- use_rethrow_when_possible
|
||||||
|
- use_setters_to_change_properties
|
||||||
|
- use_string_buffers
|
||||||
|
- use_super_parameters
|
||||||
|
- use_test_throws_matchers
|
||||||
|
- use_to_and_as_if_applicable
|
||||||
|
- valid_regexps
|
||||||
|
- void_checks
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
# Copyright (C) 2022 WYATT GROUP
|
||||||
|
# Please see the AUTHORS file for details.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
language:
|
||||||
|
strict-casts: true
|
||||||
|
strict-inference: true
|
||||||
|
strict-raw-types: true
|
||||||
|
|
||||||
|
errors:
|
||||||
|
todo: ignore
|
||||||
|
close_sinks: ignore
|
||||||
|
missing_required_param: error
|
||||||
|
missing_return: error
|
||||||
|
|
||||||
|
exclude:
|
||||||
|
- example/**
|
||||||
|
- lib/l10n/**
|
||||||
|
- lib/generated/**
|
||||||
|
- lib/generated_plugin_registrant.dart
|
||||||
|
- test/.test_coverage.dart
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
- always_declare_return_types
|
||||||
|
- always_require_non_null_named_parameters
|
||||||
|
- always_put_control_body_on_new_line
|
||||||
|
- always_put_required_named_parameters_first
|
||||||
|
- always_use_package_imports
|
||||||
|
- annotate_overrides
|
||||||
|
- avoid_bool_literals_in_conditional_expressions
|
||||||
|
- avoid_catching_errors
|
||||||
|
- avoid_double_and_int_checks
|
||||||
|
- avoid_dynamic_calls
|
||||||
|
- avoid_empty_else
|
||||||
|
- avoid_equals_and_hash_code_on_mutable_classes
|
||||||
|
- avoid_escaping_inner_quotes
|
||||||
|
- avoid_field_initializers_in_const_classes
|
||||||
|
- avoid_function_literals_in_foreach_calls
|
||||||
|
- avoid_init_to_null
|
||||||
|
- avoid_js_rounded_ints
|
||||||
|
- avoid_null_checks_in_equality_operators
|
||||||
|
- avoid_positional_boolean_parameters
|
||||||
|
- avoid_private_typedef_functions
|
||||||
|
- avoid_redundant_argument_values
|
||||||
|
- avoid_relative_lib_imports
|
||||||
|
- avoid_renaming_method_parameters
|
||||||
|
- avoid_return_types_on_setters
|
||||||
|
- avoid_returning_null
|
||||||
|
- avoid_returning_null_for_future
|
||||||
|
- avoid_returning_null_for_void
|
||||||
|
- avoid_returning_this
|
||||||
|
- avoid_setters_without_getters
|
||||||
|
- avoid_shadowing_type_parameters
|
||||||
|
- avoid_single_cascade_in_expression_statements
|
||||||
|
- avoid_slow_async_io
|
||||||
|
- avoid_type_to_string
|
||||||
|
- avoid_types_as_parameter_names
|
||||||
|
- avoid_types_on_closure_parameters
|
||||||
|
- avoid_unused_constructor_parameters
|
||||||
|
- avoid_void_async
|
||||||
|
- await_only_futures
|
||||||
|
- camel_case_extensions
|
||||||
|
- camel_case_types
|
||||||
|
- cancel_subscriptions
|
||||||
|
- cascade_invocations
|
||||||
|
- cast_nullable_to_non_nullable
|
||||||
|
- comment_references
|
||||||
|
- conditional_uri_does_not_exist
|
||||||
|
- constant_identifier_names
|
||||||
|
- control_flow_in_finally
|
||||||
|
- curly_braces_in_flow_control_structures
|
||||||
|
- depend_on_referenced_packages
|
||||||
|
- deprecated_consistency
|
||||||
|
- directives_ordering
|
||||||
|
- empty_catches
|
||||||
|
- empty_constructor_bodies
|
||||||
|
- empty_statements
|
||||||
|
- eol_at_end_of_file
|
||||||
|
- exhaustive_cases
|
||||||
|
- file_names
|
||||||
|
- flutter_style_todos
|
||||||
|
- hash_and_equals
|
||||||
|
- implementation_imports
|
||||||
|
- iterable_contains_unrelated_type
|
||||||
|
- join_return_with_assignment
|
||||||
|
- leading_newlines_in_multiline_strings
|
||||||
|
- library_names
|
||||||
|
- library_prefixes
|
||||||
|
- library_private_types_in_public_api
|
||||||
|
- lines_longer_than_80_chars
|
||||||
|
- list_remove_unrelated_type
|
||||||
|
- literal_only_boolean_expressions
|
||||||
|
- missing_whitespace_between_adjacent_strings
|
||||||
|
- no_adjacent_strings_in_list
|
||||||
|
- no_default_cases
|
||||||
|
- no_duplicate_case_values
|
||||||
|
- no_leading_underscores_for_local_identifiers
|
||||||
|
- no_runtimeType_toString
|
||||||
|
- non_constant_identifier_names
|
||||||
|
- noop_primitive_operations
|
||||||
|
- null_check_on_nullable_type_parameter
|
||||||
|
- null_closures
|
||||||
|
- one_member_abstracts
|
||||||
|
- only_throw_errors
|
||||||
|
- overridden_fields
|
||||||
|
- package_api_docs
|
||||||
|
- package_names
|
||||||
|
- package_prefixed_library_names
|
||||||
|
- parameter_assignments
|
||||||
|
- prefer_adjacent_string_concatenation
|
||||||
|
- prefer_asserts_in_initializer_lists
|
||||||
|
- prefer_asserts_with_message
|
||||||
|
- prefer_collection_literals
|
||||||
|
- prefer_conditional_assignment
|
||||||
|
- prefer_const_constructors
|
||||||
|
- prefer_const_constructors_in_immutables
|
||||||
|
- prefer_const_declarations
|
||||||
|
- prefer_const_literals_to_create_immutables
|
||||||
|
- prefer_constructors_over_static_methods
|
||||||
|
- prefer_contains
|
||||||
|
- prefer_equal_for_default_values
|
||||||
|
- prefer_expression_function_bodies
|
||||||
|
- prefer_final_fields
|
||||||
|
- prefer_final_in_for_each
|
||||||
|
- prefer_final_locals
|
||||||
|
- prefer_for_elements_to_map_fromIterable
|
||||||
|
- prefer_function_declarations_over_variables
|
||||||
|
- prefer_generic_function_type_aliases
|
||||||
|
- prefer_if_elements_to_conditional_expressions
|
||||||
|
- prefer_if_null_operators
|
||||||
|
- prefer_initializing_formals
|
||||||
|
- prefer_inlined_adds
|
||||||
|
- prefer_int_literals
|
||||||
|
- prefer_interpolation_to_compose_strings
|
||||||
|
- prefer_is_empty
|
||||||
|
- prefer_is_not_empty
|
||||||
|
- prefer_is_not_operator
|
||||||
|
- prefer_iterable_whereType
|
||||||
|
- prefer_mixin
|
||||||
|
- prefer_null_aware_operators
|
||||||
|
- prefer_null_aware_method_calls
|
||||||
|
- prefer_single_quotes
|
||||||
|
- prefer_spread_collections
|
||||||
|
- prefer_typing_uninitialized_variables
|
||||||
|
- prefer_void_to_null
|
||||||
|
- provide_deprecation_message
|
||||||
|
- recursive_getters
|
||||||
|
- require_trailing_commas
|
||||||
|
- slash_for_doc_comments
|
||||||
|
- sort_unnamed_constructors_first
|
||||||
|
- test_types_in_equals
|
||||||
|
- throw_in_finally
|
||||||
|
- tighten_type_of_initializing_formals
|
||||||
|
- type_annotate_public_apis
|
||||||
|
- type_init_formals
|
||||||
|
- unawaited_futures
|
||||||
|
- unnecessary_await_in_return
|
||||||
|
- unnecessary_brace_in_string_interps
|
||||||
|
- unnecessary_const
|
||||||
|
- unnecessary_constructor_name
|
||||||
|
- unnecessary_getters_setters
|
||||||
|
- unnecessary_lambdas
|
||||||
|
- unnecessary_late
|
||||||
|
- unnecessary_new
|
||||||
|
- unnecessary_null_aware_assignments
|
||||||
|
- unnecessary_null_checks
|
||||||
|
- unnecessary_null_in_if_null_operators
|
||||||
|
- unnecessary_nullable_for_final_variable_declarations
|
||||||
|
- unnecessary_overrides
|
||||||
|
- unnecessary_parenthesis
|
||||||
|
- unnecessary_raw_strings
|
||||||
|
- unnecessary_statements
|
||||||
|
- unnecessary_string_escapes
|
||||||
|
- unnecessary_string_interpolations
|
||||||
|
- unnecessary_this
|
||||||
|
- unrelated_type_equality_checks
|
||||||
|
- unsafe_html
|
||||||
|
- use_enums
|
||||||
|
- use_function_type_syntax_for_parameters
|
||||||
|
- use_if_null_to_convert_nulls_to_bools
|
||||||
|
- use_is_even_rather_than_modulo
|
||||||
|
- use_late_for_private_fields_and_variables
|
||||||
|
- use_named_constants
|
||||||
|
- use_raw_strings
|
||||||
|
- use_rethrow_when_possible
|
||||||
|
- use_setters_to_change_properties
|
||||||
|
- use_string_buffers
|
||||||
|
- use_super_parameters
|
||||||
|
- use_test_throws_matchers
|
||||||
|
- use_to_and_as_if_applicable
|
||||||
|
- valid_regexps
|
||||||
|
- void_checks
|
||||||
+13
-3
@@ -14,9 +14,19 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
include: package:wyatt_analysis/analysis_options.flutter.2.0.0.yaml
|
include: package:wyatt_analysis/analysis_options.2.1.1.yaml
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
rules:
|
rules:
|
||||||
- use_super_parameters
|
- avoid_print
|
||||||
- use_enums
|
- avoid_unnecessary_containers
|
||||||
|
- avoid_web_libraries_in_flutter
|
||||||
|
- no_logic_in_create_state
|
||||||
|
- sized_box_for_whitespace
|
||||||
|
- sized_box_shrink_expand
|
||||||
|
- sort_child_properties_last
|
||||||
|
- use_build_context_synchronously
|
||||||
|
- use_colored_box
|
||||||
|
- use_decorated_box
|
||||||
|
- use_full_hex_values_for_flutter_colors
|
||||||
|
- use_key_in_widget_constructors
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Copyright (C) 2022 WYATT GROUP
|
||||||
|
# Please see the AUTHORS file for details.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
include: package:wyatt_analysis/analysis_options.2.2.1.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
- avoid_print
|
||||||
|
- avoid_unnecessary_containers
|
||||||
|
- avoid_web_libraries_in_flutter
|
||||||
|
- no_logic_in_create_state
|
||||||
|
- sized_box_for_whitespace
|
||||||
|
- sized_box_shrink_expand
|
||||||
|
- sort_child_properties_last
|
||||||
|
- use_build_context_synchronously
|
||||||
|
- use_colored_box
|
||||||
|
- use_decorated_box
|
||||||
|
- use_full_hex_values_for_flutter_colors
|
||||||
|
- use_key_in_widget_constructors
|
||||||
@@ -14,4 +14,4 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
include: package:wyatt_analysis/analysis_options.flutter.2.0.0.yaml
|
include: package:wyatt_analysis/analysis_options.flutter.2.2.1.yaml
|
||||||
|
|||||||
@@ -14,4 +14,4 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
include: package:wyatt_analysis/analysis_options.2.0.0.yaml
|
include: package:wyatt_analysis/analysis_options.2.2.1.yaml
|
||||||
|
|||||||
@@ -19,4 +19,4 @@
|
|||||||
/// and [Wyatt Studio](https://wyatt-studio.fr).
|
/// and [Wyatt Studio](https://wyatt-studio.fr).
|
||||||
library wyatt_analysis;
|
library wyatt_analysis;
|
||||||
|
|
||||||
const String wyattAnalysisVersion = '2.0.0';
|
const String wyattAnalysisVersion = '2.2.1';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: wyatt_analysis
|
name: wyatt_analysis
|
||||||
description: Lint rules for Dart and Flutter used internally at Wyatt and Wyatt Studio.
|
description: Lint rules for Dart and Flutter used internally at Wyatt and Wyatt Studio.
|
||||||
repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_analysis
|
repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_analysis
|
||||||
version: 2.1.0
|
version: 2.2.1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.12.0 <3.0.0'
|
sdk: '>=2.12.0 <3.0.0'
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 0.2.1+1
|
||||||
|
|
||||||
|
- Update a dependency to the latest release.
|
||||||
|
|
||||||
## 0.2.1
|
## 0.2.1
|
||||||
|
|
||||||
- **FEAT**: add google, facebook, apple, twitter login.
|
- **FEAT**: add google, facebook, apple, twitter login.
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
- Update a dependency to the latest release.
|
||||||
|
|
||||||
@@ -14,6 +14,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"oauth_client": [
|
"oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "136771801992-nps7ic22shstvgdv0c2ojvbkj3dkntum.apps.googleusercontent.com",
|
||||||
|
"client_type": 1,
|
||||||
|
"android_info": {
|
||||||
|
"package_name": "com.example.authentication_bloc_example",
|
||||||
|
"certificate_hash": "5d8790309e13b68c35e5d4d8437c35c6d15e6131"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
|
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
|
||||||
"client_type": 3
|
"client_type": 3
|
||||||
@@ -77,58 +85,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_info": {
|
|
||||||
"mobilesdk_app_id": "1:136771801992:android:d20e0361057e815197203d",
|
|
||||||
"android_client_info": {
|
|
||||||
"package_name": "com.example.example"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"oauth_client": [
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-n2pq8oqutvrqj58e05hbavvc7n1jdfjb.apps.googleusercontent.com",
|
|
||||||
"client_type": 1,
|
|
||||||
"android_info": {
|
|
||||||
"package_name": "com.example.example",
|
|
||||||
"certificate_hash": "5d8790309e13b68c35e5d4d8437c35c6d15e6131"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-pj9s88ao7d99pnp4ace75nj1abtmoq7e.apps.googleusercontent.com",
|
|
||||||
"client_type": 1,
|
|
||||||
"android_info": {
|
|
||||||
"package_name": "com.example.example",
|
|
||||||
"certificate_hash": "bc8faf31c0b0085dc358b2319684e10c93f42719"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
|
|
||||||
"client_type": 3
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"api_key": [
|
|
||||||
{
|
|
||||||
"current_key": "AIzaSyAYS14uXupkS158Q5QAFP1864UrUN_yDSk"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"services": {
|
|
||||||
"appinvite_service": {
|
|
||||||
"other_platform_oauth_client": [
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
|
|
||||||
"client_type": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-e585bm1n9b3lv89t4phrl9u0glsg52ua.apps.googleusercontent.com",
|
|
||||||
"client_type": 2,
|
|
||||||
"ios_info": {
|
|
||||||
"bundle_id": "com.example.example"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"configuration_version": "1"
|
"configuration_version": "1"
|
||||||
|
|||||||
@@ -31,23 +31,29 @@ class App extends StatelessWidget {
|
|||||||
|
|
||||||
static FormData getNormalFormData() {
|
static FormData getNormalFormData() {
|
||||||
return const FormData([
|
return const FormData([
|
||||||
FormEntry(formFieldName, Name.pure()),
|
FormInput(formFieldName, Name.pure()),
|
||||||
FormEntry(formFieldPhone, Phone.pure()),
|
FormInput(formFieldPhone, Phone.pure()),
|
||||||
FormEntry(formFieldPro, Boolean.pure()),
|
FormInput(formFieldPro, Boolean.pure()),
|
||||||
FormEntry(formFieldConfirmedPassword, ConfirmedPassword.pure(),
|
FormInput(
|
||||||
export: false),
|
formFieldConfirmedPassword,
|
||||||
|
ConfirmedPassword.pure(),
|
||||||
|
metadata: FormInputMetadata(export: false),
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static FormData getProFormData() {
|
static FormData getProFormData() {
|
||||||
return const FormData([
|
return const FormData([
|
||||||
FormEntry(formFieldName, Name.pure()),
|
FormInput(formFieldName, Name.pure()),
|
||||||
FormEntry(formFieldPhone, Phone.pure()),
|
FormInput(formFieldPhone, Phone.pure()),
|
||||||
FormEntry(formFieldPro, Boolean.pure()),
|
FormInput(formFieldPro, Boolean.pure()),
|
||||||
FormEntry(formFieldSiren, Siren.pure()),
|
FormInput(formFieldSiren, Siren.pure()),
|
||||||
FormEntry(formFieldIban, Iban.pure()),
|
FormInput(formFieldIban, Iban.pure()),
|
||||||
FormEntry(formFieldConfirmedPassword, ConfirmedPassword.pure(),
|
FormInput(
|
||||||
export: false),
|
formFieldConfirmedPassword,
|
||||||
|
ConfirmedPassword.pure(),
|
||||||
|
metadata: FormInputMetadata(export: false),
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,42 +109,42 @@ class App extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
AuthenticationRepositoryInterface _authenticationRepository =
|
AuthenticationRepositoryInterface authenticationRepository =
|
||||||
AuthenticationRepositoryFirebase();
|
AuthenticationRepositoryFirebase();
|
||||||
|
|
||||||
AuthenticationCubit _authenticationCubit = AuthenticationCubit(
|
AuthenticationCubit authenticationCubit = AuthenticationCubit(
|
||||||
authenticationRepository: _authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
onAuthSuccess: onAuthSuccess,
|
onAuthSuccess: onAuthSuccess,
|
||||||
);
|
);
|
||||||
|
|
||||||
SignUpCubit _signUpCubit = SignUpCubit(
|
SignUpCubit signUpCubit = SignUpCubit(
|
||||||
authenticationRepository: _authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
authenticationCubit: _authenticationCubit,
|
authenticationCubit: authenticationCubit,
|
||||||
entries: getNormalFormData(),
|
entries: getNormalFormData(),
|
||||||
onSignUpSuccess: onSignUpSuccess,
|
onSignUpSuccess: onSignUpSuccess,
|
||||||
);
|
);
|
||||||
|
|
||||||
SignInCubit _signInCubit = SignInCubit(
|
SignInCubit signInCubit = SignInCubit(
|
||||||
authenticationRepository: _authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
authenticationCubit: _authenticationCubit,
|
authenticationCubit: authenticationCubit,
|
||||||
);
|
);
|
||||||
|
|
||||||
return MultiRepositoryProvider(
|
return MultiRepositoryProvider(
|
||||||
providers: [
|
providers: [
|
||||||
RepositoryProvider<AuthenticationRepositoryInterface>(
|
RepositoryProvider<AuthenticationRepositoryInterface>(
|
||||||
create: (context) => _authenticationRepository,
|
create: (context) => authenticationRepository,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: MultiBlocProvider(
|
child: MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider<AuthenticationCubit>(
|
BlocProvider<AuthenticationCubit>(
|
||||||
create: (context) => _authenticationCubit..init(),
|
create: (context) => authenticationCubit..init(),
|
||||||
),
|
),
|
||||||
BlocProvider<SignUpCubit>(
|
BlocProvider<SignUpCubit>(
|
||||||
create: (context) => _signUpCubit,
|
create: (context) => signUpCubit,
|
||||||
),
|
),
|
||||||
BlocProvider<SignInCubit>(
|
BlocProvider<SignInCubit>(
|
||||||
create: (context) => _signInCubit,
|
create: (context) => signInCubit,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: const AppView(),
|
child: const AppView(),
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class _NameInput extends StatelessWidget {
|
|||||||
labelText: 'name',
|
labelText: 'name',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText:
|
errorText:
|
||||||
state.data.input(formFieldName).invalid ? 'invalid name' : null,
|
state.data.isNotValid(formFieldName) ? 'invalid name' : null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -58,7 +58,7 @@ class _PhoneInput extends StatelessWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'phone',
|
labelText: 'phone',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: state.data.input(formFieldPhone).invalid
|
errorText: state.data.isNotValid(formFieldPhone)
|
||||||
? 'invalid phone'
|
? 'invalid phone'
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
@@ -81,7 +81,7 @@ class _SirenInput extends StatelessWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'siren',
|
labelText: 'siren',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: state.data.input(formFieldSiren).invalid
|
errorText: state.data.isNotValid(formFieldSiren)
|
||||||
? 'invalid SIREN'
|
? 'invalid SIREN'
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
@@ -105,7 +105,7 @@ class _IbanInput extends StatelessWidget {
|
|||||||
labelText: 'iban',
|
labelText: 'iban',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText:
|
errorText:
|
||||||
state.data.input(formFieldIban).invalid ? 'invalid IBAN' : null,
|
state.data.isNotValid(formFieldIban) ? 'invalid IBAN' : null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -149,8 +149,7 @@ class _PasswordInput extends StatelessWidget {
|
|||||||
.read<SignUpCubit>()
|
.read<SignUpCubit>()
|
||||||
.state
|
.state
|
||||||
.data
|
.data
|
||||||
.input(formFieldConfirmedPassword)
|
.valueOf<String, ValidationError>(formFieldConfirmedPassword),
|
||||||
.value,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -185,7 +184,7 @@ class _ConfirmPasswordInput extends StatelessWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'confirm password',
|
labelText: 'confirm password',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: state.data.input(formFieldConfirmedPassword).invalid
|
errorText: state.data.isNotValid(formFieldConfirmedPassword)
|
||||||
? 'passwords do not match'
|
? 'passwords do not match'
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
@@ -203,7 +202,7 @@ class _CheckIsProInput extends StatelessWidget {
|
|||||||
trailing: BlocBuilder<SignUpCubit, SignUpState>(
|
trailing: BlocBuilder<SignUpCubit, SignUpState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Checkbox(
|
return Checkbox(
|
||||||
value: state.data.input<bool>(formFieldPro).value,
|
value: state.data.valueOf<bool, ValidationError>(formFieldPro),
|
||||||
onChanged: (isPro) {
|
onChanged: (isPro) {
|
||||||
final value =
|
final value =
|
||||||
isPro!; // tristate is false, so value can't be null
|
isPro!; // tristate is false, so value can't be null
|
||||||
@@ -297,7 +296,7 @@ class SignUpForm extends StatelessWidget {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
BlocBuilder<SignUpCubit, SignUpState>(
|
BlocBuilder<SignUpCubit, SignUpState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.data.input<bool>(formFieldPro).value) {
|
if (state.data.valueOf<bool, ValidationError>(formFieldPro)) {
|
||||||
return Column(children: [
|
return Column(children: [
|
||||||
_SirenInput(),
|
_SirenInput(),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.0.0+1
|
version: 1.0.1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.16.2 <3.0.0"
|
sdk: ">=2.16.2 <3.0.0"
|
||||||
@@ -38,7 +38,7 @@ dependencies:
|
|||||||
wyatt_form_bloc:
|
wyatt_form_bloc:
|
||||||
git:
|
git:
|
||||||
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
||||||
ref: wyatt_form_bloc-v0.0.2
|
ref: wyatt_form_bloc-v0.0.3
|
||||||
path: packages/wyatt_form_bloc
|
path: packages/wyatt_form_bloc
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
@@ -55,7 +55,7 @@ dev_dependencies:
|
|||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
# activated in the `analysis_options.yaml` file located at the root of your
|
||||||
# package. See that file for information about deactivating specific lint
|
# package. See that file for information about deactivating specific lint
|
||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^1.0.0
|
flutter_lints: ^2.0.0
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@ class PasswordResetCubit extends Cubit<PasswordResetState> {
|
|||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
email: email,
|
email: email,
|
||||||
status: FormData.validate([email]),
|
status: FormStatus.validate([email]),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class SignInCubit extends Cubit<SignInState> {
|
|||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
email: email,
|
email: email,
|
||||||
status: FormData.validate([email, state.password]),
|
status: FormStatus.validate([email, state.password]),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ class SignInCubit extends Cubit<SignInState> {
|
|||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
password: password,
|
password: password,
|
||||||
status: FormData.validate([state.email, password]),
|
status: FormStatus.validate([state.email, password]),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,16 +44,16 @@ class SignUpCubit extends Cubit<SignUpState> {
|
|||||||
void emailChanged(String value) {
|
void emailChanged(String value) {
|
||||||
final Email email = Email.dirty(value);
|
final Email email = Email.dirty(value);
|
||||||
|
|
||||||
final List<FormInput<dynamic, ValidationError>> inputsToValidate = [
|
final List<FormInputValidator<dynamic, ValidationError>> toValidate = [
|
||||||
email,
|
email,
|
||||||
state.password,
|
state.password,
|
||||||
...state.data.inputs<dynamic>(),
|
...state.data.validators<dynamic, ValidationError>(),
|
||||||
];
|
];
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
email: email,
|
email: email,
|
||||||
status: FormData.validate(inputsToValidate),
|
status: FormStatus.validate(toValidate),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -61,26 +61,26 @@ class SignUpCubit extends Cubit<SignUpState> {
|
|||||||
void passwordChanged(String value) {
|
void passwordChanged(String value) {
|
||||||
final Password password = Password.dirty(value);
|
final Password password = Password.dirty(value);
|
||||||
|
|
||||||
final List<FormInput<dynamic, ValidationError>> inputsToValidate = [
|
final List<FormInputValidator<dynamic, ValidationError>> toValidate = [
|
||||||
state.email,
|
state.email,
|
||||||
password,
|
password,
|
||||||
...state.data.inputs<dynamic>(),
|
...state.data.validators<dynamic, ValidationError>(),
|
||||||
];
|
];
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
password: password,
|
password: password,
|
||||||
status: FormData.validate(inputsToValidate),
|
status: FormStatus.validate(toValidate),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take from wyatt_form_bloc/wyatt_form_bloc.dart
|
// Take from wyatt_form_bloc/wyatt_form_bloc.dart
|
||||||
void dataChanged<T>(String field, FormInput dirtyValue) {
|
void dataChanged<T>(String field, FormInputValidator dirtyValue) {
|
||||||
final _form = state.data.clone();
|
final _form = state.data.clone();
|
||||||
|
|
||||||
if (_form.contains(field)) {
|
if (_form.contains(field)) {
|
||||||
_form.update(field, dirtyValue);
|
_form.updateValidator(field, dirtyValue);
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Form field $field not found');
|
throw Exception('Form field $field not found');
|
||||||
}
|
}
|
||||||
@@ -88,8 +88,12 @@ class SignUpCubit extends Cubit<SignUpState> {
|
|||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
data: _form,
|
data: _form,
|
||||||
status: FormData.validate(
|
status: FormStatus.validate(
|
||||||
[state.email, state.password, ..._form.inputs<dynamic>()],
|
[
|
||||||
|
state.email,
|
||||||
|
state.password,
|
||||||
|
...state.data.validators<dynamic, ValidationError>(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -120,8 +124,12 @@ class SignUpCubit extends Cubit<SignUpState> {
|
|||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
data: _form,
|
data: _form,
|
||||||
status: FormData.validate(
|
status: FormStatus.validate(
|
||||||
[state.email, state.password, ..._form.inputs<dynamic>()],
|
[
|
||||||
|
state.email,
|
||||||
|
state.password,
|
||||||
|
...state.data.validators<dynamic, ValidationError>(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: wyatt_authentication_bloc
|
name: wyatt_authentication_bloc
|
||||||
description: Authentication BLoC for Flutter
|
description: Authentication BLoC for Flutter
|
||||||
repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_authentication_bloc
|
repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_authentication_bloc
|
||||||
version: 0.2.1
|
version: 0.2.1+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.15.1 <3.0.0"
|
sdk: ">=2.15.1 <3.0.0"
|
||||||
@@ -23,7 +23,7 @@ dependencies:
|
|||||||
wyatt_form_bloc:
|
wyatt_form_bloc:
|
||||||
git:
|
git:
|
||||||
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
||||||
ref: wyatt_form_bloc-v0.0.2
|
ref: wyatt_form_bloc-v0.0.3
|
||||||
path: packages/wyatt_form_bloc
|
path: packages/wyatt_form_bloc
|
||||||
|
|
||||||
|
|
||||||
@@ -35,5 +35,5 @@ dev_dependencies:
|
|||||||
wyatt_analysis:
|
wyatt_analysis:
|
||||||
git:
|
git:
|
||||||
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
||||||
ref: wyatt_analysis-v2.1.0
|
ref: wyatt_analysis-v2.2.1
|
||||||
path: packages/wyatt_analysis
|
path: packages/wyatt_analysis
|
||||||
|
|||||||
@@ -68,9 +68,9 @@ void main() {
|
|||||||
SignUpCubit(
|
SignUpCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
authenticationCubit: authenticationCubit,
|
authenticationCubit: authenticationCubit,
|
||||||
entries: FormData.empty(),
|
entries: const FormData.empty(),
|
||||||
).state,
|
).state,
|
||||||
SignUpState(data: FormData.empty()),
|
const SignUpState(data: FormData.empty()),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -80,11 +80,11 @@ void main() {
|
|||||||
build: () => SignUpCubit(
|
build: () => SignUpCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
authenticationCubit: authenticationCubit,
|
authenticationCubit: authenticationCubit,
|
||||||
entries: FormData.empty(),
|
entries: const FormData.empty(),
|
||||||
),
|
),
|
||||||
act: (SignUpCubit cubit) => cubit.emailChanged(invalidEmailString),
|
act: (SignUpCubit cubit) => cubit.emailChanged(invalidEmailString),
|
||||||
expect: () => <SignUpState>[
|
expect: () => <SignUpState>[
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
email: invalidEmail,
|
email: invalidEmail,
|
||||||
status: FormStatus.invalid,
|
status: FormStatus.invalid,
|
||||||
data: FormData.empty(),
|
data: FormData.empty(),
|
||||||
@@ -97,15 +97,15 @@ void main() {
|
|||||||
build: () => SignUpCubit(
|
build: () => SignUpCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
authenticationCubit: authenticationCubit,
|
authenticationCubit: authenticationCubit,
|
||||||
entries: FormData.empty(),
|
entries: const FormData.empty(),
|
||||||
),
|
),
|
||||||
seed: () => SignUpState(
|
seed: () => const SignUpState(
|
||||||
password: validPassword,
|
password: validPassword,
|
||||||
data: FormData.empty(),
|
data: FormData.empty(),
|
||||||
),
|
),
|
||||||
act: (SignUpCubit cubit) => cubit.emailChanged(validEmailString),
|
act: (SignUpCubit cubit) => cubit.emailChanged(validEmailString),
|
||||||
expect: () => <SignUpState>[
|
expect: () => <SignUpState>[
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
email: validEmail,
|
email: validEmail,
|
||||||
password: validPassword,
|
password: validPassword,
|
||||||
status: FormStatus.valid,
|
status: FormStatus.valid,
|
||||||
@@ -121,12 +121,12 @@ void main() {
|
|||||||
build: () => SignUpCubit(
|
build: () => SignUpCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
authenticationCubit: authenticationCubit,
|
authenticationCubit: authenticationCubit,
|
||||||
entries: FormData.empty(),
|
entries: const FormData.empty(),
|
||||||
),
|
),
|
||||||
act: (SignUpCubit cubit) =>
|
act: (SignUpCubit cubit) =>
|
||||||
cubit.passwordChanged(invalidPasswordString),
|
cubit.passwordChanged(invalidPasswordString),
|
||||||
expect: () => <SignUpState>[
|
expect: () => <SignUpState>[
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
password: invalidPassword,
|
password: invalidPassword,
|
||||||
status: FormStatus.invalid,
|
status: FormStatus.invalid,
|
||||||
data: FormData.empty(),
|
data: FormData.empty(),
|
||||||
@@ -139,15 +139,15 @@ void main() {
|
|||||||
build: () => SignUpCubit(
|
build: () => SignUpCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
authenticationCubit: authenticationCubit,
|
authenticationCubit: authenticationCubit,
|
||||||
entries: FormData.empty(),
|
entries: const FormData.empty(),
|
||||||
),
|
),
|
||||||
seed: () => SignUpState(
|
seed: () => const SignUpState(
|
||||||
email: validEmail,
|
email: validEmail,
|
||||||
data: FormData.empty(),
|
data: FormData.empty(),
|
||||||
),
|
),
|
||||||
act: (SignUpCubit cubit) => cubit.passwordChanged(validPasswordString),
|
act: (SignUpCubit cubit) => cubit.passwordChanged(validPasswordString),
|
||||||
expect: () => <SignUpState>[
|
expect: () => <SignUpState>[
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
email: validEmail,
|
email: validEmail,
|
||||||
password: validPassword,
|
password: validPassword,
|
||||||
status: FormStatus.valid,
|
status: FormStatus.valid,
|
||||||
@@ -163,7 +163,7 @@ void main() {
|
|||||||
build: () => SignUpCubit(
|
build: () => SignUpCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
authenticationCubit: authenticationCubit,
|
authenticationCubit: authenticationCubit,
|
||||||
entries: FormData.empty(),
|
entries: const FormData.empty(),
|
||||||
),
|
),
|
||||||
act: (SignUpCubit cubit) => cubit.signUpFormSubmitted(),
|
act: (SignUpCubit cubit) => cubit.signUpFormSubmitted(),
|
||||||
expect: () => const <SignUpState>[],
|
expect: () => const <SignUpState>[],
|
||||||
@@ -174,9 +174,9 @@ void main() {
|
|||||||
build: () => SignUpCubit(
|
build: () => SignUpCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
authenticationCubit: authenticationCubit,
|
authenticationCubit: authenticationCubit,
|
||||||
entries: FormData.empty(),
|
entries: const FormData.empty(),
|
||||||
),
|
),
|
||||||
seed: () => SignUpState(
|
seed: () => const SignUpState(
|
||||||
status: FormStatus.valid,
|
status: FormStatus.valid,
|
||||||
email: validEmail,
|
email: validEmail,
|
||||||
password: validPassword,
|
password: validPassword,
|
||||||
@@ -199,9 +199,9 @@ void main() {
|
|||||||
build: () => SignUpCubit(
|
build: () => SignUpCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
authenticationCubit: authenticationCubit,
|
authenticationCubit: authenticationCubit,
|
||||||
entries: FormData.empty(),
|
entries: const FormData.empty(),
|
||||||
),
|
),
|
||||||
seed: () => SignUpState(
|
seed: () => const SignUpState(
|
||||||
status: FormStatus.valid,
|
status: FormStatus.valid,
|
||||||
email: validEmail,
|
email: validEmail,
|
||||||
password: validPassword,
|
password: validPassword,
|
||||||
@@ -209,13 +209,13 @@ void main() {
|
|||||||
),
|
),
|
||||||
act: (SignUpCubit cubit) => cubit.signUpFormSubmitted(),
|
act: (SignUpCubit cubit) => cubit.signUpFormSubmitted(),
|
||||||
expect: () => <SignUpState>[
|
expect: () => <SignUpState>[
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
status: FormStatus.submissionInProgress,
|
status: FormStatus.submissionInProgress,
|
||||||
email: validEmail,
|
email: validEmail,
|
||||||
password: validPassword,
|
password: validPassword,
|
||||||
data: FormData.empty(),
|
data: FormData.empty(),
|
||||||
),
|
),
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
status: FormStatus.submissionSuccess,
|
status: FormStatus.submissionSuccess,
|
||||||
email: validEmail,
|
email: validEmail,
|
||||||
password: validPassword,
|
password: validPassword,
|
||||||
@@ -238,9 +238,9 @@ void main() {
|
|||||||
build: () => SignUpCubit(
|
build: () => SignUpCubit(
|
||||||
authenticationRepository: authenticationRepository,
|
authenticationRepository: authenticationRepository,
|
||||||
authenticationCubit: authenticationCubit,
|
authenticationCubit: authenticationCubit,
|
||||||
entries: FormData.empty(),
|
entries: const FormData.empty(),
|
||||||
),
|
),
|
||||||
seed: () => SignUpState(
|
seed: () => const SignUpState(
|
||||||
status: FormStatus.valid,
|
status: FormStatus.valid,
|
||||||
email: validEmail,
|
email: validEmail,
|
||||||
password: validPassword,
|
password: validPassword,
|
||||||
@@ -248,13 +248,13 @@ void main() {
|
|||||||
),
|
),
|
||||||
act: (SignUpCubit cubit) => cubit.signUpFormSubmitted(),
|
act: (SignUpCubit cubit) => cubit.signUpFormSubmitted(),
|
||||||
expect: () => <SignUpState>[
|
expect: () => <SignUpState>[
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
status: FormStatus.submissionInProgress,
|
status: FormStatus.submissionInProgress,
|
||||||
email: validEmail,
|
email: validEmail,
|
||||||
password: validPassword,
|
password: validPassword,
|
||||||
data: FormData.empty(),
|
data: FormData.empty(),
|
||||||
),
|
),
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
status: FormStatus.submissionFailure,
|
status: FormStatus.submissionFailure,
|
||||||
email: validEmail,
|
email: validEmail,
|
||||||
password: validPassword,
|
password: validPassword,
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ void main() {
|
|||||||
group('SignUpState', () {
|
group('SignUpState', () {
|
||||||
test('supports value comparisons', () {
|
test('supports value comparisons', () {
|
||||||
expect(
|
expect(
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
data: FormData.empty(),
|
data: FormData.empty(),
|
||||||
),
|
),
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
data: FormData.empty(),
|
data: FormData.empty(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -37,10 +37,10 @@ void main() {
|
|||||||
|
|
||||||
test('returns same object when no properties are passed', () {
|
test('returns same object when no properties are passed', () {
|
||||||
expect(
|
expect(
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
data: FormData.empty(),
|
data: FormData.empty(),
|
||||||
).copyWith(),
|
).copyWith(),
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
data: FormData.empty(),
|
data: FormData.empty(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -48,10 +48,10 @@ void main() {
|
|||||||
|
|
||||||
test('returns object with updated status when status is passed', () {
|
test('returns object with updated status when status is passed', () {
|
||||||
expect(
|
expect(
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
data: FormData.empty(),
|
data: FormData.empty(),
|
||||||
).copyWith(status: FormStatus.pure),
|
).copyWith(status: FormStatus.pure),
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
data: FormData.empty(),
|
data: FormData.empty(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -59,10 +59,10 @@ void main() {
|
|||||||
|
|
||||||
test('returns object with updated email when email is passed', () {
|
test('returns object with updated email when email is passed', () {
|
||||||
expect(
|
expect(
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
data: FormData.empty(),
|
data: FormData.empty(),
|
||||||
).copyWith(email: email),
|
).copyWith(email: email),
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
email: email,
|
email: email,
|
||||||
data: FormData.empty(),
|
data: FormData.empty(),
|
||||||
),
|
),
|
||||||
@@ -71,10 +71,10 @@ void main() {
|
|||||||
|
|
||||||
test('returns object with updated password when password is passed', () {
|
test('returns object with updated password when password is passed', () {
|
||||||
expect(
|
expect(
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
data: FormData.empty(),
|
data: FormData.empty(),
|
||||||
).copyWith(password: password),
|
).copyWith(password: password),
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
password: password,
|
password: password,
|
||||||
data: FormData.empty(),
|
data: FormData.empty(),
|
||||||
),
|
),
|
||||||
@@ -85,12 +85,12 @@ void main() {
|
|||||||
'returns object with updated data'
|
'returns object with updated data'
|
||||||
' when data is passed', () {
|
' when data is passed', () {
|
||||||
expect(
|
expect(
|
||||||
SignUpState(
|
const SignUpState(
|
||||||
data: FormData.empty(),
|
data: FormData.empty(),
|
||||||
).copyWith(
|
).copyWith(
|
||||||
data: const FormData(
|
data: const FormData(
|
||||||
[
|
[
|
||||||
FormEntry(
|
FormInput(
|
||||||
'field',
|
'field',
|
||||||
Name.pure(),
|
Name.pure(),
|
||||||
),
|
),
|
||||||
@@ -100,7 +100,7 @@ void main() {
|
|||||||
const SignUpState(
|
const SignUpState(
|
||||||
data: FormData(
|
data: FormData(
|
||||||
[
|
[
|
||||||
FormEntry(
|
FormInput(
|
||||||
'field',
|
'field',
|
||||||
Name.pure(),
|
Name.pure(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,14 +1,3 @@
|
|||||||
/*
|
|
||||||
* Author: Hugo Pointcheval
|
|
||||||
* Email: git@pcl.ovh
|
|
||||||
* -----
|
|
||||||
* File: LICENSE
|
|
||||||
* Created Date: 20/05/2022 16:47:39
|
|
||||||
* Last Modified: 20/05/2022 16:47:39
|
|
||||||
* -----
|
|
||||||
* Copyright (c) 2022
|
|
||||||
*/
|
|
||||||
|
|
||||||
GNU GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
|||||||
@@ -21,5 +21,5 @@ dev_dependencies:
|
|||||||
wyatt_analysis:
|
wyatt_analysis:
|
||||||
git:
|
git:
|
||||||
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
||||||
ref: wyatt_analysis-v2.1.0
|
ref: wyatt_analysis-v2.2.1
|
||||||
path: packages/wyatt_analysis
|
path: packages/wyatt_analysis
|
||||||
@@ -10,6 +10,6 @@ dev_dependencies:
|
|||||||
wyatt_analysis:
|
wyatt_analysis:
|
||||||
git:
|
git:
|
||||||
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
||||||
ref: wyatt_analysis-v2.1.0
|
ref: wyatt_analysis-v2.2.1
|
||||||
path: packages/wyatt_analysis
|
path: packages/wyatt_analysis
|
||||||
dependencies: {meta: ^1.8.0}
|
dependencies: {meta: ^1.8.0}
|
||||||
|
|||||||
@@ -14,6 +14,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"oauth_client": [
|
"oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "136771801992-nps7ic22shstvgdv0c2ojvbkj3dkntum.apps.googleusercontent.com",
|
||||||
|
"client_type": 1,
|
||||||
|
"android_info": {
|
||||||
|
"package_name": "com.example.authentication_bloc_example",
|
||||||
|
"certificate_hash": "5d8790309e13b68c35e5d4d8437c35c6d15e6131"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
|
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
|
||||||
"client_type": 3
|
"client_type": 3
|
||||||
@@ -77,58 +85,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_info": {
|
|
||||||
"mobilesdk_app_id": "1:136771801992:android:d20e0361057e815197203d",
|
|
||||||
"android_client_info": {
|
|
||||||
"package_name": "com.example.example"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"oauth_client": [
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-n2pq8oqutvrqj58e05hbavvc7n1jdfjb.apps.googleusercontent.com",
|
|
||||||
"client_type": 1,
|
|
||||||
"android_info": {
|
|
||||||
"package_name": "com.example.example",
|
|
||||||
"certificate_hash": "5d8790309e13b68c35e5d4d8437c35c6d15e6131"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-pj9s88ao7d99pnp4ace75nj1abtmoq7e.apps.googleusercontent.com",
|
|
||||||
"client_type": 1,
|
|
||||||
"android_info": {
|
|
||||||
"package_name": "com.example.example",
|
|
||||||
"certificate_hash": "bc8faf31c0b0085dc358b2319684e10c93f42719"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
|
|
||||||
"client_type": 3
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"api_key": [
|
|
||||||
{
|
|
||||||
"current_key": "AIzaSyAYS14uXupkS158Q5QAFP1864UrUN_yDSk"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"services": {
|
|
||||||
"appinvite_service": {
|
|
||||||
"other_platform_oauth_client": [
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-ncuib3rbu7p4ro4eo5su4vaudn2u4qrv.apps.googleusercontent.com",
|
|
||||||
"client_type": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_id": "136771801992-e585bm1n9b3lv89t4phrl9u0glsg52ua.apps.googleusercontent.com",
|
|
||||||
"client_type": 2,
|
|
||||||
"ios_info": {
|
|
||||||
"bundle_id": "com.example.example"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"configuration_version": "1"
|
"configuration_version": "1"
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ dev_dependencies:
|
|||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
# activated in the `analysis_options.yaml` file located at the root of your
|
||||||
# package. See that file for information about deactivating specific lint
|
# package. See that file for information about deactivating specific lint
|
||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^1.0.0
|
flutter_lints: ^2.0.0
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|||||||
@@ -23,5 +23,5 @@ dev_dependencies:
|
|||||||
wyatt_analysis:
|
wyatt_analysis:
|
||||||
git:
|
git:
|
||||||
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
||||||
ref: wyatt_analysis-v2.1.0
|
ref: wyatt_analysis-v2.2.1
|
||||||
path: packages/wyatt_analysis
|
path: packages/wyatt_analysis
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
## 0.0.3
|
||||||
|
|
||||||
|
- **FEAT**: add helpers.
|
||||||
|
- **FEAT**: add metadata on inputs.
|
||||||
|
- **FEAT**: update to latest dart version.
|
||||||
|
|
||||||
## 0.0.2
|
## 0.0.2
|
||||||
|
|
||||||
- **FEAT**: add list option validator.
|
- **FEAT**: add list option validator.
|
||||||
|
|||||||
@@ -30,21 +30,22 @@ Form Bloc for Dart & Flutter.
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Form
|
- Form
|
||||||
* FormInput: *atom of a form*
|
* FormInputValidator:
|
||||||
- Store data
|
- Store data
|
||||||
- Validate this data
|
- Validate this data
|
||||||
* FormEntry: *shell of this atom*
|
* FormInputMetadata:
|
||||||
- Associate a key to an input
|
- Store infos and options about an input.
|
||||||
- Configure form attribute (exportation, name...)
|
* FormInput:
|
||||||
* FormData: *collection of entries*
|
- Associaite a key, to a validator and metadata.
|
||||||
- Contain all entries
|
* FormData: *collection of inputs*
|
||||||
|
- Contain all inputs
|
||||||
- Basic set operation
|
- Basic set operation
|
||||||
|
|
||||||
- FormDataCubit
|
- FormDataCubit
|
||||||
* Data management behind a form.
|
* Data management behind a form.
|
||||||
- Use entries to pass a FormData object
|
- Use entries to pass a FormData object
|
||||||
- You can use several pre configured FormInput for validation
|
- You can use several pre configured `FormInputValidator` for validation
|
||||||
- You can use updateFormData() to change FormData and validators during runtime (intersection, union, difference or replace)
|
- You can use updateFormData() to change `FormData` during runtime (intersection, union, difference or replace)
|
||||||
|
|
||||||
- Consistent
|
- Consistent
|
||||||
* Every class have same naming convention
|
* Every class have same naming convention
|
||||||
@@ -59,4 +60,120 @@ import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
todo
|
Firstly, you have to create your form inputs.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
static List<FormInput> getNormalEntries() {
|
||||||
|
return const [
|
||||||
|
FormInput(formFieldName, Name.pure()),
|
||||||
|
FormInput(formFieldEmail, Email.pure()),
|
||||||
|
FormInput(formFieldPhone, Phone.pure()),
|
||||||
|
FormInput(formFieldList, ListOption<String>.pure(defaultValue: ['checkbox3'])),
|
||||||
|
FormInput(formFieldRadio, TextString.pure()),
|
||||||
|
FormInput(formFieldPro, Boolean.pure(), metadata: FormInputMetadata<void>(name: 'business')),
|
||||||
|
FormInput(formFieldHidden, Boolean.pure(), metadata: FormInputMetadata<void>(export: false)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<FormInput> getBusinessEntries() {
|
||||||
|
const entries = [
|
||||||
|
FormInput(formFieldSiren, Siren.pure()),
|
||||||
|
FormInput(formFieldIban, Iban.pure()),
|
||||||
|
];
|
||||||
|
return getNormalEntries() + entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FormData getNormalFormData() {
|
||||||
|
return FormData(getNormalEntries());
|
||||||
|
}
|
||||||
|
|
||||||
|
static FormData getProFormData() {
|
||||||
|
return FormData(getBusinessEntries());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Let's create functions to enable dynamic changes in runtime.
|
||||||
|
|
||||||
|
> When creating metadata with `FormInputMetadata<T>`, `T` is the type of `extra` object.
|
||||||
|
|
||||||
|
Then a `FormData` is a collection of inputs. You can `validate`, `contains`, `intersection`, `difference`, `union`, or `clone` this data.
|
||||||
|
|
||||||
|
After that, you have to extends `FormDataCubit`.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class CustomFormCubit extends FormDataCubit {
|
||||||
|
CustomFormCubit({required FormData inputs}) : super(inputs: inputs);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> submitForm() {
|
||||||
|
log(state.data.toMap().toString());
|
||||||
|
// Handle all your logic here.
|
||||||
|
emit(...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> The submited form is in `state.data` !
|
||||||
|
|
||||||
|
Don't forget to create and provide it !
|
||||||
|
|
||||||
|
```dart
|
||||||
|
FormDataCubit _formCubit = CustomFormCubit(inputs: getNormalFormData());
|
||||||
|
|
||||||
|
return BlocProvider(
|
||||||
|
create: (context) => _formCubit,
|
||||||
|
child: const WidgetTree(),
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
You can now create the first form input !
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class _EmailInput extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<FormDataCubit, FormDataState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return TextField(
|
||||||
|
onChanged: (email) => context
|
||||||
|
.read<FormDataCubit>()
|
||||||
|
.dataChanged(formFieldEmail, Email.dirty(email)),
|
||||||
|
keyboardType: TextInputType.emailAddress,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'email',
|
||||||
|
helperText: '',
|
||||||
|
errorText: state.data.isNotValid(formFieldEmail)
|
||||||
|
? 'invalid email'
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, you can create the trigger
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class _SignUpButton extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<FormDataCubit, FormDataState>(
|
||||||
|
buildWhen: (previous, current) => previous.status != current.status,
|
||||||
|
builder: (context, state) {
|
||||||
|
return state.status.isSubmissionInProgress
|
||||||
|
? const CircularProgressIndicator()
|
||||||
|
: ElevatedButton(
|
||||||
|
onPressed: state.status.isValidated
|
||||||
|
? () => context.read<FormDataCubit>().submitForm()
|
||||||
|
: null,
|
||||||
|
child: const Text('SIGN UP'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> With `state.status` you can access the status of the form to check if there is an error or if it's in submission.
|
||||||
@@ -1,10 +1,30 @@
|
|||||||
# This file tracks properties of this Flutter project.
|
# This file tracks properties of this Flutter project.
|
||||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
#
|
#
|
||||||
# This file should be version controlled and should not be manually edited.
|
# This file should be version controlled.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: 5464c5bac742001448fe4fc0597be939379f88ea
|
revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
channel: stable
|
channel: stable
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
- platform: web
|
||||||
|
create_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
base_revision: 85684f9300908116a78138ea4c6036c35c9a1236
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
## 1.1.0
|
||||||
|
|
||||||
|
- **FEAT**: add list option validator.
|
||||||
|
- **DOCS**: add example app.
|
||||||
|
|
||||||
@@ -16,31 +16,37 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:form_bloc_example/app/metadata.dart';
|
||||||
import 'package:form_bloc_example/constants.dart';
|
import 'package:form_bloc_example/constants.dart';
|
||||||
import 'package:form_bloc_example/cubit/custom_form_cubit.dart';
|
import 'package:form_bloc_example/cubit/custom_form_cubit.dart';
|
||||||
import 'package:form_bloc_example/sign_up/sign_up_page.dart';
|
import 'package:form_bloc_example/sign_up/sign_up_page.dart';
|
||||||
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
|
||||||
|
const blue = Metadata(category: Category.perso);
|
||||||
|
const red = Metadata(category: Category.business);
|
||||||
|
|
||||||
class App extends StatelessWidget {
|
class App extends StatelessWidget {
|
||||||
const App({Key? key}) : super(key: key);
|
const App({Key? key}) : super(key: key);
|
||||||
|
|
||||||
static List<FormEntry> getNormalEntries() {
|
static List<FormInput> getNormalEntries() {
|
||||||
return const [
|
return const [
|
||||||
FormEntry(formFieldName, Name.pure()),
|
FormInput(formFieldName, Name.pure(), metadata: FormInputMetadata<Metadata>(extra: blue)),
|
||||||
FormEntry(formFieldEmail, Email.pure()),
|
FormInput(formFieldEmail, Email.pure(), metadata: FormInputMetadata<Metadata>(extra: blue)),
|
||||||
FormEntry(formFieldPhone, Phone.pure()),
|
FormInput(formFieldPhone, Phone.pure(), metadata: FormInputMetadata<Metadata>(extra: red)),
|
||||||
FormEntry(
|
FormInput(
|
||||||
formFieldList, ListOption<String>.pure(defaultValue: ['checkbox3'])),
|
formFieldList, ListOption<String>.pure(defaultValue: ['checkbox3'])),
|
||||||
FormEntry(formFieldRadio, TextString.pure()),
|
FormInput(formFieldRadio, TextString.pure()),
|
||||||
FormEntry(formFieldPro, Boolean.pure(), name: 'business'),
|
FormInput(formFieldPro, Boolean.pure(),
|
||||||
FormEntry(formFieldHidden, Boolean.pure(), export: false),
|
metadata: FormInputMetadata<Metadata>(name: 'business')),
|
||||||
|
FormInput(formFieldHidden, Boolean.pure(),
|
||||||
|
metadata: FormInputMetadata<Metadata>(export: false, extra: blue)),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<FormEntry> getBusinessEntries() {
|
static List<FormInput> getBusinessEntries() {
|
||||||
const entries = [
|
const entries = [
|
||||||
FormEntry(formFieldSiren, Siren.pure()),
|
FormInput(formFieldSiren, Siren.pure()),
|
||||||
FormEntry(formFieldIban, Iban.pure()),
|
FormInput(formFieldIban, Iban.pure()),
|
||||||
];
|
];
|
||||||
return getNormalEntries() + entries;
|
return getNormalEntries() + entries;
|
||||||
}
|
}
|
||||||
@@ -55,7 +61,7 @@ class App extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
FormDataCubit _formCubit = CustomFormCubit(entries: getNormalFormData());
|
FormDataCubit _formCubit = CustomFormCubit(inputs: getNormalFormData());
|
||||||
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => _formCubit,
|
create: (context) => _formCubit,
|
||||||
|
|||||||
+16
-7
@@ -1,3 +1,4 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
@@ -14,12 +15,20 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
bool listEquals<T>(List<T>? a, List<T>? b) {
|
enum Category {
|
||||||
if (a == null) return b == null;
|
perso,
|
||||||
if (b == null || a.length != b.length) return false;
|
business
|
||||||
if (identical(a, b)) return true;
|
}
|
||||||
for (int index = 0; index < a.length; index += 1) {
|
|
||||||
if (a[index] != b[index]) return false;
|
class Metadata {
|
||||||
|
final Category category;
|
||||||
|
|
||||||
|
const Metadata({
|
||||||
|
required this.category,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return category.toString();
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
@@ -16,18 +16,15 @@
|
|||||||
|
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
|
||||||
part 'custom_form_state.dart';
|
|
||||||
|
|
||||||
class CustomFormCubit extends FormDataCubit {
|
class CustomFormCubit extends FormDataCubit {
|
||||||
CustomFormCubit({required FormData entries}) : super(entries: entries);
|
CustomFormCubit({required FormData inputs}) : super(inputs: inputs);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> submitForm() {
|
Future<void> submitForm() {
|
||||||
log(state.data.toMap().toString());
|
log(state.data.toMap().toString());
|
||||||
|
|
||||||
return Future.value();
|
return Future.value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,35 +19,83 @@ import 'dart:developer';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:form_bloc_example/app/app.dart';
|
import 'package:form_bloc_example/app/app.dart';
|
||||||
|
import 'package:form_bloc_example/app/metadata.dart';
|
||||||
import 'package:form_bloc_example/constants.dart';
|
import 'package:form_bloc_example/constants.dart';
|
||||||
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
|
||||||
|
|
||||||
|
class CategoryIndicator extends StatelessWidget {
|
||||||
|
const CategoryIndicator(
|
||||||
|
{Key? key, required this.field, required this.builder})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
final String field;
|
||||||
|
final Function(BuildContext context, FormDataState state) builder;
|
||||||
|
|
||||||
|
Color computeColor(Metadata meta) {
|
||||||
|
switch (meta.category) {
|
||||||
|
case Category.perso:
|
||||||
|
return Colors.blue;
|
||||||
|
case Category.business:
|
||||||
|
return Colors.red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<FormDataCubit, FormDataState>(
|
||||||
|
builder: ((context, state) {
|
||||||
|
final meta = state.data.metadataOf(field).extra as Metadata;
|
||||||
|
final color = computeColor(meta);
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 8,
|
||||||
|
width: 8,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20,),
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width - 45,
|
||||||
|
child: builder(context, state),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _NameInput extends StatelessWidget {
|
class _NameInput extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<FormDataCubit, FormDataState>(
|
return CategoryIndicator(
|
||||||
builder: (context, state) {
|
field: formFieldName,
|
||||||
return TextField(
|
builder: (context, state) {
|
||||||
onChanged: (name) => context
|
return TextField(
|
||||||
.read<FormDataCubit>()
|
onChanged: (name) => context
|
||||||
.dataChanged(formFieldName, Name.dirty(name)),
|
.read<FormDataCubit>()
|
||||||
keyboardType: TextInputType.name,
|
.dataChanged(formFieldName, Name.dirty(name)),
|
||||||
decoration: InputDecoration(
|
keyboardType: TextInputType.name,
|
||||||
labelText: 'name',
|
decoration: InputDecoration(
|
||||||
helperText: '',
|
labelText: 'name',
|
||||||
errorText:
|
helperText: '',
|
||||||
state.data.input(formFieldName).invalid ? 'invalid name' : null,
|
errorText:
|
||||||
),
|
state.data.isNotValid(formFieldName) ? 'invalid name' : null,
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EmailInput extends StatelessWidget {
|
class _EmailInput extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<FormDataCubit, FormDataState>(
|
return CategoryIndicator(
|
||||||
|
field: formFieldEmail,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (email) => context
|
onChanged: (email) => context
|
||||||
@@ -57,9 +105,8 @@ class _EmailInput extends StatelessWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'email',
|
labelText: 'email',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: state.data.input(formFieldEmail).invalid
|
errorText:
|
||||||
? 'invalid email'
|
state.data.isNotValid(formFieldEmail) ? 'invalid email' : null,
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -70,7 +117,8 @@ class _EmailInput extends StatelessWidget {
|
|||||||
class _PhoneInput extends StatelessWidget {
|
class _PhoneInput extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<FormDataCubit, FormDataState>(
|
return CategoryIndicator(
|
||||||
|
field: formFieldPhone,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (phone) => context
|
onChanged: (phone) => context
|
||||||
@@ -80,9 +128,8 @@ class _PhoneInput extends StatelessWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'phone',
|
labelText: 'phone',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: state.data.input(formFieldPhone).invalid
|
errorText:
|
||||||
? 'invalid phone'
|
state.data.isNotValid(formFieldPhone) ? 'invalid phone' : null,
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -93,7 +140,8 @@ class _PhoneInput extends StatelessWidget {
|
|||||||
class _SirenInput extends StatelessWidget {
|
class _SirenInput extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<FormDataCubit, FormDataState>(
|
return CategoryIndicator(
|
||||||
|
field: formFieldName,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return TextField(
|
return TextField(
|
||||||
onChanged: (siren) => context
|
onChanged: (siren) => context
|
||||||
@@ -103,9 +151,8 @@ class _SirenInput extends StatelessWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'siren',
|
labelText: 'siren',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText: state.data.input(formFieldSiren).invalid
|
errorText:
|
||||||
? 'invalid SIREN'
|
state.data.isNotValid(formFieldSiren) ? 'invalid SIREN' : null,
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -127,7 +174,7 @@ class _IbanInput extends StatelessWidget {
|
|||||||
labelText: 'iban',
|
labelText: 'iban',
|
||||||
helperText: '',
|
helperText: '',
|
||||||
errorText:
|
errorText:
|
||||||
state.data.input(formFieldIban).invalid ? 'invalid IBAN' : null,
|
state.data.isNotValid(formFieldIban) ? 'invalid IBAN' : null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -141,7 +188,7 @@ class _CheckListInput extends StatelessWidget {
|
|||||||
return BlocBuilder<FormDataCubit, FormDataState>(
|
return BlocBuilder<FormDataCubit, FormDataState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final _input =
|
final _input =
|
||||||
state.data.input<List<String>>(formFieldList) as ListOption<String>;
|
state.data.validatorOf<ListOption<String>>(formFieldList);
|
||||||
final _options = _input.value;
|
final _options = _input.value;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
@@ -192,8 +239,7 @@ class _RadioListInput extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<FormDataCubit, FormDataState>(
|
return BlocBuilder<FormDataCubit, FormDataState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final _input =
|
final _input = state.data.validatorOf<TextString>(formFieldRadio);
|
||||||
state.data.input<String>(formFieldRadio) as TextString;
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@@ -249,7 +295,7 @@ class _CheckHiddenInput extends StatelessWidget {
|
|||||||
trailing: BlocBuilder<FormDataCubit, FormDataState>(
|
trailing: BlocBuilder<FormDataCubit, FormDataState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Checkbox(
|
return Checkbox(
|
||||||
value: state.data.input<bool>(formFieldHidden).value,
|
value: state.data.validatorOf<Boolean>(formFieldHidden).value,
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
final value = v!; // state is false, so value can't be null
|
final value = v!; // state is false, so value can't be null
|
||||||
|
|
||||||
@@ -272,7 +318,7 @@ class _CheckIsProInput extends StatelessWidget {
|
|||||||
trailing: BlocBuilder<FormDataCubit, FormDataState>(
|
trailing: BlocBuilder<FormDataCubit, FormDataState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Checkbox(
|
return Checkbox(
|
||||||
value: state.data.input<bool>(formFieldPro).value,
|
value: state.data.validatorOf<Boolean>(formFieldPro).value,
|
||||||
onChanged: (isPro) {
|
onChanged: (isPro) {
|
||||||
final value = isPro!; // state is false, so value can't be null
|
final value = isPro!; // state is false, so value can't be null
|
||||||
|
|
||||||
@@ -370,7 +416,7 @@ class SignUpForm extends StatelessWidget {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
BlocBuilder<FormDataCubit, FormDataState>(
|
BlocBuilder<FormDataCubit, FormDataState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.data.input<bool>(formFieldPro).value) {
|
if (state.data.validatorOf<Boolean>(formFieldPro).value) {
|
||||||
return Column(children: [
|
return Column(children: [
|
||||||
_SirenInput(),
|
_SirenInput(),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.0.0+1
|
version: 1.1.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.16.2 <3.0.0"
|
sdk: ">=2.17.2 <3.0.0"
|
||||||
|
|
||||||
# Dependencies specify other packages that your package needs in order to work.
|
# Dependencies specify other packages that your package needs in order to work.
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
# To automatically upgrade your package dependencies to the latest versions
|
||||||
@@ -47,7 +47,7 @@ dev_dependencies:
|
|||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
# activated in the `analysis_options.yaml` file located at the root of your
|
||||||
# package. See that file for information about deactivating specific lint
|
# package. See that file for information about deactivating specific lint
|
||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^1.0.0
|
flutter_lints: ^2.0.0
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 917 B |
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,58 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!--
|
||||||
|
If you are serving your web app in a path other than the root, change the
|
||||||
|
href value below to reflect the base path you are serving from.
|
||||||
|
|
||||||
|
The path provided below has to start and end with a slash "/" in order for
|
||||||
|
it to work correctly.
|
||||||
|
|
||||||
|
For more details:
|
||||||
|
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||||
|
|
||||||
|
This is a placeholder for base href that will be replaced by the value of
|
||||||
|
the `--base-href` argument provided to `flutter build`.
|
||||||
|
-->
|
||||||
|
<base href="$FLUTTER_BASE_HREF">
|
||||||
|
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||||
|
<meta name="description" content="A new Flutter project.">
|
||||||
|
|
||||||
|
<!-- iOS meta tags & icons -->
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="example">
|
||||||
|
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||||
|
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||||
|
|
||||||
|
<title>example</title>
|
||||||
|
<link rel="manifest" href="manifest.json">
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// The value below is injected by flutter build, do not touch.
|
||||||
|
var serviceWorkerVersion = null;
|
||||||
|
</script>
|
||||||
|
<!-- This script adds the flutter initialization JS code -->
|
||||||
|
<script src="flutter.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
window.addEventListener('load', function(ev) {
|
||||||
|
// Download main.dart.js
|
||||||
|
_flutter.loader.loadEntrypoint({
|
||||||
|
serviceWorker: {
|
||||||
|
serviceWorkerVersion: serviceWorkerVersion,
|
||||||
|
}
|
||||||
|
}).then(function(engineInitializer) {
|
||||||
|
return engineInitializer.initializeEngine();
|
||||||
|
}).then(function(appRunner) {
|
||||||
|
return appRunner.runApp();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "example",
|
||||||
|
"short_name": "example",
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0175C2",
|
||||||
|
"theme_color": "#0175C2",
|
||||||
|
"description": "A new Flutter project.",
|
||||||
|
"orientation": "portrait-primary",
|
||||||
|
"prefer_related_applications": false,
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-maskable-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-maskable-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:wyatt_form_bloc/src/enums/enums.dart';
|
import 'package:wyatt_form_bloc/src/enums/enums.dart';
|
||||||
import 'package:wyatt_form_bloc/src/form/form.dart';
|
import 'package:wyatt_form_bloc/src/form/form.dart';
|
||||||
@@ -24,19 +25,19 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
part 'form_data_state.dart';
|
part 'form_data_state.dart';
|
||||||
|
|
||||||
abstract class FormDataCubit extends Cubit<FormDataState> {
|
abstract class FormDataCubit extends Cubit<FormDataState> {
|
||||||
FormDataCubit({required FormData entries})
|
FormDataCubit({required FormData inputs})
|
||||||
: super(FormDataState(data: entries));
|
: super(FormDataState(data: inputs));
|
||||||
|
|
||||||
/// Change value of a field.
|
/// Change value of a field.
|
||||||
///
|
///
|
||||||
/// Inputs:
|
/// Inputs:
|
||||||
/// - `field`: The key of the field to change.
|
/// - `field`: The key of the field to change.
|
||||||
/// - `dirtyValue`: The new value of the field.
|
/// - `dirtyValue`: The new value of the field. (Wrapped in a dirty validator)
|
||||||
void dataChanged(String field, FormInput dirtyValue) {
|
void dataChanged(String field, FormInputValidator dirtyValue) {
|
||||||
final _form = state.data.clone();
|
final _form = state.data.clone();
|
||||||
|
|
||||||
if (_form.contains(field)) {
|
if (_form.contains(field)) {
|
||||||
_form.update(field, dirtyValue);
|
_form.updateValidator(field, dirtyValue);
|
||||||
} else {
|
} else {
|
||||||
throw Exception('Form field $field not found');
|
throw Exception('Form field $field not found');
|
||||||
}
|
}
|
||||||
@@ -44,7 +45,7 @@ abstract class FormDataCubit extends Cubit<FormDataState> {
|
|||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
data: _form,
|
data: _form,
|
||||||
status: _form.selfValidate(),
|
status: _form.validate(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -78,7 +79,7 @@ abstract class FormDataCubit extends Cubit<FormDataState> {
|
|||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
data: _form,
|
data: _form,
|
||||||
status: _form.selfValidate(),
|
status: _form.validate(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,14 @@
|
|||||||
part of 'form_data_cubit.dart';
|
part of 'form_data_cubit.dart';
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class FormDataState {
|
class FormDataState extends Equatable {
|
||||||
|
/// Global status of a form.
|
||||||
final FormStatus status;
|
final FormStatus status;
|
||||||
|
|
||||||
|
/// FormData with all inputs, and associated metadata.
|
||||||
final FormData data;
|
final FormData data;
|
||||||
|
|
||||||
|
/// Optional error message.
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
|
|
||||||
const FormDataState({
|
const FormDataState({
|
||||||
@@ -41,23 +46,8 @@ class FormDataState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool? get stringify => true;
|
||||||
if (identical(this, other)) return true;
|
|
||||||
|
|
||||||
return other is FormDataState &&
|
|
||||||
other.status == status &&
|
|
||||||
other.data == data &&
|
|
||||||
other.errorMessage == errorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
List<Object?> get props => [status, data, errorMessage];
|
||||||
return status.hashCode ^ data.hashCode ^ errorMessage.hashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'FormDataState(status: $status, data: $data, '
|
|
||||||
'errorMessage: $errorMessage)';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,16 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'package:wyatt_form_bloc/src/form/form.dart';
|
||||||
|
|
||||||
|
const Set<FormStatus> _validatedFormStatuses = <FormStatus>{
|
||||||
|
FormStatus.valid,
|
||||||
|
FormStatus.submissionInProgress,
|
||||||
|
FormStatus.submissionSuccess,
|
||||||
|
FormStatus.submissionFailure,
|
||||||
|
FormStatus.submissionCanceled,
|
||||||
|
};
|
||||||
|
|
||||||
/// Enum representing the status of a form at any given point in time.
|
/// Enum representing the status of a form at any given point in time.
|
||||||
enum FormStatus {
|
enum FormStatus {
|
||||||
/// The form has not been touched.
|
/// The form has not been touched.
|
||||||
@@ -35,19 +45,8 @@ enum FormStatus {
|
|||||||
submissionFailure,
|
submissionFailure,
|
||||||
|
|
||||||
/// The form submission has been canceled.
|
/// The form submission has been canceled.
|
||||||
submissionCanceled
|
submissionCanceled;
|
||||||
}
|
|
||||||
|
|
||||||
const Set<FormStatus> _validatedFormStatuses = <FormStatus>{
|
|
||||||
FormStatus.valid,
|
|
||||||
FormStatus.submissionInProgress,
|
|
||||||
FormStatus.submissionSuccess,
|
|
||||||
FormStatus.submissionFailure,
|
|
||||||
FormStatus.submissionCanceled,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Useful extensions on [FormStatus]
|
|
||||||
extension FormStatusX on FormStatus {
|
|
||||||
/// Indicates whether the form is untouched.
|
/// Indicates whether the form is untouched.
|
||||||
bool get isPure => this == FormStatus.pure;
|
bool get isPure => this == FormStatus.pure;
|
||||||
|
|
||||||
@@ -76,4 +75,23 @@ extension FormStatusX on FormStatus {
|
|||||||
|
|
||||||
/// Indicates whether the form submission has been canceled.
|
/// Indicates whether the form submission has been canceled.
|
||||||
bool get isSubmissionCanceled => this == FormStatus.submissionCanceled;
|
bool get isSubmissionCanceled => this == FormStatus.submissionCanceled;
|
||||||
|
|
||||||
|
/// Validate a list of inputs by processing them in `validate` as validators.
|
||||||
|
static FormStatus validateInputs(List<FormInput> inputs) {
|
||||||
|
return validate(
|
||||||
|
inputs
|
||||||
|
.map<FormInputValidator>((FormInput input) => input.validator)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate a list of validators.
|
||||||
|
static FormStatus validate(List<FormInputValidator> validators) {
|
||||||
|
return validators.every((FormInputValidator validator) => validator.pure)
|
||||||
|
? FormStatus.pure
|
||||||
|
: validators
|
||||||
|
.any((FormInputValidator validator) => validator.valid == false)
|
||||||
|
? FormStatus.invalid
|
||||||
|
: FormStatus.valid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,25 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License as published by
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// any later version.
|
// any later version.
|
||||||
//
|
//
|
||||||
// This program is distributed in the hope that it will be useful,
|
// This program is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
export 'form_data.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
export 'form_entry.dart';
|
import 'package:meta/meta.dart';
|
||||||
export 'form_input.dart';
|
import 'package:wyatt_form_bloc/src/enums/form_input_status.dart';
|
||||||
|
import 'package:wyatt_form_bloc/src/enums/form_status.dart';
|
||||||
|
|
||||||
|
part 'form_input.dart';
|
||||||
|
part 'form_input_metadata.dart';
|
||||||
|
part 'form_input_validator.dart';
|
||||||
|
part 'form_data.dart';
|
||||||
|
|||||||
@@ -14,133 +14,185 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'package:meta/meta.dart';
|
part of 'form.dart';
|
||||||
import 'package:wyatt_form_bloc/src/enums/enums.dart';
|
|
||||||
import 'package:wyatt_form_bloc/src/form/form.dart';
|
|
||||||
import 'package:wyatt_form_bloc/src/utils/list_equals.dart';
|
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
class FormData {
|
class FormData extends Equatable {
|
||||||
const FormData(this._entries);
|
final List<FormInput> _inputs;
|
||||||
|
|
||||||
FormData.empty() : this(<FormEntry>[]);
|
const FormData(this._inputs);
|
||||||
|
const FormData.empty() : this(const <FormInput>[]);
|
||||||
|
|
||||||
final List<FormEntry> _entries;
|
/// Returns all inputs as a list
|
||||||
|
List<FormInput> inputs() => _inputs;
|
||||||
|
|
||||||
List<FormEntry> get entries => _entries;
|
/// Returns the input for the associated key
|
||||||
|
FormInput inputOf(String key) {
|
||||||
List<FormInput<T, ValidationError>> inputs<T>() {
|
|
||||||
return _entries
|
|
||||||
.map((FormEntry entry) => entry.input as FormInput<T, ValidationError>)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
static FormStatus validate(List<FormInput> inputs) {
|
|
||||||
return inputs.every((FormInput element) => element.pure)
|
|
||||||
? FormStatus.pure
|
|
||||||
: inputs.any((FormInput input) => input.valid == false)
|
|
||||||
? FormStatus.invalid
|
|
||||||
: FormStatus.valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
FormStatus selfValidate() {
|
|
||||||
return validate(inputs<dynamic>());
|
|
||||||
}
|
|
||||||
|
|
||||||
FormInput<T, ValidationError> input<T>(String key) {
|
|
||||||
if (contains(key)) {
|
if (contains(key)) {
|
||||||
return _entries.firstWhere((FormEntry entry) => entry.key == key).input
|
return _inputs.firstWhere((FormInput input) => input.key == key);
|
||||||
as FormInput<T, ValidationError>;
|
|
||||||
} else {
|
} else {
|
||||||
throw Exception('FormInput with key `$key` does not exist in form');
|
throw Exception('FormInput with key `$key` does not exist in form');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool contains(String key) {
|
/// Updates a input (perform a replace at index).
|
||||||
return _entries.any((FormEntry entry) => entry.key == key);
|
void updateInput(String key, FormInput input) {
|
||||||
}
|
|
||||||
|
|
||||||
FormData intersection(FormData other) {
|
|
||||||
final List<FormEntry> entries = <FormEntry>[];
|
|
||||||
|
|
||||||
for (final FormEntry entry in _entries) {
|
|
||||||
if (other.contains(entry.key)) {
|
|
||||||
entries.add(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return FormData(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
FormData difference(FormData other) {
|
|
||||||
final List<FormEntry> entries = <FormEntry>[];
|
|
||||||
|
|
||||||
for (final FormEntry otherEntry in other._entries) {
|
|
||||||
if (!contains(otherEntry.key)) {
|
|
||||||
entries.add(otherEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final FormEntry entry in _entries) {
|
|
||||||
if (!other.contains(entry.key)) {
|
|
||||||
entries.add(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return FormData(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
FormData union(FormData other) {
|
|
||||||
final List<FormEntry> entries = <FormEntry>[];
|
|
||||||
|
|
||||||
for (final FormEntry entry in _entries) {
|
|
||||||
entries.add(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final FormEntry otherEntry in other._entries) {
|
|
||||||
if (!contains(otherEntry.key)) {
|
|
||||||
entries.add(otherEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return FormData(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
void update(String key, FormInput input) {
|
|
||||||
if (contains(key)) {
|
if (contains(key)) {
|
||||||
final index = _entries.indexOf(
|
final index = _inputs.indexOf(
|
||||||
_entries.firstWhere((FormEntry entry) => entry.key == key),
|
inputOf(key),
|
||||||
);
|
);
|
||||||
_entries[index] = _entries[index].copyWith(input: input);
|
_inputs[index] = input;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns all associated validators as a list
|
||||||
|
List<FormInputValidator<V, E>> validators<V, E>() {
|
||||||
|
return _inputs
|
||||||
|
.map<FormInputValidator<V, E>>(
|
||||||
|
(FormInput input) => input.validator as FormInputValidator<V, E>,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [FormInputValidator] represents the value of a single form input field.
|
||||||
|
/// It contains information about the [FormInputStatus], value, as well
|
||||||
|
/// as validation status.
|
||||||
|
T validatorOf<T>(String key) {
|
||||||
|
return inputOf(key).validator as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates validator of a given input. (perform copyWith)
|
||||||
|
void updateValidator(String key, FormInputValidator dirtyValue) {
|
||||||
|
if (contains(key)) {
|
||||||
|
final index = _inputs.indexOf(
|
||||||
|
inputOf(key),
|
||||||
|
);
|
||||||
|
_inputs[index] = _inputs[index].copyWith(validator: dirtyValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a validation error if the [FormInputValidator] is invalid.
|
||||||
|
/// Returns null if the [FormInputValidator] is valid.
|
||||||
|
E? errorOf<V, E>(String key) {
|
||||||
|
return (inputOf(key).validator as FormInputValidator<V, E>).error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The value of the associated [FormInputValidator]. For example,
|
||||||
|
/// if you have a FormInputValidator for FirstName, the value could be 'Joe'.
|
||||||
|
V valueOf<V, E>(String key) {
|
||||||
|
return (inputOf(key).validator as FormInputValidator<V, E>).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the [FormInputValidator] is not valid.
|
||||||
|
/// Same as `E? errorOf<V, E>(String key) != null`
|
||||||
|
bool isNotValid(String key) {
|
||||||
|
return !inputOf(key).validator.valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns all associated metadata as a list
|
||||||
|
List<FormInputMetadata<M>> metadata<M>() {
|
||||||
|
return _inputs
|
||||||
|
.map<FormInputMetadata<M>>(
|
||||||
|
(FormInput input) => input.metadata as FormInputMetadata<M>,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the metadata associated. With `M` the type of extra data.
|
||||||
|
FormInputMetadata<M> metadataOf<M>(String key) {
|
||||||
|
return inputOf(key).metadata as FormInputMetadata<M>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates metadata of a given input. (perform copyWith)
|
||||||
|
void updateMetadata(String key, FormInputMetadata meta) {
|
||||||
|
if (contains(key)) {
|
||||||
|
final index = _inputs.indexOf(
|
||||||
|
inputOf(key),
|
||||||
|
);
|
||||||
|
_inputs[index] = _inputs[index].copyWith(metadata: meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate self inputs.
|
||||||
|
FormStatus validate() {
|
||||||
|
return FormStatus.validateInputs(_inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if this contains an input with the given key.
|
||||||
|
bool contains(String key) {
|
||||||
|
return _inputs.any((FormInput input) => input.key == key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes an intersection set operation and returns newly created [FormData]
|
||||||
|
FormData intersection(FormData other) {
|
||||||
|
final List<FormInput> inputs = <FormInput>[];
|
||||||
|
|
||||||
|
for (final FormInput i in _inputs) {
|
||||||
|
if (other.contains(i.key)) {
|
||||||
|
inputs.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FormData(inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes a difference set operation and returns newly created [FormData]
|
||||||
|
FormData difference(FormData other) {
|
||||||
|
final List<FormInput> inputs = <FormInput>[];
|
||||||
|
|
||||||
|
for (final FormInput i in other._inputs) {
|
||||||
|
if (!contains(i.key)) {
|
||||||
|
inputs.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final FormInput i in _inputs) {
|
||||||
|
if (!other.contains(i.key)) {
|
||||||
|
inputs.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FormData(inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes an union set operation and returns newly created [FormData]
|
||||||
|
FormData union(FormData other) {
|
||||||
|
final List<FormInput> inputs = <FormInput>[];
|
||||||
|
|
||||||
|
for (final FormInput i in _inputs) {
|
||||||
|
inputs.add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final FormInput i in other._inputs) {
|
||||||
|
if (!contains(i.key)) {
|
||||||
|
inputs.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FormData(inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deeply copy this.
|
||||||
FormData clone() {
|
FormData clone() {
|
||||||
return FormData(
|
return FormData(
|
||||||
_entries.map((FormEntry entry) => entry.clone()).toList(),
|
_inputs.map((FormInput input) => input.clone()).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Export this to [Map] format.
|
||||||
Map<String, dynamic> toMap() {
|
Map<String, dynamic> toMap() {
|
||||||
final map = <String, dynamic>{};
|
final map = <String, dynamic>{};
|
||||||
for (final entry in _entries) {
|
for (final input in _inputs) {
|
||||||
if (entry.export) {
|
if (input.metadata.export) {
|
||||||
map[entry.name] = entry.input.value;
|
map[input.name] = input.validator.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool? get stringify => true;
|
||||||
if (identical(this, other)) return true;
|
|
||||||
|
|
||||||
return other is FormData && listEquals(other._entries, _entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => _entries.hashCode;
|
List<Object?> get props => _inputs;
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'FormData(entries: $_entries)';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
|
||||||
// Please see the AUTHORS file for details.
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:wyatt_form_bloc/src/form/form.dart';
|
|
||||||
|
|
||||||
@immutable
|
|
||||||
class FormEntry {
|
|
||||||
const FormEntry(this.key, this.input, {this.export = true, String? name})
|
|
||||||
: _field = name ?? key;
|
|
||||||
|
|
||||||
final String key;
|
|
||||||
final FormInput input;
|
|
||||||
final bool export;
|
|
||||||
final String _field;
|
|
||||||
|
|
||||||
String get name => _field;
|
|
||||||
|
|
||||||
FormEntry copyWith({
|
|
||||||
String? key,
|
|
||||||
FormInput? input,
|
|
||||||
bool? export,
|
|
||||||
String? name,
|
|
||||||
}) {
|
|
||||||
return FormEntry(
|
|
||||||
key ?? this.key,
|
|
||||||
input ?? this.input,
|
|
||||||
export: export ?? this.export,
|
|
||||||
name: name,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
FormEntry clone() {
|
|
||||||
return FormEntry(
|
|
||||||
key,
|
|
||||||
input,
|
|
||||||
export: export,
|
|
||||||
name: name,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
if (identical(this, other)) return true;
|
|
||||||
|
|
||||||
return other is FormEntry &&
|
|
||||||
other.key == key &&
|
|
||||||
other.input == input &&
|
|
||||||
other.export == export &&
|
|
||||||
other.name == name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode {
|
|
||||||
return key.hashCode ^
|
|
||||||
input.hashCode ^
|
|
||||||
export.hashCode ^
|
|
||||||
name.hashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'FormEntry(key: $key, input: $input, '
|
|
||||||
'export: $export, name: $name)';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
@@ -14,98 +15,45 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'package:meta/meta.dart';
|
part of 'form.dart';
|
||||||
import 'package:wyatt_form_bloc/src/enums/enums.dart';
|
|
||||||
|
|
||||||
/// {@template form_input}
|
class FormInput extends Equatable {
|
||||||
/// A [FormInput] represents the value of a single form input field.
|
final String key;
|
||||||
/// It contains information about the [FormInputStatus], [value], as well
|
final FormInputValidator validator;
|
||||||
/// as validation status.
|
final FormInputMetadata metadata;
|
||||||
///
|
|
||||||
/// [FormInput] should be extended to define custom [FormInput] instances.
|
|
||||||
///
|
|
||||||
/// ```dart
|
|
||||||
/// enum FirstNameError { empty }
|
|
||||||
/// class FirstName extends FormInput<String, FirstNameError> {
|
|
||||||
/// const FirstName.pure({String value = ''}) : super.pure(value);
|
|
||||||
/// const FirstName.dirty({String value = ''}) : super.dirty(value);
|
|
||||||
///
|
|
||||||
/// @override
|
|
||||||
/// FirstNameError? validator(String value) {
|
|
||||||
/// return value.isEmpty ? FirstNameError.empty : null;
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
/// {@endtemplate}
|
|
||||||
@immutable
|
|
||||||
abstract class FormInput<T, E> {
|
|
||||||
const FormInput._(this.value, [this.pure = true]);
|
|
||||||
|
|
||||||
/// Constructor which create a `pure` [FormInput] with a given value.
|
String get name => metadata._name ?? key;
|
||||||
const FormInput.pure(T value) : this._(value);
|
|
||||||
|
|
||||||
/// Constructor which create a `dirty` [FormInput] with a given value.
|
const FormInput(
|
||||||
const FormInput.dirty(T value) : this._(value, false);
|
this.key,
|
||||||
|
this.validator, {
|
||||||
/// The value of the given [FormInput].
|
// ignore: avoid_redundant_argument_values
|
||||||
/// For example, if you have a `FormInput` for `FirstName`,
|
this.metadata = const FormInputMetadata<void>(export: true),
|
||||||
/// the value could be 'Joe'.
|
});
|
||||||
final T value;
|
|
||||||
|
|
||||||
/// If the [FormInput] is pure (has been touched/modified).
|
|
||||||
/// Typically when the `FormInput` is initially created,
|
|
||||||
/// it is created using the `FormInput.pure` constructor to
|
|
||||||
/// signify that the user has not modified it.
|
|
||||||
///
|
|
||||||
/// For subsequent changes (in response to user input), the
|
|
||||||
/// `FormInput.dirty` constructor should be used to signify that
|
|
||||||
/// the `FormInput` has been manipulated.
|
|
||||||
final bool pure;
|
|
||||||
|
|
||||||
/// The [FormInputStatus] which can be one of the following:
|
|
||||||
/// * [FormInputStatus.pure]
|
|
||||||
/// - if the input has not been modified.
|
|
||||||
/// * [FormInputStatus.invalid]
|
|
||||||
/// - if the input has been modified and validation failed.
|
|
||||||
/// * [FormInputStatus.valid]
|
|
||||||
/// - if the input has been modified and validation succeeded.
|
|
||||||
FormInputStatus get status => pure
|
|
||||||
? FormInputStatus.pure
|
|
||||||
: valid
|
|
||||||
? FormInputStatus.valid
|
|
||||||
: FormInputStatus.invalid;
|
|
||||||
|
|
||||||
/// Returns a validation error if the [FormInput] is invalid.
|
|
||||||
/// Returns `null` if the [FormInput] is valid.
|
|
||||||
E? get error => validator(value);
|
|
||||||
|
|
||||||
/// Whether the [FormInput] value is valid according to the
|
|
||||||
/// overridden `validator`.
|
|
||||||
///
|
|
||||||
/// Returns `true` if `validator` returns `null` for the
|
|
||||||
/// current [FormInput] value and `false` otherwise.
|
|
||||||
bool get valid => validator(value) == null;
|
|
||||||
|
|
||||||
/// Whether the [FormInput] value is not valid.
|
|
||||||
/// A value is invalid when the overridden `validator`
|
|
||||||
/// returns an error (non-null value).
|
|
||||||
bool get invalid => status == FormInputStatus.invalid;
|
|
||||||
|
|
||||||
/// A function that must return a validation error if the provided
|
|
||||||
/// [value] is invalid and `null` otherwise.
|
|
||||||
E? validator(T value);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => value.hashCode ^ pure.hashCode;
|
bool? get stringify => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
List<Object?> get props => [key, validator, metadata];
|
||||||
if (other.runtimeType != runtimeType) return false;
|
|
||||||
return other is FormInput<T, E> &&
|
FormInput copyWith({
|
||||||
other.value == value &&
|
String? key,
|
||||||
other.pure == pure;
|
FormInputValidator? validator,
|
||||||
|
FormInputMetadata? metadata,
|
||||||
|
}) {
|
||||||
|
return FormInput(
|
||||||
|
key ?? this.key,
|
||||||
|
validator ?? this.validator,
|
||||||
|
metadata: metadata ?? this.metadata,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
FormInput clone() {
|
||||||
String toString() => 'FormInput<$runtimeType>(value: $value, pure: $pure)';
|
return copyWith(
|
||||||
|
key: key,
|
||||||
|
validator: validator,
|
||||||
|
metadata: metadata,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
part of 'form.dart';
|
||||||
|
|
||||||
|
class FormInputMetadata<T> extends Equatable {
|
||||||
|
final bool export;
|
||||||
|
final String? _name;
|
||||||
|
final T? extra;
|
||||||
|
|
||||||
|
const FormInputMetadata({
|
||||||
|
this.export = true,
|
||||||
|
this.extra,
|
||||||
|
String? name,
|
||||||
|
}) : _name = name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool? get stringify => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [export, _name, extra];
|
||||||
|
|
||||||
|
FormInputMetadata<T> copyWith({
|
||||||
|
bool? export,
|
||||||
|
String? name,
|
||||||
|
T? extra,
|
||||||
|
}) {
|
||||||
|
return FormInputMetadata<T>(
|
||||||
|
export: export ?? this.export,
|
||||||
|
name: name ?? _name,
|
||||||
|
extra: extra ?? this.extra,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FormInputMetadata<T> clone() {
|
||||||
|
return copyWith(
|
||||||
|
export: export,
|
||||||
|
name: _name,
|
||||||
|
extra: extra,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
part of 'form.dart';
|
||||||
|
|
||||||
|
/// {@template form_input}
|
||||||
|
/// A [FormInputValidator] represents the value of a single form input field.
|
||||||
|
/// It contains information about the [FormInputStatus], [value], as well
|
||||||
|
/// as validation status.
|
||||||
|
///
|
||||||
|
/// [FormInputValidator] should be extended to define custom
|
||||||
|
/// [FormInputValidator] instances.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// enum FirstNameError { empty }
|
||||||
|
/// class FirstName extends FormInputValidator<String, FirstNameError> {
|
||||||
|
/// const FirstName.pure({String value = ''}) : super.pure(value);
|
||||||
|
/// const FirstName.dirty({String value = ''}) : super.dirty(value);
|
||||||
|
///
|
||||||
|
/// @override
|
||||||
|
/// FirstNameError? validator(String value) {
|
||||||
|
/// return value.isEmpty ? FirstNameError.empty : null;
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// {@endtemplate}
|
||||||
|
@immutable
|
||||||
|
abstract class FormInputValidator<V, E> extends Equatable {
|
||||||
|
const FormInputValidator._(this.value, [this.pure = true]);
|
||||||
|
|
||||||
|
/// Constructor which create a `pure` [FormInputValidator] with a given value.
|
||||||
|
const FormInputValidator.pure(V value) : this._(value);
|
||||||
|
|
||||||
|
/// Constructor which create a `dirty` [FormInputValidator] with a
|
||||||
|
/// given value.
|
||||||
|
const FormInputValidator.dirty(V value) : this._(value, false);
|
||||||
|
|
||||||
|
/// The value of the given [FormInputValidator].
|
||||||
|
/// For example, if you have a `FormInputValidator` for `FirstName`,
|
||||||
|
/// the value could be 'Joe'.
|
||||||
|
final V value;
|
||||||
|
|
||||||
|
/// If the [FormInputValidator] is pure (has been touched/modified).
|
||||||
|
/// Typically when the `FormInputValidator` is initially created,
|
||||||
|
/// it is created using the `FormInputValidator.pure` constructor to
|
||||||
|
/// signify that the user has not modified it.
|
||||||
|
///
|
||||||
|
/// For subsequent changes (in response to user input), the
|
||||||
|
/// `FormInputValidator.dirty` constructor should be used to signify that
|
||||||
|
/// the `FormInputValidator` has been manipulated.
|
||||||
|
final bool pure;
|
||||||
|
|
||||||
|
/// The [FormInputStatus] which can be one of the following:
|
||||||
|
/// * [FormInputStatus.pure]
|
||||||
|
/// - if the input has not been modified.
|
||||||
|
/// * [FormInputStatus.invalid]
|
||||||
|
/// - if the input has been modified and validation failed.
|
||||||
|
/// * [FormInputStatus.valid]
|
||||||
|
/// - if the input has been modified and validation succeeded.
|
||||||
|
FormInputStatus get status => pure
|
||||||
|
? FormInputStatus.pure
|
||||||
|
: valid
|
||||||
|
? FormInputStatus.valid
|
||||||
|
: FormInputStatus.invalid;
|
||||||
|
|
||||||
|
/// Returns a validation error if the [FormInputValidator] is invalid.
|
||||||
|
/// Returns `null` if the [FormInputValidator] is valid.
|
||||||
|
E? get error => validator(value);
|
||||||
|
|
||||||
|
/// Whether the [FormInputValidator] value is valid according to the
|
||||||
|
/// overridden `validator`.
|
||||||
|
///
|
||||||
|
/// Returns `true` if `validator` returns `null` for the
|
||||||
|
/// current [FormInputValidator] value and `false` otherwise.
|
||||||
|
bool get valid => validator(value) == null;
|
||||||
|
|
||||||
|
/// Whether the [FormInputValidator] value is not valid.
|
||||||
|
/// A value is invalid when the overridden `validator`
|
||||||
|
/// returns an error (non-null value).
|
||||||
|
bool get invalid => status == FormInputStatus.invalid;
|
||||||
|
|
||||||
|
/// A function that must return a validation error if the provided
|
||||||
|
/// [value] is invalid and `null` otherwise.
|
||||||
|
E? validator(V value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool? get stringify => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [value, pure];
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// {@template boolean}
|
/// {@template boolean}
|
||||||
/// Form input for a bool input
|
/// Form input for a bool input
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class Boolean extends FormInput<bool, ValidationError> {
|
class Boolean extends FormInputValidator<bool, ValidationError> {
|
||||||
/// {@macro boolean}
|
/// {@macro boolean}
|
||||||
const Boolean.pure({bool? defaultValue = false})
|
const Boolean.pure({bool? defaultValue = false})
|
||||||
: super.pure(defaultValue ?? false);
|
: super.pure(defaultValue ?? false);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// Form input for a confirmed password input.
|
/// Form input for a confirmed password input.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class ConfirmedPassword
|
class ConfirmedPassword
|
||||||
extends FormInput<String, ValidationError> {
|
extends FormInputValidator<String, ValidationError> {
|
||||||
/// {@macro confirmed_password}
|
/// {@macro confirmed_password}
|
||||||
const ConfirmedPassword.pure({this.password = ''}) : super.pure('');
|
const ConfirmedPassword.pure({this.password = ''}) : super.pure('');
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// {@template email}
|
/// {@template email}
|
||||||
/// Form input for an email input.
|
/// Form input for an email input.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class Email extends FormInput<String, ValidationError> {
|
class Email extends FormInputValidator<String, ValidationError> {
|
||||||
/// {@macro email}
|
/// {@macro email}
|
||||||
const Email.pure() : super.pure('');
|
const Email.pure() : super.pure('');
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ class Email extends FormInput<String, ValidationError> {
|
|||||||
const Email.dirty([String value = '']) : super.dirty(value);
|
const Email.dirty([String value = '']) : super.dirty(value);
|
||||||
|
|
||||||
static final RegExp _emailRegExp = RegExp(
|
static final RegExp _emailRegExp = RegExp(
|
||||||
r'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$',
|
r'[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+',
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// {@template iban}
|
/// {@template iban}
|
||||||
/// Form input for an IBAN input.
|
/// Form input for an IBAN input.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class Iban extends FormInput<String, ValidationError> {
|
class Iban extends FormInputValidator<String, ValidationError> {
|
||||||
/// {@macro iban}
|
/// {@macro iban}
|
||||||
const Iban.pure() : super.pure('');
|
const Iban.pure() : super.pure('');
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// {@template list_option}
|
/// {@template list_option}
|
||||||
/// Form input for a list input
|
/// Form input for a list input
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class ListOption<T> extends FormInput<List<T>, ValidationError> {
|
class ListOption<T> extends FormInputValidator<List<T>, ValidationError> {
|
||||||
/// {@macro list_option}
|
/// {@macro list_option}
|
||||||
const ListOption.pure({List<T>? defaultValue})
|
const ListOption.pure({List<T>? defaultValue})
|
||||||
: super.pure(defaultValue ?? const []);
|
: super.pure(defaultValue ?? const []);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// {@template name}
|
/// {@template name}
|
||||||
/// Form input for a name input.
|
/// Form input for a name input.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class Name extends FormInput<String, ValidationError> {
|
class Name extends FormInputValidator<String, ValidationError> {
|
||||||
/// {@macro name}
|
/// {@macro name}
|
||||||
const Name.pure() : super.pure('');
|
const Name.pure() : super.pure('');
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// {@template password}
|
/// {@template password}
|
||||||
/// Form input for a password input.
|
/// Form input for a password input.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class Password extends FormInput<String, ValidationError> {
|
class Password extends FormInputValidator<String, ValidationError> {
|
||||||
/// {@macro password}
|
/// {@macro password}
|
||||||
const Password.pure() : super.pure('');
|
const Password.pure() : super.pure('');
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// {@template phone}
|
/// {@template phone}
|
||||||
/// Form input for a phone input.
|
/// Form input for a phone input.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class Phone extends FormInput<String, ValidationError> {
|
class Phone extends FormInputValidator<String, ValidationError> {
|
||||||
/// {@macro phone}
|
/// {@macro phone}
|
||||||
const Phone.pure() : super.pure('');
|
const Phone.pure() : super.pure('');
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// {@template siren}
|
/// {@template siren}
|
||||||
/// Form input for a SIREN input.
|
/// Form input for a SIREN input.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class Siren extends FormInput<String, ValidationError> {
|
class Siren extends FormInputValidator<String, ValidationError> {
|
||||||
/// {@macro siren}
|
/// {@macro siren}
|
||||||
const Siren.pure() : super.pure('');
|
const Siren.pure() : super.pure('');
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import 'package:wyatt_form_bloc/src/form/form.dart';
|
|||||||
/// {@template text_string}
|
/// {@template text_string}
|
||||||
/// Form input for a text input
|
/// Form input for a text input
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class TextString extends FormInput<String, ValidationError> {
|
class TextString extends FormInputValidator<String, ValidationError> {
|
||||||
/// {@macro text_string}
|
/// {@macro text_string}
|
||||||
const TextString.pure() : super.pure('');
|
const TextString.pure() : super.pure('');
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
name: wyatt_form_bloc
|
name: wyatt_form_bloc
|
||||||
description: Manage forms in Dart & Flutter with Bloc
|
description: Manage forms in Dart & Flutter with Bloc
|
||||||
repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_form_bloc
|
repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_form_bloc
|
||||||
version: 0.0.2
|
version: 0.0.3
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.16.2 <3.0.0'
|
sdk: '>=2.17.2 <3.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
bloc: ^8.0.3
|
bloc: ^8.0.3
|
||||||
|
equatable: ^2.0.3
|
||||||
meta: ^1.7.0
|
meta: ^1.7.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
test: ^1.21.0
|
test: ^1.21.4
|
||||||
|
|
||||||
wyatt_analysis:
|
wyatt_analysis:
|
||||||
git:
|
git:
|
||||||
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
|
||||||
ref: wyatt_analysis-v2.1.0
|
ref: wyatt_analysis-v2.2.1
|
||||||
path: packages/wyatt_analysis
|
path: packages/wyatt_analysis
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
## 1.2.0
|
||||||
|
|
||||||
|
- **FEAT**: add new middleware feature.
|
||||||
|
- **FEAT**: implements doublelinked list for middlewares.
|
||||||
|
- **FEAT**: [WIP] implements middleware system.
|
||||||
|
- **FEAT**: [WIP] work on middleware feature.
|
||||||
|
|
||||||
## 1.1.0
|
## 1.1.0
|
||||||
|
|
||||||
- **FEAT**: add oauth2 refresh token client.
|
- **FEAT**: add oauth2 refresh token client.
|
||||||
|
|||||||
@@ -1,39 +1,182 @@
|
|||||||
<!--
|
<!--
|
||||||
This README describes the package. If you publish this package to pub.dev,
|
* Copyright (C) 2022 WYATT GROUP
|
||||||
this README's contents appear on the landing page for your package.
|
* Please see the AUTHORS file for details.
|
||||||
|
|
||||||
For information about how to write a good package README, see the guide for
|
* This program is free software: you can redistribute it and/or modify
|
||||||
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* any later version.
|
||||||
|
|
||||||
For general information about developing packages, see the Dart guide for
|
* This program is distributed in the hope that it will be useful,
|
||||||
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
and the Flutter guide for
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
[developing packages and plugins](https://flutter.dev/developing-packages).
|
* GNU General Public License for more details.
|
||||||
|
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
TODO: Put a short description of the package here that helps potential users
|
# Dart - HTTP Client
|
||||||
know whether this package might be useful for them.
|
|
||||||
|
|
||||||
## Features
|
<p align="left">
|
||||||
|
<a href="https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_analysis">
|
||||||
|
<img src="https://img.shields.io/badge/Style-Wyatt%20Analysis-blue.svg?style=flat-square" alt="Style: Wyatt Analysis" />
|
||||||
|
</a>
|
||||||
|
<img src="https://img.shields.io/badge/SDK-Dart%20%7C%20Flutter-blue?style=flat-square" alt="SDK: Dart & Flutter" />
|
||||||
|
</p>
|
||||||
|
|
||||||
TODO: List what your package can do. Maybe include images, gifs, or videos.
|
HTTP Client for Dart with Middlewares !
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
TODO: List prerequisites and provide or point to information on how to
|
Simply add wyatt_http_client in pubspec.yaml, then
|
||||||
start using the package.
|
|
||||||
|
```dart
|
||||||
|
import 'package:wyatt_http_client/wyatt_http_client.dart';
|
||||||
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
TODO: Include short and useful examples for package users. Add longer examples
|
Firstly you have to understand **Middleware** and **Pipeline** concepts.
|
||||||
to `/example` folder.
|
|
||||||
|
In `wyatt_http_client` a middleware is an object where requests and responses
|
||||||
|
pass through. And a pipeline is basicaly a list of middlewares.
|
||||||
|
|
||||||
|
In a pipeline with middlewares A and B, if request pass through A, then B,
|
||||||
|
the response will pass through B then A.
|
||||||
|
|
||||||
|
> You can `print(pipeline)` to get full process order of a pipeline.
|
||||||
|
|
||||||
|
For example, if you want to log every request, and simplify an url you can use provided `SimpleLogger` and `UriPrefix` .
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
const like = 'sample';
|
// Create the Pipeline
|
||||||
|
final Pipeline pipeline = Pipeline()
|
||||||
|
.addMiddleware(
|
||||||
|
UriPrefixMiddleware(
|
||||||
|
protocol: Protocols.http,
|
||||||
|
authority: 'localhost:80',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.addMiddleware(SimpleLoggerMiddleware());
|
||||||
```
|
```
|
||||||
|
|
||||||
## Additional information
|
Then if you print the pipeline,
|
||||||
|
|
||||||
TODO: Tell users more about the package: where to find more information, how to
|
```
|
||||||
contribute to the package, how to file issues, what response they can expect
|
[Req] -> UriPrefix -> SimpleLogger
|
||||||
from the package authors, and more.
|
[Res] -> SimpleLogger
|
||||||
|
```
|
||||||
|
|
||||||
|
> The `response` doesn't pass through `UriPrefix` because it's an `OnRequestMiddleware` only.
|
||||||
|
|
||||||
|
And you can create a client.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final client = MiddlewareClient(pipeline: pipeline);
|
||||||
|
```
|
||||||
|
|
||||||
|
At this point you can use `client` like every Client from `package:http/http.dart` .
|
||||||
|
|
||||||
|
## Recipes
|
||||||
|
|
||||||
|
### Rest API with URL Authentication
|
||||||
|
|
||||||
|
Let's build a client for a REST API where the (bad) authentication is through the URL.
|
||||||
|
We need some middlewares:
|
||||||
|
|
||||||
|
* SimpleLogger, to log every request and response (useful for debug).
|
||||||
|
* BodyToJson, to automaticaly transform Map object to JSON.
|
||||||
|
* UriPrefix, to simplify the build of an API Object (save protocol and API prefix).
|
||||||
|
* UnsafeAuth, to use url based authentication.
|
||||||
|
|
||||||
|
Let's start by creating the Pipeline:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final Pipeline pipeline = Pipeline()
|
||||||
|
.addMiddleware(
|
||||||
|
UriPrefixMiddleware(
|
||||||
|
protocol: Protocols.http,
|
||||||
|
authority: 'localhost:80',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.addMiddleware(BodyToJsonMiddleware())
|
||||||
|
.addMiddleware(
|
||||||
|
UnsafeAuthMiddleware(
|
||||||
|
username: 'wyatt',
|
||||||
|
password: 'motdepasse',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.addMiddleware(SimpleLoggerMiddleware());
|
||||||
|
```
|
||||||
|
|
||||||
|
Then simply create a client and make a call.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final client = MiddlewareClient(pipeline: pipeline);
|
||||||
|
|
||||||
|
await client.get(Uri.parse('/protected'));
|
||||||
|
```
|
||||||
|
|
||||||
|
> Here it make a `GET` call on `http://localhost:80/protected?username=wyatt&password=motdepasse`
|
||||||
|
|
||||||
|
And voilà.
|
||||||
|
|
||||||
|
### Rest API with Oauth2
|
||||||
|
|
||||||
|
So now we want a real authentication.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
final Pipeline pipeline = Pipeline()
|
||||||
|
.addMiddleware(
|
||||||
|
UriPrefixMiddleware(
|
||||||
|
protocol: Protocols.http,
|
||||||
|
authority: 'localhost:80',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.addMiddleware(BodyToJsonMiddleware())
|
||||||
|
.addMiddleware(
|
||||||
|
RefreshTokenAuthMiddleware(
|
||||||
|
authorizationEndpoint: '/auth/sign-in',
|
||||||
|
tokenEndpoint: '/auth/refresh',
|
||||||
|
accessTokenParser: (body) => body['access_token']! as String,
|
||||||
|
refreshTokenParser: (body) => body['refresh_token']! as String,
|
||||||
|
unauthorized: HttpStatus.forbidden,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.addMiddleware(SimpleLoggerMiddleware());
|
||||||
|
```
|
||||||
|
|
||||||
|
> Here we just change `UnsafeAuthMiddleware` by `RefreshTokenAuthMiddleware` and the whole app while adapt to a new authentication system.
|
||||||
|
|
||||||
|
### Create a new Middleware
|
||||||
|
|
||||||
|
You can create your own middleware by implementing `Middleware` class, and use mixins to add `OnRequest` and/or `OnResponse` methods.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
class SimpleLoggerMiddleware
|
||||||
|
with OnRequestMiddleware, OnResponseMiddleware
|
||||||
|
implements Middleware {
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getName() => 'SimpleLogger';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MiddlewareRequest> onRequest(
|
||||||
|
MiddlewareContext context,
|
||||||
|
MiddlewareRequest request,
|
||||||
|
) async {
|
||||||
|
print('${getName()}::OnRequest');
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MiddlewareResponse> onResponse(
|
||||||
|
MiddlewareContext context,
|
||||||
|
MiddlewareResponse response,
|
||||||
|
) async {
|
||||||
|
print('${getName()}::OnResponse');
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -17,14 +17,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:wyatt_http_client/src/authentication/basic_authentication_client.dart';
|
import 'package:wyatt_http_client/wyatt_http_client.dart';
|
||||||
import 'package:wyatt_http_client/src/authentication/bearer_authentication_client.dart';
|
|
||||||
import 'package:wyatt_http_client/src/authentication/digest_authentication_client.dart';
|
|
||||||
import 'package:wyatt_http_client/src/authentication/refresh_token_client.dart';
|
|
||||||
import 'package:wyatt_http_client/src/authentication/unsafe_authentication_client.dart';
|
|
||||||
import 'package:wyatt_http_client/src/rest_client.dart';
|
|
||||||
import 'package:wyatt_http_client/src/utils/header_keys.dart';
|
|
||||||
import 'package:wyatt_http_client/src/utils/protocols.dart';
|
|
||||||
|
|
||||||
String lastToken = '';
|
String lastToken = '';
|
||||||
int token = 0;
|
int token = 0;
|
||||||
@@ -42,7 +35,7 @@ Future<void> handleBasic(HttpRequest req) async {
|
|||||||
|
|
||||||
Future<void> handleBasicNegotiate(HttpRequest req) async {
|
Future<void> handleBasicNegotiate(HttpRequest req) async {
|
||||||
if (req.headers.value('Authorization') == null) {
|
if (req.headers.value('Authorization') == null) {
|
||||||
req.response.statusCode = HttpStatus.unauthorized;
|
req.response.statusCode = HttpStatus.unauthorized.statusCode;
|
||||||
req.response.headers.set(HeaderKeys.wwwAuthenticate, 'Basic realm="Wyatt"');
|
req.response.headers.set(HeaderKeys.wwwAuthenticate, 'Basic realm="Wyatt"');
|
||||||
print(req.response.headers.value('WWW-Authenticate'));
|
print(req.response.headers.value('WWW-Authenticate'));
|
||||||
return req.response.close();
|
return req.response.close();
|
||||||
@@ -56,7 +49,7 @@ Future<void> handleBearer(HttpRequest req) async {
|
|||||||
|
|
||||||
Future<void> handleDigest(HttpRequest req) async {
|
Future<void> handleDigest(HttpRequest req) async {
|
||||||
if (req.headers.value('Authorization') == null) {
|
if (req.headers.value('Authorization') == null) {
|
||||||
req.response.statusCode = HttpStatus.unauthorized;
|
req.response.statusCode = HttpStatus.unauthorized.statusCode;
|
||||||
req.response.headers.set(
|
req.response.headers.set(
|
||||||
'WWW-Authenticate',
|
'WWW-Authenticate',
|
||||||
'Digest realm="Wyatt", '
|
'Digest realm="Wyatt", '
|
||||||
@@ -110,7 +103,7 @@ Future<void> handleOauth2RefreshToken(HttpRequest req) async {
|
|||||||
return req.response.close();
|
return req.response.close();
|
||||||
} else {
|
} else {
|
||||||
lastToken = receivedToken;
|
lastToken = receivedToken;
|
||||||
req.response.statusCode = HttpStatus.unauthorized;
|
req.response.statusCode = HttpStatus.unauthorized.statusCode;
|
||||||
return req.response.close();
|
return req.response.close();
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -160,13 +153,13 @@ Future<void> server() async {
|
|||||||
print('Authorized');
|
print('Authorized');
|
||||||
error = 0;
|
error = 0;
|
||||||
} else {
|
} else {
|
||||||
request.response.statusCode = HttpStatus.unauthorized;
|
request.response.statusCode = HttpStatus.unauthorized.statusCode;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '/test/oauth2-test-timeout':
|
case '/test/oauth2-test-timeout':
|
||||||
error++;
|
error++;
|
||||||
print('Error $error');
|
print('Error $error');
|
||||||
request.response.statusCode = HttpStatus.unauthorized;
|
request.response.statusCode = HttpStatus.unauthorized.statusCode;
|
||||||
break;
|
break;
|
||||||
case '/test/oauth2-login':
|
case '/test/oauth2-login':
|
||||||
if (request.method == 'POST') {
|
if (request.method == 'POST') {
|
||||||
@@ -189,12 +182,12 @@ Future<void> server() async {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '/test/oauth2-refresh-error':
|
case '/test/oauth2-refresh-error':
|
||||||
request.response.statusCode = HttpStatus.unauthorized;
|
request.response.statusCode = HttpStatus.unauthorized.statusCode;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
print(' => Unknown path or method');
|
print(' => Unknown path or method');
|
||||||
request.response.statusCode = HttpStatus.notFound;
|
request.response.statusCode = HttpStatus.notFound.statusCode;
|
||||||
}
|
}
|
||||||
request.response.close();
|
request.response.close();
|
||||||
print('====================');
|
print('====================');
|
||||||
@@ -204,73 +197,98 @@ Future<void> server() async {
|
|||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
unawaited(server());
|
unawaited(server());
|
||||||
final base = 'localhost:8080';
|
final base = 'localhost:8080';
|
||||||
final restClient = RestClient(protocol: Protocols.http, authority: base);
|
final uriPrefix = UriPrefixMiddleware(
|
||||||
|
protocol: Protocols.http,
|
||||||
|
authority: base,
|
||||||
|
);
|
||||||
|
final jsonEncoder = BodyToJsonMiddleware();
|
||||||
|
final logger = SimpleLoggerMiddleware();
|
||||||
|
|
||||||
// Basic
|
// Basic
|
||||||
final basic = BasicAuthenticationClient(
|
final basicAuth = BasicAuthMiddleware(
|
||||||
username: 'username',
|
username: 'username',
|
||||||
password: 'password',
|
password: 'password',
|
||||||
inner: restClient,
|
);
|
||||||
|
final basic = MiddlewareClient(
|
||||||
|
pipeline: Pipeline.fromIterable([
|
||||||
|
uriPrefix,
|
||||||
|
basicAuth,
|
||||||
|
logger,
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
await basic.get(Uri.parse('/test/basic-test'));
|
await basic.get(Uri.parse('/test/basic-test'));
|
||||||
|
|
||||||
// Basic with negotiate
|
|
||||||
final basicWithNegotiate = BasicAuthenticationClient(
|
|
||||||
username: 'username',
|
|
||||||
password: 'password',
|
|
||||||
preemptive: false,
|
|
||||||
inner: restClient,
|
|
||||||
);
|
|
||||||
await basicWithNegotiate.get(Uri.parse('/test/basic-test-with-negotiate'));
|
|
||||||
|
|
||||||
// Digest
|
// Digest
|
||||||
final digest = DigestAuthenticationClient(
|
final digestAuth = DigestAuthMiddleware(
|
||||||
username: 'Mufasa',
|
username: 'Mufasa',
|
||||||
password: 'Circle Of Life',
|
password: 'Circle Of Life',
|
||||||
inner: restClient,
|
);
|
||||||
|
final digest = MiddlewareClient(
|
||||||
|
pipeline: Pipeline.fromIterable([
|
||||||
|
uriPrefix,
|
||||||
|
digestAuth,
|
||||||
|
logger,
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
await digest.get(Uri.parse('/test/digest-test'));
|
await digest.get(Uri.parse('/test/digest-test'));
|
||||||
|
|
||||||
// Bearer
|
// // Bearer
|
||||||
final bearer = BearerAuthenticationClient(
|
// final bearer = BearerAuthenticationClient(
|
||||||
token: 'access-token-test',
|
// token: 'access-token-test',
|
||||||
inner: restClient,
|
// inner: restClient,
|
||||||
);
|
// );
|
||||||
await bearer.get(Uri.parse('/test/bearer-test'));
|
// await bearer.get(Uri.parse('/test/bearer-test'));
|
||||||
|
|
||||||
// API Key
|
// // API Key
|
||||||
final apiKey = BearerAuthenticationClient(
|
// final apiKey = BearerAuthenticationClient(
|
||||||
token: 'awesome-api-key',
|
// token: 'awesome-api-key',
|
||||||
authenticationMethod: 'ApiKey',
|
// authenticationMethod: 'ApiKey',
|
||||||
inner: restClient,
|
// inner: restClient,
|
||||||
);
|
// );
|
||||||
await apiKey.get(Uri.parse('/test/apikey-test'));
|
// await apiKey.get(Uri.parse('/test/apikey-test'));
|
||||||
|
|
||||||
// Unsafe URL
|
// Unsafe URL
|
||||||
final unsafe = UnsafeAuthenticationClient(
|
final unsafeAuth = UnsafeAuthMiddleware(
|
||||||
username: 'Mufasa',
|
username: 'Mufasa',
|
||||||
password: 'Circle Of Life',
|
password: 'Circle Of Life',
|
||||||
inner: restClient,
|
);
|
||||||
|
final unsafe = MiddlewareClient(
|
||||||
|
pipeline: Pipeline.fromIterable([
|
||||||
|
uriPrefix,
|
||||||
|
unsafeAuth,
|
||||||
|
logger,
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
await unsafe.get(Uri.parse('/test/unsafe-test'));
|
await unsafe.get(Uri.parse('/test/unsafe-test'));
|
||||||
|
|
||||||
// OAuth2
|
// OAuth2
|
||||||
final refreshToken = RefreshTokenClient(
|
final refreshTokenAuth = RefreshTokenAuthMiddleware(
|
||||||
authorizationEndpoint: '/test/oauth2-test?action=login',
|
authorizationEndpoint: '/test/oauth2-test?action=login',
|
||||||
tokenEndpoint: '/test/oauth2-test?action=refresh',
|
tokenEndpoint: '/test/oauth2-test?action=refresh',
|
||||||
accessTokenParser: (body) => body['accessToken']! as String,
|
accessTokenParser: (body) => body['accessToken']! as String,
|
||||||
refreshTokenParser: (body) => body['refreshToken']! as String,
|
refreshTokenParser: (body) => body['refreshToken']! as String,
|
||||||
inner: restClient,
|
);
|
||||||
|
final refreshToken = MiddlewareClient(
|
||||||
|
pipeline: Pipeline.fromIterable([
|
||||||
|
uriPrefix,
|
||||||
|
jsonEncoder,
|
||||||
|
refreshTokenAuth,
|
||||||
|
logger,
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
await refreshToken.get(Uri.parse('/test/oauth2-test'));
|
await refreshToken.get(Uri.parse('/test/oauth2-test'));
|
||||||
await refreshToken.authorize(<String, String>{
|
// Login
|
||||||
'username': 'username',
|
await refreshToken.post(
|
||||||
'password': 'password',
|
Uri.parse('/test/oauth2-test'),
|
||||||
});
|
body: <String, String>{
|
||||||
|
'username': 'username',
|
||||||
|
'password': 'password',
|
||||||
|
},
|
||||||
|
);
|
||||||
await refreshToken.get(Uri.parse('/test/oauth2-test'));
|
await refreshToken.get(Uri.parse('/test/oauth2-test'));
|
||||||
await refreshToken.refresh();
|
// await refreshToken.refresh();
|
||||||
await refreshToken.get(Uri.parse('/test/oauth2-test'));
|
// await refreshToken.get(Uri.parse('/test/oauth2-test'));
|
||||||
await refreshToken.get(Uri.parse('/test/oauth2-test?action=access-denied'));
|
// await refreshToken.get(Uri.parse('/test/oauth2-test?action=access-denied'));
|
||||||
|
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,398 @@
|
|||||||
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:wyatt_http_client/src/middleware_client.dart';
|
||||||
|
import 'package:wyatt_http_client/src/middlewares/body_to_json_middleware.dart';
|
||||||
|
import 'package:wyatt_http_client/src/middlewares/refresh_token_auth_middleware.dart';
|
||||||
|
import 'package:wyatt_http_client/src/middlewares/simple_logger_middleware.dart';
|
||||||
|
import 'package:wyatt_http_client/src/middlewares/uri_prefix_middleware.dart';
|
||||||
|
import 'package:wyatt_http_client/src/pipeline.dart';
|
||||||
|
import 'package:wyatt_http_client/src/utils/http_status.dart';
|
||||||
|
import 'package:wyatt_http_client/src/utils/protocols.dart';
|
||||||
|
|
||||||
|
enum EmailVerificationAction {
|
||||||
|
signUp,
|
||||||
|
resetPassword,
|
||||||
|
changeEmail;
|
||||||
|
|
||||||
|
String toSnakeCase() {
|
||||||
|
return name.splitMapJoin(
|
||||||
|
RegExp('[A-Z]'),
|
||||||
|
onMatch: (m) => '_${m[0]?.toLowerCase()}',
|
||||||
|
onNonMatch: (n) => n,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory EmailVerificationAction.fromString(String str) {
|
||||||
|
return EmailVerificationAction.values.firstWhere(
|
||||||
|
(EmailVerificationAction element) => element.toSnakeCase() == str,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VerifyCode {
|
||||||
|
final String email;
|
||||||
|
final String verificationCode;
|
||||||
|
final EmailVerificationAction action;
|
||||||
|
VerifyCode({
|
||||||
|
required this.email,
|
||||||
|
required this.verificationCode,
|
||||||
|
required this.action,
|
||||||
|
});
|
||||||
|
|
||||||
|
VerifyCode copyWith({
|
||||||
|
String? email,
|
||||||
|
String? verificationCode,
|
||||||
|
EmailVerificationAction? action,
|
||||||
|
}) {
|
||||||
|
return VerifyCode(
|
||||||
|
email: email ?? this.email,
|
||||||
|
verificationCode: verificationCode ?? this.verificationCode,
|
||||||
|
action: action ?? this.action,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'email': email,
|
||||||
|
'verification_code': verificationCode,
|
||||||
|
'action': action.toSnakeCase(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory VerifyCode.fromMap(Map<String, dynamic> map) {
|
||||||
|
return VerifyCode(
|
||||||
|
email: map['email'] as String,
|
||||||
|
verificationCode: map['verification_code'] as String,
|
||||||
|
action: EmailVerificationAction.fromString(map['action'] as String),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory VerifyCode.fromJson(String source) =>
|
||||||
|
VerifyCode.fromMap(json.decode(source) as Map<String, dynamic>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'VerifyCode(email: $email, verificationCode: $verificationCode, action: $action)';
|
||||||
|
}
|
||||||
|
|
||||||
|
class Account {
|
||||||
|
final String email;
|
||||||
|
final String? sessionId;
|
||||||
|
Account({
|
||||||
|
required this.email,
|
||||||
|
this.sessionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
Account copyWith({
|
||||||
|
String? email,
|
||||||
|
String? sessionId,
|
||||||
|
}) {
|
||||||
|
return Account(
|
||||||
|
email: email ?? this.email,
|
||||||
|
sessionId: sessionId ?? this.sessionId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'email': email,
|
||||||
|
'session_id': sessionId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Account.fromMap(Map<String, dynamic> map) {
|
||||||
|
return Account(
|
||||||
|
email: map['email'] as String,
|
||||||
|
sessionId: map['session_id'] != null ? map['session_id'] as String : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory Account.fromJson(String source) =>
|
||||||
|
Account.fromMap(json.decode(source) as Map<String, dynamic>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'Account(email: $email, sessionId: $sessionId)';
|
||||||
|
}
|
||||||
|
|
||||||
|
class SignUp {
|
||||||
|
final String sessionId;
|
||||||
|
final String password;
|
||||||
|
SignUp({
|
||||||
|
required this.sessionId,
|
||||||
|
required this.password,
|
||||||
|
});
|
||||||
|
|
||||||
|
SignUp copyWith({
|
||||||
|
String? sessionId,
|
||||||
|
String? password,
|
||||||
|
}) {
|
||||||
|
return SignUp(
|
||||||
|
sessionId: sessionId ?? this.sessionId,
|
||||||
|
password: password ?? this.password,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'session_id': sessionId,
|
||||||
|
'password': password,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory SignUp.fromMap(Map<String, dynamic> map) {
|
||||||
|
return SignUp(
|
||||||
|
sessionId: map['session_id'] as String,
|
||||||
|
password: map['password'] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory SignUp.fromJson(String source) =>
|
||||||
|
SignUp.fromMap(json.decode(source) as Map<String, dynamic>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'SignUp(sessionId: $sessionId, password: $password)';
|
||||||
|
}
|
||||||
|
|
||||||
|
class TokenSuccess {
|
||||||
|
final String accessToken;
|
||||||
|
final String refreshToken;
|
||||||
|
final Account account;
|
||||||
|
TokenSuccess({
|
||||||
|
required this.accessToken,
|
||||||
|
required this.refreshToken,
|
||||||
|
required this.account,
|
||||||
|
});
|
||||||
|
|
||||||
|
TokenSuccess copyWith({
|
||||||
|
String? accessToken,
|
||||||
|
String? refreshToken,
|
||||||
|
Account? account,
|
||||||
|
}) {
|
||||||
|
return TokenSuccess(
|
||||||
|
accessToken: accessToken ?? this.accessToken,
|
||||||
|
refreshToken: refreshToken ?? this.refreshToken,
|
||||||
|
account: account ?? this.account,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'access_token': accessToken,
|
||||||
|
'refresh_token': refreshToken,
|
||||||
|
'account': account.toMap(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory TokenSuccess.fromMap(Map<String, dynamic> map) {
|
||||||
|
return TokenSuccess(
|
||||||
|
accessToken: map['access_token'] as String,
|
||||||
|
refreshToken: map['refresh_token'] as String,
|
||||||
|
account: Account.fromMap(map['account'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory TokenSuccess.fromJson(String source) =>
|
||||||
|
TokenSuccess.fromMap(json.decode(source) as Map<String, dynamic>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'TokenSuccess(accessToken: $accessToken, refreshToken: $refreshToken, account: $account)';
|
||||||
|
}
|
||||||
|
|
||||||
|
class Login {
|
||||||
|
final String email;
|
||||||
|
final String password;
|
||||||
|
Login({
|
||||||
|
required this.email,
|
||||||
|
required this.password,
|
||||||
|
});
|
||||||
|
|
||||||
|
Login copyWith({
|
||||||
|
String? email,
|
||||||
|
String? password,
|
||||||
|
}) {
|
||||||
|
return Login(
|
||||||
|
email: email ?? this.email,
|
||||||
|
password: password ?? this.password,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'email': email,
|
||||||
|
'password': password,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Login.fromMap(Map<String, dynamic> map) {
|
||||||
|
return Login(
|
||||||
|
email: map['email'] as String,
|
||||||
|
password: map['password'] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory Login.fromJson(String source) =>
|
||||||
|
Login.fromMap(json.decode(source) as Map<String, dynamic>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'Login(email: $email, password: $password)';
|
||||||
|
}
|
||||||
|
|
||||||
|
class FastAPI {
|
||||||
|
final String baseUrl;
|
||||||
|
final MiddlewareClient client;
|
||||||
|
final int apiVersion;
|
||||||
|
|
||||||
|
FastAPI({
|
||||||
|
this.baseUrl = 'localhost:80',
|
||||||
|
MiddlewareClient? client,
|
||||||
|
this.apiVersion = 1,
|
||||||
|
}) : client = client ?? MiddlewareClient();
|
||||||
|
|
||||||
|
String get apiPath => '/api/v$apiVersion';
|
||||||
|
|
||||||
|
Future<void> sendSignUpCode(String email) async {
|
||||||
|
final r = await client.post(
|
||||||
|
Uri.parse('$apiPath/auth/send-sign-up-code'),
|
||||||
|
body: <String, String>{
|
||||||
|
'email': email,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (r.statusCode != 201) {
|
||||||
|
throw Exception('Invalid reponse: ${r.statusCode}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Account> verifyCode(VerifyCode verifyCode) async {
|
||||||
|
final r = await client.post(
|
||||||
|
Uri.parse('$apiPath/auth/verify-code'),
|
||||||
|
body: verifyCode.toMap(),
|
||||||
|
);
|
||||||
|
if (r.statusCode != 202) {
|
||||||
|
throw Exception('Invalid reponse: ${r.statusCode}');
|
||||||
|
} else {
|
||||||
|
return Account.fromMap(
|
||||||
|
(jsonDecode(r.body) as Map<String, dynamic>)['account']
|
||||||
|
as Map<String, dynamic>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Account> signUp(SignUp signUp) async {
|
||||||
|
final r = await client.post(
|
||||||
|
Uri.parse('$apiPath/auth/sign-up'),
|
||||||
|
body: signUp.toMap(),
|
||||||
|
);
|
||||||
|
if (r.statusCode != 201) {
|
||||||
|
throw Exception('Invalid reponse: ${r.statusCode}');
|
||||||
|
} else {
|
||||||
|
return Account.fromJson(r.body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<TokenSuccess> signInWithPassword(Login login) async {
|
||||||
|
final r = await client.post(
|
||||||
|
Uri.parse('$apiPath/auth/sign-in-with-password'),
|
||||||
|
body: login.toMap(),
|
||||||
|
);
|
||||||
|
if (r.statusCode != 200) {
|
||||||
|
throw Exception('Invalid reponse: ${r.statusCode}');
|
||||||
|
} else {
|
||||||
|
return TokenSuccess.fromJson(r.body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Future<TokenSuccess> refresh() async {
|
||||||
|
// final r = await client.refresh();
|
||||||
|
// return TokenSuccess.fromJson(r?.body ?? '');
|
||||||
|
// }
|
||||||
|
|
||||||
|
Future<List<Account>> getAccountList() async {
|
||||||
|
final r = await client.get(
|
||||||
|
Uri.parse('$apiPath/account'),
|
||||||
|
);
|
||||||
|
if (r.statusCode != 200) {
|
||||||
|
throw Exception('Invalid reponse: ${r.statusCode}');
|
||||||
|
} else {
|
||||||
|
final list = (jsonDecode(r.body) as Map<String, dynamic>)['founds']
|
||||||
|
as List<Map<String, dynamic>>;
|
||||||
|
final result = <Account>[];
|
||||||
|
for (final element in list) {
|
||||||
|
result.add(Account.fromMap(element));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main(List<String> args) async {
|
||||||
|
final Pipeline pipeline = Pipeline()
|
||||||
|
.addMiddleware(
|
||||||
|
UriPrefixMiddleware(
|
||||||
|
protocol: Protocols.http,
|
||||||
|
authority: 'localhost:80',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.addMiddleware(BodyToJsonMiddleware())
|
||||||
|
.addMiddleware(
|
||||||
|
RefreshTokenAuthMiddleware(
|
||||||
|
authorizationEndpoint: '/api/v1/auth/sign-in-with-password',
|
||||||
|
tokenEndpoint: '/api/v1/auth/refresh',
|
||||||
|
accessTokenParser: (body) => body['access_token']! as String,
|
||||||
|
refreshTokenParser: (body) => body['refresh_token']! as String,
|
||||||
|
unauthorized: HttpStatus.forbidden,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.addMiddleware(SimpleLoggerMiddleware());
|
||||||
|
|
||||||
|
print(pipeline);
|
||||||
|
final client = MiddlewareClient(pipeline: pipeline);
|
||||||
|
|
||||||
|
final api = FastAPI(
|
||||||
|
client: client,
|
||||||
|
);
|
||||||
|
|
||||||
|
await api.sendSignUpCode('git@pcl.ovh');
|
||||||
|
final verifiedAccount = await api.verifyCode(
|
||||||
|
VerifyCode(
|
||||||
|
email: 'git@pcl.ovh',
|
||||||
|
verificationCode: '000000000',
|
||||||
|
action: EmailVerificationAction.signUp,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final registeredAccount = await api.signUp(
|
||||||
|
SignUp(sessionId: verifiedAccount.sessionId ?? '', password: 'password'),
|
||||||
|
);
|
||||||
|
final signedInAccount = await api.signInWithPassword(
|
||||||
|
Login(email: 'git@pcl.ovh', password: 'password'),
|
||||||
|
);
|
||||||
|
final accountList = await api.getAccountList();
|
||||||
|
print(accountList);
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'package:wyatt_http_client/src/middleware_client.dart';
|
||||||
|
import 'package:wyatt_http_client/src/middlewares/body_to_json_middleware.dart';
|
||||||
|
import 'package:wyatt_http_client/src/middlewares/simple_logger_middleware.dart';
|
||||||
|
import 'package:wyatt_http_client/src/middlewares/unsafe_auth_middleware.dart';
|
||||||
|
import 'package:wyatt_http_client/src/middlewares/uri_prefix_middleware.dart';
|
||||||
|
import 'package:wyatt_http_client/src/pipeline.dart';
|
||||||
|
import 'package:wyatt_http_client/src/utils/protocols.dart';
|
||||||
|
|
||||||
|
// class RequestMutatorMiddleware implements Middleware {
|
||||||
|
// @override
|
||||||
|
// Middleware? parent;
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Middleware? child;
|
||||||
|
|
||||||
|
// RequestMutatorMiddleware({
|
||||||
|
// this.parent,
|
||||||
|
// this.child,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// BaseRequest onRequest(BaseRequest request) {
|
||||||
|
// print('RequestMutator::OnRequest: ${request.method} -> ${request.url}');
|
||||||
|
// return child?.onRequest(request) ?? request;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// BaseResponse onResponse(BaseResponse response) {
|
||||||
|
// final res = child?.onResponse(response) ?? response;
|
||||||
|
// print(
|
||||||
|
// 'RequestMutator::OnResponse: ${res.statusCode} -> ${res.contentLength} bytes',
|
||||||
|
// );
|
||||||
|
// return res;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// typedef Middleware = Handler Function(Handler innerHandler);
|
||||||
|
|
||||||
|
// Middleware createMiddleware({
|
||||||
|
// FutureOr<Response?> Function(Request)? requestHandler,
|
||||||
|
// FutureOr<Response> Function(Response)? responseHandler,
|
||||||
|
// FutureOr<Response> Function(Object error, StackTrace)? errorHandler,
|
||||||
|
// }) {
|
||||||
|
// requestHandler ??= (request) => null;
|
||||||
|
// responseHandler ??= (response) => response;
|
||||||
|
|
||||||
|
// FutureOr<Response> Function(Object, StackTrace)? onError;
|
||||||
|
// if (errorHandler != null) {
|
||||||
|
// onError = (error, stackTrace) {
|
||||||
|
// if (error is Exception) throw error;
|
||||||
|
// return errorHandler(error, stackTrace);
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return (Handler innerHandler) {
|
||||||
|
// return (request) {
|
||||||
|
// return Future.sync(() => requestHandler!(request)).then((response) {
|
||||||
|
// if (response != null) return response;
|
||||||
|
|
||||||
|
// return Future.sync(() => innerHandler(request))
|
||||||
|
// .then((response) => responseHandler!(response), onError: onError);
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// extension MiddlewareX on Middleware {
|
||||||
|
// Middleware addMiddleware(Middleware other) =>
|
||||||
|
// (Handler handler) => this(other(handler));
|
||||||
|
// Handler addHandler(Handler handler) => this(handler);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// typedef Handler = FutureOr<Response> Function(Request request);
|
||||||
|
|
||||||
|
// final headerMutator = createMiddleware(
|
||||||
|
// responseHandler: (response) {
|
||||||
|
// print(response.headers);
|
||||||
|
// return response;
|
||||||
|
// },);
|
||||||
|
|
||||||
|
// class Pipeline {
|
||||||
|
// const Pipeline();
|
||||||
|
|
||||||
|
// Pipeline addMiddleware(Middleware middleware) =>
|
||||||
|
// _Pipeline(middleware, addHandler);
|
||||||
|
|
||||||
|
// Handler addHandler(Handler handler) => handler;
|
||||||
|
|
||||||
|
// Middleware get middleware => addHandler;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// class _Pipeline extends Pipeline {
|
||||||
|
// final Middleware _middleware;
|
||||||
|
// final Middleware _parent;
|
||||||
|
|
||||||
|
// _Pipeline(this._middleware, this._parent);
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// Handler addHandler(Handler handler) => _parent(_middleware(handler));
|
||||||
|
// }
|
||||||
|
|
||||||
|
Future<void> main(List<String> args) async {
|
||||||
|
final UnsafeAuthMiddleware auth = UnsafeAuthMiddleware();
|
||||||
|
final Pipeline pipeline = Pipeline()
|
||||||
|
.addMiddleware(
|
||||||
|
UriPrefixMiddleware(
|
||||||
|
protocol: Protocols.http,
|
||||||
|
authority: 'localhost:80',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.addMiddleware(BodyToJsonMiddleware())
|
||||||
|
.addMiddleware(
|
||||||
|
UnsafeAuthMiddleware(
|
||||||
|
username: 'wyatt',
|
||||||
|
password: 'motdepasse',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.addMiddleware(SimpleLoggerMiddleware());
|
||||||
|
// .addMiddleware(
|
||||||
|
// RefreshTokenMiddleware(
|
||||||
|
// authorizationEndpoint: '/api/v1/account/test?action=authorize',
|
||||||
|
// tokenEndpoint: '/api/v1/account/test?action=refresh',
|
||||||
|
// accessTokenParser: (body) => body['access_token']! as String,
|
||||||
|
// refreshTokenParser: (body) => body['refresh_token']! as String,
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
|
||||||
|
print(pipeline);
|
||||||
|
final client = MiddlewareClient(pipeline: pipeline);
|
||||||
|
await client.post(
|
||||||
|
Uri.parse('/api/v1/account/test'),
|
||||||
|
body: <String, String>{
|
||||||
|
'email': 'test@test.fr',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
auth
|
||||||
|
..username = 'username'
|
||||||
|
..password = 'password';
|
||||||
|
await client.post(
|
||||||
|
Uri.parse('/api/v1/account/test'),
|
||||||
|
body: <String, String>{
|
||||||
|
'email': 'test@test.fr',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
|
||||||
// Please see the AUTHORS file for details.
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:http/http.dart';
|
|
||||||
import 'package:wyatt_http_client/src/authentication/interfaces/header_authentication_client.dart';
|
|
||||||
import 'package:wyatt_http_client/src/utils/authentication_methods.dart';
|
|
||||||
import 'package:wyatt_http_client/src/utils/header_keys.dart';
|
|
||||||
import 'package:wyatt_http_client/src/utils/http_status.dart';
|
|
||||||
import 'package:wyatt_http_client/src/utils/utils.dart';
|
|
||||||
|
|
||||||
class BasicAuthenticationClient extends HeaderAuthenticationClient {
|
|
||||||
final String username;
|
|
||||||
final String password;
|
|
||||||
final bool preemptive;
|
|
||||||
final String authenticationHeader;
|
|
||||||
|
|
||||||
BasicAuthenticationClient({
|
|
||||||
required this.username,
|
|
||||||
required this.password,
|
|
||||||
this.preemptive = true,
|
|
||||||
this.authenticationHeader = HeaderKeys.authorization,
|
|
||||||
BaseClient? inner,
|
|
||||||
}) : super(inner);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, String> modifyHeader(
|
|
||||||
Map<String, String> header, [
|
|
||||||
BaseRequest? request,
|
|
||||||
]) {
|
|
||||||
header[authenticationHeader] = '${AuthenticationMethods.basic} '
|
|
||||||
'${base64Encode(utf8.encode('$username:$password'))}';
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<StreamedResponse> send(BaseRequest request) async {
|
|
||||||
if (preemptive) {
|
|
||||||
// Just send request with modified header.
|
|
||||||
return super.send(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to send request without modified header,
|
|
||||||
// and if it fails, send it with.
|
|
||||||
final response = await inner.send(request);
|
|
||||||
if (response.statusCode == HttpStatus.unauthorized) {
|
|
||||||
// TODO(hpcl): save realm.
|
|
||||||
final newRequest = Utils.copyRequest(request);
|
|
||||||
return super.send(newRequest);
|
|
||||||
} else {
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
|
||||||
// Please see the AUTHORS file for details.
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import 'package:http/http.dart';
|
|
||||||
import 'package:wyatt_http_client/src/authentication/interfaces/header_authentication_client.dart';
|
|
||||||
import 'package:wyatt_http_client/src/utils/authentication_methods.dart';
|
|
||||||
import 'package:wyatt_http_client/src/utils/header_keys.dart';
|
|
||||||
import 'package:wyatt_http_client/src/utils/http_status.dart';
|
|
||||||
import 'package:wyatt_http_client/src/utils/utils.dart';
|
|
||||||
|
|
||||||
class BearerAuthenticationClient extends HeaderAuthenticationClient {
|
|
||||||
final String token;
|
|
||||||
final bool preemptive;
|
|
||||||
final String authenticationHeader;
|
|
||||||
final String authenticationMethod;
|
|
||||||
|
|
||||||
BearerAuthenticationClient({
|
|
||||||
required this.token,
|
|
||||||
this.preemptive = true,
|
|
||||||
this.authenticationHeader = HeaderKeys.authorization,
|
|
||||||
this.authenticationMethod = AuthenticationMethods.bearer,
|
|
||||||
BaseClient? inner,
|
|
||||||
}) : super(inner);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, String> modifyHeader(
|
|
||||||
Map<String, String> header, [
|
|
||||||
BaseRequest? request,
|
|
||||||
]) {
|
|
||||||
header[authenticationHeader] = '$authenticationMethod $token';
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<StreamedResponse> send(BaseRequest request) async {
|
|
||||||
if (preemptive) {
|
|
||||||
// Just send request with modified header.
|
|
||||||
return super.send(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to send request without modified header,
|
|
||||||
final response = await inner.send(request);
|
|
||||||
if (response.statusCode == HttpStatus.unauthorized) {
|
|
||||||
final newRequest = Utils.copyRequest(request);
|
|
||||||
return super.send(newRequest);
|
|
||||||
} else {
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
|
||||||
// Please see the AUTHORS file for details.
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import 'package:http/http.dart';
|
|
||||||
import 'package:wyatt_http_client/src/authentication/interfaces/header_authentication_client.dart';
|
|
||||||
import 'package:wyatt_http_client/src/utils/digest_auth.dart';
|
|
||||||
import 'package:wyatt_http_client/src/utils/header_keys.dart';
|
|
||||||
import 'package:wyatt_http_client/src/utils/http_status.dart';
|
|
||||||
import 'package:wyatt_http_client/src/utils/utils.dart';
|
|
||||||
|
|
||||||
class DigestAuthenticationClient extends HeaderAuthenticationClient {
|
|
||||||
final String username;
|
|
||||||
final String password;
|
|
||||||
final DigestAuth _digestAuth;
|
|
||||||
final String authenticationHeader;
|
|
||||||
final String wwwAuthenticateHeader;
|
|
||||||
|
|
||||||
DigestAuthenticationClient({
|
|
||||||
required this.username,
|
|
||||||
required this.password,
|
|
||||||
this.authenticationHeader = HeaderKeys.authorization,
|
|
||||||
this.wwwAuthenticateHeader = HeaderKeys.wwwAuthenticate,
|
|
||||||
BaseClient? inner,
|
|
||||||
}) : _digestAuth = DigestAuth(username, password),
|
|
||||||
super(inner);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, String> modifyHeader(
|
|
||||||
Map<String, String> header, [
|
|
||||||
BaseRequest? request,
|
|
||||||
]) {
|
|
||||||
if ((_digestAuth.isReady()) && request != null) {
|
|
||||||
header[authenticationHeader] = _digestAuth.getAuthString(
|
|
||||||
request.method,
|
|
||||||
request.url,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<StreamedResponse> send(BaseRequest request) async {
|
|
||||||
// Check if our DigestAuth is ready.
|
|
||||||
if (_digestAuth.isReady()) {
|
|
||||||
// If it is, try to send the request with the modified header.
|
|
||||||
return super.send(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it isn't, try to send the request without the modified header.
|
|
||||||
final response = await inner.send(request);
|
|
||||||
|
|
||||||
if (response.statusCode == HttpStatus.unauthorized) {
|
|
||||||
final newRequest = Utils.copyRequest(request);
|
|
||||||
final authInfo =
|
|
||||||
response.headers[HeaderKeys.wwwAuthenticate.toLowerCase()];
|
|
||||||
_digestAuth.initFromAuthenticateHeader(authInfo);
|
|
||||||
return super.send(newRequest);
|
|
||||||
} else {
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
|
||||||
// Please see the AUTHORS file for details.
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:http/http.dart';
|
|
||||||
import 'package:wyatt_http_client/src/authentication/interfaces/oauth2_client.dart';
|
|
||||||
import 'package:wyatt_http_client/src/utils/authentication_methods.dart';
|
|
||||||
import 'package:wyatt_http_client/src/utils/header_keys.dart';
|
|
||||||
import 'package:wyatt_http_client/src/utils/http_status.dart';
|
|
||||||
import 'package:wyatt_http_client/src/utils/utils.dart';
|
|
||||||
|
|
||||||
class RefreshTokenClient extends Oauth2Client {
|
|
||||||
final String authorizationEndpoint;
|
|
||||||
final String tokenEndpoint;
|
|
||||||
|
|
||||||
String? accessToken;
|
|
||||||
final TokenParser accessTokenParser;
|
|
||||||
String? refreshToken;
|
|
||||||
final TokenParser refreshTokenParser;
|
|
||||||
|
|
||||||
final String authenticationHeader;
|
|
||||||
final String authenticationMethod;
|
|
||||||
|
|
||||||
RefreshTokenClient({
|
|
||||||
required this.authorizationEndpoint,
|
|
||||||
required this.tokenEndpoint,
|
|
||||||
required this.accessTokenParser,
|
|
||||||
required this.refreshTokenParser,
|
|
||||||
this.authenticationHeader = HeaderKeys.authorization,
|
|
||||||
this.authenticationMethod = AuthenticationMethods.bearer,
|
|
||||||
BaseClient? inner,
|
|
||||||
}) : super(inner);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, String> modifyHeader(
|
|
||||||
Map<String, String> header, [
|
|
||||||
BaseRequest? request,
|
|
||||||
]) {
|
|
||||||
if (accessToken != null && request != null) {
|
|
||||||
header[authenticationHeader] = '$authenticationMethod $accessToken';
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
return header;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> authorize(
|
|
||||||
Map<String, dynamic> body, {
|
|
||||||
Map<String, String>? headers,
|
|
||||||
}) async {
|
|
||||||
final response = await inner.post(
|
|
||||||
Uri.parse(authorizationEndpoint),
|
|
||||||
body: jsonEncode(body),
|
|
||||||
headers: headers,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == HttpStatus.ok) {
|
|
||||||
final body = json.decode(response.body) as Map<String, dynamic>;
|
|
||||||
final accessToken = accessTokenParser(body);
|
|
||||||
final refreshToken = refreshTokenParser(body);
|
|
||||||
|
|
||||||
if (accessToken.isNotEmpty) {
|
|
||||||
this.accessToken = accessToken;
|
|
||||||
}
|
|
||||||
if (refreshToken.isNotEmpty) {
|
|
||||||
this.refreshToken = refreshToken;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> refresh() async {
|
|
||||||
if (refreshToken != null) {
|
|
||||||
final Map<String, String> header = {
|
|
||||||
authenticationHeader: '$authenticationHeader $refreshToken',
|
|
||||||
};
|
|
||||||
|
|
||||||
final response = await inner.get(
|
|
||||||
Uri.parse(tokenEndpoint),
|
|
||||||
headers: header,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == HttpStatus.ok) {
|
|
||||||
final body = json.decode(response.body) as Map<String, dynamic>;
|
|
||||||
accessToken = accessTokenParser(body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<StreamedResponse> send(BaseRequest request) async {
|
|
||||||
final response = await super.send(request);
|
|
||||||
|
|
||||||
if (response.statusCode == HttpStatus.unauthorized) {
|
|
||||||
await refresh();
|
|
||||||
return super.send(Utils.copyRequest(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+18
-17
@@ -14,24 +14,25 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'package:http/http.dart';
|
import 'package:wyatt_http_client/src/models/middleware_context.dart';
|
||||||
import 'package:wyatt_http_client/src/rest_client.dart';
|
import 'package:wyatt_http_client/src/models/middleware_request.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_response.dart';
|
||||||
|
|
||||||
abstract class AuthenticationClient extends BaseClient {
|
abstract class Middleware {
|
||||||
final BaseClient _inner;
|
Middleware();
|
||||||
|
String getName();
|
||||||
|
}
|
||||||
|
|
||||||
BaseClient get inner => _inner;
|
mixin OnRequestMiddleware {
|
||||||
|
Future<MiddlewareRequest> onRequest(
|
||||||
|
MiddlewareContext context,
|
||||||
|
MiddlewareRequest request,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
AuthenticationClient(BaseClient? inner) : _inner = inner ?? RestClient();
|
mixin OnResponseMiddleware {
|
||||||
|
Future<MiddlewareResponse> onResponse(
|
||||||
@override
|
MiddlewareContext context,
|
||||||
Future<StreamedResponse> send(BaseRequest request) {
|
MiddlewareResponse response,
|
||||||
return _inner.send(request);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void close() {
|
|
||||||
_inner.close();
|
|
||||||
return super.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_context.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_request.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_response.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/unfreezed_request.dart';
|
||||||
|
import 'package:wyatt_http_client/src/pipeline.dart';
|
||||||
|
import 'package:wyatt_http_client/src/utils/http_methods.dart';
|
||||||
|
|
||||||
|
class MiddlewareClient extends BaseClient {
|
||||||
|
final Client inner;
|
||||||
|
final Pipeline pipeline;
|
||||||
|
|
||||||
|
MiddlewareClient({
|
||||||
|
Pipeline? pipeline,
|
||||||
|
Client? inner,
|
||||||
|
}) : pipeline = pipeline ?? Pipeline(),
|
||||||
|
inner = inner ?? Client() {
|
||||||
|
print('Using Pipeline:\n$pipeline');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Response> head(Uri url, {Map<String, String>? headers}) =>
|
||||||
|
_sendUnstreamed(HttpMethods.head.method, url, headers);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Response> get(Uri url, {Map<String, String>? headers}) =>
|
||||||
|
_sendUnstreamed(HttpMethods.get.method, url, headers);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Response> post(
|
||||||
|
Uri url, {
|
||||||
|
Map<String, String>? headers,
|
||||||
|
Object? body,
|
||||||
|
Encoding? encoding,
|
||||||
|
}) =>
|
||||||
|
_sendUnstreamed(HttpMethods.post.method, url, headers, body, encoding);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Response> put(
|
||||||
|
Uri url, {
|
||||||
|
Map<String, String>? headers,
|
||||||
|
Object? body,
|
||||||
|
Encoding? encoding,
|
||||||
|
}) =>
|
||||||
|
_sendUnstreamed(HttpMethods.put.method, url, headers, body, encoding);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Response> patch(
|
||||||
|
Uri url, {
|
||||||
|
Map<String, String>? headers,
|
||||||
|
Object? body,
|
||||||
|
Encoding? encoding,
|
||||||
|
}) =>
|
||||||
|
_sendUnstreamed(HttpMethods.patch.method, url, headers, body, encoding);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Response> delete(
|
||||||
|
Uri url, {
|
||||||
|
Map<String, String>? headers,
|
||||||
|
Object? body,
|
||||||
|
Encoding? encoding,
|
||||||
|
}) =>
|
||||||
|
_sendUnstreamed(HttpMethods.delete.method, url, headers, body, encoding);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<StreamedResponse> send(BaseRequest request) {
|
||||||
|
return inner.send(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Response> _sendUnstreamed(
|
||||||
|
String method,
|
||||||
|
Uri url,
|
||||||
|
Map<String, String>? headers, [
|
||||||
|
Object? body,
|
||||||
|
Encoding? encoding,
|
||||||
|
]) async {
|
||||||
|
final originalRequest = MiddlewareRequest(
|
||||||
|
unfreezedRequest: UnfreezedRequest(
|
||||||
|
method: method,
|
||||||
|
url: url,
|
||||||
|
headers: headers,
|
||||||
|
body: body,
|
||||||
|
encoding: encoding,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final requestContext = MiddlewareContext(
|
||||||
|
pipeline: pipeline,
|
||||||
|
client: this,
|
||||||
|
originalRequest: originalRequest,
|
||||||
|
);
|
||||||
|
|
||||||
|
final modifiedRequest = await pipeline.onRequest(
|
||||||
|
requestContext,
|
||||||
|
originalRequest.copyWith(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final originalResponse = MiddlewareResponse(
|
||||||
|
httpResponse: await Response.fromStream(
|
||||||
|
await send(modifiedRequest.request),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final responseContext =
|
||||||
|
requestContext.copyWith(originalResponse: originalResponse);
|
||||||
|
|
||||||
|
final modifiedResponse =
|
||||||
|
await pipeline.onResponse(responseContext, originalResponse.copyWith());
|
||||||
|
|
||||||
|
return modifiedResponse.httpResponse as Response;
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-9
@@ -1,22 +1,16 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License as published by
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// any later version.
|
// any later version.
|
||||||
//
|
//
|
||||||
// This program is distributed in the hope that it will be useful,
|
// This program is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
part of 'custom_form_cubit.dart';
|
|
||||||
|
|
||||||
@immutable
|
|
||||||
abstract class CustomFormState {}
|
|
||||||
|
|
||||||
class CustomFormInitial extends CustomFormState {}
|
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:wyatt_http_client/src/middleware.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_context.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_request.dart';
|
||||||
|
import 'package:wyatt_http_client/src/utils/authentication_methods.dart';
|
||||||
|
import 'package:wyatt_http_client/src/utils/header_keys.dart';
|
||||||
|
|
||||||
|
class BasicAuthMiddleware with OnRequestMiddleware implements Middleware {
|
||||||
|
String? username;
|
||||||
|
String? password;
|
||||||
|
final String authenticationHeader;
|
||||||
|
|
||||||
|
BasicAuthMiddleware({
|
||||||
|
this.username,
|
||||||
|
this.password,
|
||||||
|
this.authenticationHeader = HeaderKeys.authorization,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getName() => 'BasicAuth';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MiddlewareRequest> onRequest(
|
||||||
|
MiddlewareContext context,
|
||||||
|
MiddlewareRequest request,
|
||||||
|
) async {
|
||||||
|
if (username == null || password == null) {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
print(
|
||||||
|
'${getName()}::OnRequest\n'
|
||||||
|
'>> Basic: ${base64Encode(utf8.encode('$username:$password'))}',
|
||||||
|
);
|
||||||
|
final mutation = {
|
||||||
|
authenticationHeader: '${AuthenticationMethods.basic} '
|
||||||
|
'${base64Encode(utf8.encode('$username:$password'))}',
|
||||||
|
};
|
||||||
|
final Map<String, String> headers = request.headers..addAll(mutation);
|
||||||
|
request.modifyRequest(request.unfreezedRequest.copyWith(headers: headers));
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:wyatt_http_client/src/middleware.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_context.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_request.dart';
|
||||||
|
|
||||||
|
class BodyToJsonMiddleware with OnRequestMiddleware implements Middleware {
|
||||||
|
@override
|
||||||
|
String getName() => 'BodyToJson';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MiddlewareRequest> onRequest(
|
||||||
|
MiddlewareContext context,
|
||||||
|
MiddlewareRequest request,
|
||||||
|
) async {
|
||||||
|
print(
|
||||||
|
'${getName()}::OnRequest\n'
|
||||||
|
'>> Transforms body in json if Map then update '
|
||||||
|
'headers with right content-type',
|
||||||
|
);
|
||||||
|
final mutation = {
|
||||||
|
'content-type': 'application/json; charset=utf-8',
|
||||||
|
};
|
||||||
|
if (request.body is Map) {
|
||||||
|
final Map<String, String> headers = request.headers..addAll(mutation);
|
||||||
|
request.modifyRequest(
|
||||||
|
request.unfreezedRequest
|
||||||
|
.copyWith(headers: headers, body: jsonEncode(request.body)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-11
@@ -14,17 +14,9 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'package:http/http.dart';
|
import 'package:wyatt_http_client/src/middleware.dart';
|
||||||
import 'package:wyatt_http_client/src/authentication/interfaces/authentication_client.dart';
|
|
||||||
|
|
||||||
abstract class UrlAuthenticationClient extends AuthenticationClient {
|
|
||||||
UrlAuthenticationClient(super.inner);
|
|
||||||
|
|
||||||
BaseRequest modifyRequest(BaseRequest request) => request;
|
|
||||||
|
|
||||||
|
class DefaultMiddleware implements Middleware {
|
||||||
@override
|
@override
|
||||||
Future<StreamedResponse> send(BaseRequest request) {
|
String getName() => 'DefaultMiddleware';
|
||||||
final newRequest = modifyRequest(request);
|
|
||||||
return super.send(newRequest);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'package:wyatt_http_client/src/middleware.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_context.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_request.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_response.dart';
|
||||||
|
import 'package:wyatt_http_client/src/utils/digest_auth.dart';
|
||||||
|
import 'package:wyatt_http_client/src/utils/header_keys.dart';
|
||||||
|
import 'package:wyatt_http_client/src/utils/http_status.dart';
|
||||||
|
|
||||||
|
class DigestAuthMiddleware
|
||||||
|
with OnRequestMiddleware, OnResponseMiddleware
|
||||||
|
implements Middleware {
|
||||||
|
final String username;
|
||||||
|
final String password;
|
||||||
|
final DigestAuth _digestAuth;
|
||||||
|
final String authenticationHeader;
|
||||||
|
final String wwwAuthenticateHeader;
|
||||||
|
final HttpStatus unauthorized;
|
||||||
|
|
||||||
|
DigestAuthMiddleware({
|
||||||
|
required this.username,
|
||||||
|
required this.password,
|
||||||
|
this.authenticationHeader = HeaderKeys.authorization,
|
||||||
|
this.wwwAuthenticateHeader = HeaderKeys.wwwAuthenticate,
|
||||||
|
this.unauthorized = HttpStatus.unauthorized,
|
||||||
|
}) : _digestAuth = DigestAuth(username, password);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getName() => 'DigestAuth';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MiddlewareRequest> onRequest(
|
||||||
|
MiddlewareContext context,
|
||||||
|
MiddlewareRequest request,
|
||||||
|
) async {
|
||||||
|
print(
|
||||||
|
'${getName()}::OnRequest\n'
|
||||||
|
'>> Digest ready: ${_digestAuth.isReady()}',
|
||||||
|
);
|
||||||
|
if (_digestAuth.isReady()) {
|
||||||
|
final mutation = {
|
||||||
|
authenticationHeader: _digestAuth.getAuthString(
|
||||||
|
request.method,
|
||||||
|
request.url,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
final Map<String, String> headers = request.headers..addAll(mutation);
|
||||||
|
request
|
||||||
|
.modifyRequest(request.unfreezedRequest.copyWith(headers: headers));
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MiddlewareResponse> onResponse(
|
||||||
|
MiddlewareContext context,
|
||||||
|
MiddlewareResponse response,
|
||||||
|
) async {
|
||||||
|
if (response.status == unauthorized) {
|
||||||
|
final authInfo =
|
||||||
|
response.headers[HeaderKeys.wwwAuthenticate.toLowerCase()];
|
||||||
|
_digestAuth.initFromAuthenticateHeader(authInfo);
|
||||||
|
|
||||||
|
final MiddlewareRequest? newRequest = context.lastRequest?.copyWith();
|
||||||
|
|
||||||
|
if (newRequest != null) {
|
||||||
|
final newResponse = await context.client.send(newRequest.request);
|
||||||
|
return MiddlewareResponse(httpResponse: newResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print(
|
||||||
|
'${getName()}::OnResponse\n'
|
||||||
|
'>> Digest ready: ${_digestAuth.isReady()}',
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export 'access_token_auth_middleware.dart';
|
||||||
|
export 'basic_auth_middleware.dart';
|
||||||
|
export 'body_to_json_middleware.dart';
|
||||||
|
export 'default_middleware.dart';
|
||||||
|
export 'digest_auth_middleware.dart';
|
||||||
|
export 'refresh_token_auth_middleware.dart';
|
||||||
|
export 'simple_logger_middleware.dart';
|
||||||
|
export 'unsafe_auth_middleware.dart';
|
||||||
|
export 'uri_prefix_middleware.dart';
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:wyatt_http_client/src/middleware.dart';
|
||||||
|
import 'package:wyatt_http_client/src/middleware_client.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_context.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_request.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_response.dart';
|
||||||
|
import 'package:wyatt_http_client/src/utils/authentication_methods.dart';
|
||||||
|
import 'package:wyatt_http_client/src/utils/delay.dart';
|
||||||
|
import 'package:wyatt_http_client/src/utils/header_keys.dart';
|
||||||
|
import 'package:wyatt_http_client/src/utils/http_status.dart';
|
||||||
|
|
||||||
|
typedef TokenParser = String Function(Map<String, dynamic>);
|
||||||
|
|
||||||
|
class RefreshTokenAuthMiddleware
|
||||||
|
with OnRequestMiddleware, OnResponseMiddleware
|
||||||
|
implements Middleware {
|
||||||
|
final String authorizationEndpoint;
|
||||||
|
final String tokenEndpoint;
|
||||||
|
|
||||||
|
String? accessToken;
|
||||||
|
final TokenParser accessTokenParser;
|
||||||
|
String? refreshToken;
|
||||||
|
final TokenParser refreshTokenParser;
|
||||||
|
|
||||||
|
final String authenticationHeader;
|
||||||
|
final String authenticationMethod;
|
||||||
|
final HttpStatus unauthorized;
|
||||||
|
final int maxAttempts;
|
||||||
|
|
||||||
|
RefreshTokenAuthMiddleware({
|
||||||
|
required this.authorizationEndpoint,
|
||||||
|
required this.tokenEndpoint,
|
||||||
|
required this.accessTokenParser,
|
||||||
|
required this.refreshTokenParser,
|
||||||
|
this.authenticationHeader = HeaderKeys.authorization,
|
||||||
|
this.authenticationMethod = AuthenticationMethods.bearer,
|
||||||
|
this.unauthorized = HttpStatus.unauthorized,
|
||||||
|
this.maxAttempts = 8,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getName() => 'RefreshToken';
|
||||||
|
|
||||||
|
Future<MiddlewareRequest?> refresh(MiddlewareContext context) async {
|
||||||
|
final subPipeline = context.pipeline.sub(this);
|
||||||
|
final httpClient = MiddlewareClient(
|
||||||
|
pipeline: subPipeline,
|
||||||
|
inner: context.client.inner,
|
||||||
|
);
|
||||||
|
final headers = {
|
||||||
|
authenticationHeader: '$authenticationMethod $refreshToken',
|
||||||
|
};
|
||||||
|
final response = MiddlewareResponse(
|
||||||
|
httpResponse: await httpClient.get(
|
||||||
|
Uri.parse(tokenEndpoint),
|
||||||
|
headers: headers,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (response.status.isSuccess()) {
|
||||||
|
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||||
|
accessToken = accessTokenParser(body);
|
||||||
|
|
||||||
|
// Then modify current request with accessToken
|
||||||
|
final mutation = {
|
||||||
|
authenticationHeader: '$authenticationMethod $accessToken',
|
||||||
|
};
|
||||||
|
final Map<String, String>? headers = context.lastRequest?.headers
|
||||||
|
?..addAll(mutation);
|
||||||
|
final newRequest = context.lastRequest?.copyWith(
|
||||||
|
unfreezedRequest:
|
||||||
|
context.lastRequest?.unfreezedRequest.copyWith(headers: headers),
|
||||||
|
);
|
||||||
|
|
||||||
|
return newRequest;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<MiddlewareRequest?> retry(MiddlewareContext context) async {
|
||||||
|
// Retry
|
||||||
|
int attempt = 1;
|
||||||
|
while (attempt <= maxAttempts) {
|
||||||
|
// Delayed before retry
|
||||||
|
await Future<void>.delayed(Delay.getRetryDelay(attempt));
|
||||||
|
|
||||||
|
final newRequest = await refresh(context);
|
||||||
|
if (newRequest != null) {
|
||||||
|
return newRequest;
|
||||||
|
}
|
||||||
|
attempt++;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MiddlewareRequest> onRequest(
|
||||||
|
MiddlewareContext context,
|
||||||
|
MiddlewareRequest request,
|
||||||
|
) async {
|
||||||
|
print(
|
||||||
|
'${getName()}::OnRequest\n'
|
||||||
|
'>> accessToken: $accessToken\n'
|
||||||
|
'>> refreshToken: $refreshToken',
|
||||||
|
);
|
||||||
|
// Check if it is authorization
|
||||||
|
if (context.originalRequest?.url == Uri.parse(authorizationEndpoint)) {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
// Check if it is refresh
|
||||||
|
if (context.originalRequest?.url == Uri.parse(tokenEndpoint)) {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
// If AccessToken not null then return request with authorization header
|
||||||
|
if (accessToken != null) {
|
||||||
|
final mutation = {
|
||||||
|
authenticationHeader: '$authenticationMethod $accessToken',
|
||||||
|
};
|
||||||
|
final Map<String, String> headers = request.headers..addAll(mutation);
|
||||||
|
request
|
||||||
|
.modifyRequest(request.unfreezedRequest.copyWith(headers: headers));
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
// If AccessToken is null BUT there is a refreshToken, then try refreshing
|
||||||
|
if (refreshToken != null) {
|
||||||
|
MiddlewareRequest? newRequest = await refresh(context);
|
||||||
|
newRequest ??= await retry(context);
|
||||||
|
return newRequest ?? request;
|
||||||
|
}
|
||||||
|
// Pass
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MiddlewareResponse> onResponse(
|
||||||
|
MiddlewareContext context,
|
||||||
|
MiddlewareResponse response,
|
||||||
|
) async {
|
||||||
|
// Check if it is authorization
|
||||||
|
if (context.originalRequest?.url == Uri.parse(authorizationEndpoint)) {
|
||||||
|
// If success, then update tokens
|
||||||
|
if (response.status.isSuccess()) {
|
||||||
|
final body = jsonDecode(response.body) as Map<String, dynamic>;
|
||||||
|
final accessToken = accessTokenParser(body);
|
||||||
|
final refreshToken = refreshTokenParser(body);
|
||||||
|
|
||||||
|
if (accessToken.isNotEmpty) {
|
||||||
|
this.accessToken = accessToken;
|
||||||
|
}
|
||||||
|
if (refreshToken.isNotEmpty) {
|
||||||
|
this.refreshToken = refreshToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print(
|
||||||
|
'${getName()}::OnResponse\n'
|
||||||
|
'>> accessToken: $accessToken\n'
|
||||||
|
'>> refreshToken: $refreshToken',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status == unauthorized) {
|
||||||
|
// Refresh
|
||||||
|
MiddlewareRequest? newRequest = await refresh(context);
|
||||||
|
newRequest ??= await retry(context);
|
||||||
|
|
||||||
|
if (newRequest != null) {
|
||||||
|
return response.copyWith(
|
||||||
|
httpResponse: await context.client.send(newRequest.request),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'package:wyatt_http_client/src/middleware.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_context.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_request.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_response.dart';
|
||||||
|
|
||||||
|
class SimpleLoggerMiddleware
|
||||||
|
with OnRequestMiddleware, OnResponseMiddleware
|
||||||
|
implements Middleware {
|
||||||
|
@override
|
||||||
|
String getName() => 'SimpleLogger';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MiddlewareRequest> onRequest(
|
||||||
|
MiddlewareContext context,
|
||||||
|
MiddlewareRequest request,
|
||||||
|
) async {
|
||||||
|
print(
|
||||||
|
'${getName()}::OnRequest\n'
|
||||||
|
'>> ${request.method} ${request.url}\n'
|
||||||
|
'>> Headers: ${request.headers}\n>> Body: ${request.encodedBody}',
|
||||||
|
);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MiddlewareResponse> onResponse(
|
||||||
|
MiddlewareContext context,
|
||||||
|
MiddlewareResponse response,
|
||||||
|
) async {
|
||||||
|
print(
|
||||||
|
'${getName()}::OnResponse\n'
|
||||||
|
'>> Status: ${response.status.name.toUpperCase()}\n'
|
||||||
|
'>> Length: ${response.contentLength ?? '0'} bytes',
|
||||||
|
// '>> Body: ${response.body}',
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
-14
@@ -14,30 +14,43 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'package:http/http.dart';
|
import 'package:wyatt_http_client/src/middleware.dart';
|
||||||
import 'package:wyatt_http_client/src/authentication/interfaces/url_authentication_client.dart';
|
import 'package:wyatt_http_client/src/models/middleware_context.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_request.dart';
|
||||||
import 'package:wyatt_http_client/src/utils/convert.dart';
|
import 'package:wyatt_http_client/src/utils/convert.dart';
|
||||||
import 'package:wyatt_http_client/src/utils/utils.dart';
|
|
||||||
|
|
||||||
class UnsafeAuthenticationClient extends UrlAuthenticationClient {
|
class UnsafeAuthMiddleware with OnRequestMiddleware implements Middleware {
|
||||||
final String username;
|
String? username;
|
||||||
final String password;
|
String? password;
|
||||||
|
|
||||||
final String usernameField;
|
final String usernameField;
|
||||||
final String passwordField;
|
final String passwordField;
|
||||||
|
|
||||||
UnsafeAuthenticationClient({
|
UnsafeAuthMiddleware({
|
||||||
required this.username,
|
this.username,
|
||||||
required this.password,
|
this.password,
|
||||||
this.usernameField = 'username',
|
this.usernameField = 'username',
|
||||||
this.passwordField = 'password',
|
this.passwordField = 'password',
|
||||||
BaseClient? inner,
|
});
|
||||||
}) : super(inner);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BaseRequest modifyRequest(BaseRequest request) {
|
String getName() => 'UnsafeAuth';
|
||||||
final url =
|
|
||||||
|
@override
|
||||||
|
Future<MiddlewareRequest> onRequest(
|
||||||
|
MiddlewareContext context,
|
||||||
|
MiddlewareRequest request,
|
||||||
|
) async {
|
||||||
|
if (username == null || password == null) {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
final Uri uri =
|
||||||
request.url + '?$usernameField=$username&$passwordField=$password';
|
request.url + '?$usernameField=$username&$passwordField=$password';
|
||||||
return Utils.copyRequestWith(request, url: url);
|
print(
|
||||||
|
'${getName()}::OnRequest\n'
|
||||||
|
'>> Append: ?$usernameField=$username&$passwordField=$password',
|
||||||
|
);
|
||||||
|
request.modifyRequest(request.unfreezedRequest.copyWith(url: uri));
|
||||||
|
return request;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+22
-12
@@ -14,25 +14,35 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'package:http/http.dart';
|
import 'package:wyatt_http_client/src/middleware.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_context.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_request.dart';
|
||||||
import 'package:wyatt_http_client/src/utils/protocols.dart';
|
import 'package:wyatt_http_client/src/utils/protocols.dart';
|
||||||
import 'package:wyatt_http_client/src/utils/utils.dart';
|
|
||||||
|
|
||||||
class RestClient extends BaseClient {
|
class UriPrefixMiddleware with OnRequestMiddleware implements Middleware {
|
||||||
final Protocols protocol;
|
final Protocols protocol;
|
||||||
final String? authority;
|
final String? authority;
|
||||||
|
|
||||||
final Client _inner;
|
UriPrefixMiddleware({
|
||||||
|
required this.protocol,
|
||||||
RestClient({
|
required this.authority,
|
||||||
this.protocol = Protocols.https,
|
});
|
||||||
this.authority = '',
|
|
||||||
Client? inner,
|
|
||||||
}) : _inner = inner ?? Client();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<StreamedResponse> send(BaseRequest request) {
|
String getName() => 'UriPrefix';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MiddlewareRequest> onRequest(
|
||||||
|
MiddlewareContext context,
|
||||||
|
MiddlewareRequest request,
|
||||||
|
) async {
|
||||||
final Uri uri = Uri.parse('${protocol.scheme}$authority${request.url}');
|
final Uri uri = Uri.parse('${protocol.scheme}$authority${request.url}');
|
||||||
return _inner.send(Utils.copyRequestWith(request, url: uri));
|
print(
|
||||||
|
'${getName()}::OnRequest\n'
|
||||||
|
'>> From: ${request.url}\n'
|
||||||
|
'>> To: $uri',
|
||||||
|
);
|
||||||
|
request.modifyRequest(request.unfreezedRequest.copyWith(url: uri));
|
||||||
|
return request;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'package:wyatt_http_client/src/middleware_client.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_request.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_response.dart';
|
||||||
|
import 'package:wyatt_http_client/src/pipeline.dart';
|
||||||
|
|
||||||
|
class MiddlewareContext {
|
||||||
|
Pipeline pipeline;
|
||||||
|
MiddlewareClient client;
|
||||||
|
MiddlewareRequest? originalRequest;
|
||||||
|
MiddlewareRequest? lastRequest;
|
||||||
|
MiddlewareResponse? originalResponse;
|
||||||
|
MiddlewareResponse? lastResponse;
|
||||||
|
|
||||||
|
MiddlewareContext({
|
||||||
|
required this.pipeline,
|
||||||
|
required this.client,
|
||||||
|
this.originalRequest,
|
||||||
|
this.lastRequest,
|
||||||
|
this.originalResponse,
|
||||||
|
this.lastResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
MiddlewareContext copyWith({
|
||||||
|
Pipeline? pipeline,
|
||||||
|
MiddlewareClient? client,
|
||||||
|
MiddlewareRequest? originalRequest,
|
||||||
|
MiddlewareRequest? lastRequest,
|
||||||
|
MiddlewareResponse? originalResponse,
|
||||||
|
MiddlewareResponse? lastResponse,
|
||||||
|
}) {
|
||||||
|
return MiddlewareContext(
|
||||||
|
pipeline: pipeline ?? this.pipeline,
|
||||||
|
client: client ?? this.client,
|
||||||
|
originalRequest: originalRequest ?? this.originalRequest,
|
||||||
|
lastRequest: lastRequest ?? this.lastRequest,
|
||||||
|
originalResponse: originalResponse ?? this.originalResponse,
|
||||||
|
lastResponse: lastResponse ?? this.lastResponse,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'MiddlewareContext(pipeline: $pipeline, client: $client, originalRequest: $originalRequest, lastRequest: $lastRequest, originalResponse: $originalResponse, lastResponse: $lastResponse)';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/unfreezed_request.dart';
|
||||||
|
import 'package:wyatt_http_client/src/utils/convert.dart';
|
||||||
|
import 'package:wyatt_http_client/src/utils/request_utils.dart';
|
||||||
|
|
||||||
|
class MiddlewareRequest {
|
||||||
|
UnfreezedRequest unfreezedRequest;
|
||||||
|
Request _httpRequest;
|
||||||
|
|
||||||
|
Request get request => _httpRequest;
|
||||||
|
|
||||||
|
// Proxy
|
||||||
|
String get method => _httpRequest.method;
|
||||||
|
Uri get url => _httpRequest.url;
|
||||||
|
Map<String, String> get headers => _httpRequest.headers;
|
||||||
|
Encoding get encoding => _httpRequest.encoding;
|
||||||
|
String get encodedBody => _httpRequest.body;
|
||||||
|
Object? get body => unfreezedRequest.body;
|
||||||
|
|
||||||
|
MiddlewareRequest({
|
||||||
|
required this.unfreezedRequest,
|
||||||
|
}) : _httpRequest = Request(unfreezedRequest.method, unfreezedRequest.url);
|
||||||
|
|
||||||
|
MiddlewareRequest copyWith({
|
||||||
|
UnfreezedRequest? unfreezedRequest,
|
||||||
|
}) {
|
||||||
|
return MiddlewareRequest(
|
||||||
|
unfreezedRequest: unfreezedRequest ?? this.unfreezedRequest,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void modifyRequest(UnfreezedRequest unfreezedRequest) {
|
||||||
|
String? _body;
|
||||||
|
if (unfreezedRequest.body != null) {
|
||||||
|
final body = unfreezedRequest.body;
|
||||||
|
if (body is String) {
|
||||||
|
_body = body;
|
||||||
|
} else if (body is List) {
|
||||||
|
_body = String.fromCharCodes(body.cast<int>());
|
||||||
|
} else if (body is Map) {
|
||||||
|
_body = Convert.mapToQuery(body.cast<String, String>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_httpRequest = RequestUtils.copyRequestWith(
|
||||||
|
_httpRequest,
|
||||||
|
method: unfreezedRequest.method,
|
||||||
|
url: unfreezedRequest.url,
|
||||||
|
headers: unfreezedRequest.headers,
|
||||||
|
body: _body,
|
||||||
|
) as Request;
|
||||||
|
if (unfreezedRequest.encoding != null) {
|
||||||
|
_httpRequest.encoding = unfreezedRequest.encoding!;
|
||||||
|
}
|
||||||
|
this.unfreezedRequest = unfreezedRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply() {
|
||||||
|
modifyRequest(unfreezedRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'MiddlewareRequest(unfreezedRequest: $unfreezedRequest)';
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:wyatt_http_client/src/utils/http_status.dart';
|
||||||
|
|
||||||
|
class MiddlewareResponse {
|
||||||
|
BaseResponse httpResponse;
|
||||||
|
|
||||||
|
// Proxy
|
||||||
|
int get statusCode => httpResponse.statusCode;
|
||||||
|
HttpStatus get status => HttpStatus.from(statusCode);
|
||||||
|
String get body {
|
||||||
|
if (httpResponse is Response) {
|
||||||
|
return (httpResponse as Response).body;
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int? get contentLength => httpResponse.contentLength;
|
||||||
|
Map<String, String> get headers => httpResponse.headers;
|
||||||
|
|
||||||
|
MiddlewareResponse({
|
||||||
|
required this.httpResponse,
|
||||||
|
});
|
||||||
|
|
||||||
|
MiddlewareResponse copyWith({
|
||||||
|
BaseResponse? httpResponse,
|
||||||
|
}) {
|
||||||
|
return MiddlewareResponse(
|
||||||
|
httpResponse: httpResponse ?? this.httpResponse,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'MiddlewareResponse(httpResponse: $httpResponse)';
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export 'middleware_context.dart';
|
||||||
|
export 'middleware_request.dart';
|
||||||
|
export 'middleware_response.dart';
|
||||||
|
export 'unfreezed_request.dart';
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class UnfreezedRequest {
|
||||||
|
final String method;
|
||||||
|
final Uri url;
|
||||||
|
final Map<String, String>? headers;
|
||||||
|
final Object? body;
|
||||||
|
final Encoding? encoding;
|
||||||
|
|
||||||
|
UnfreezedRequest({
|
||||||
|
required this.method,
|
||||||
|
required this.url,
|
||||||
|
this.headers,
|
||||||
|
this.body,
|
||||||
|
this.encoding,
|
||||||
|
});
|
||||||
|
|
||||||
|
UnfreezedRequest copyWith({
|
||||||
|
String? method,
|
||||||
|
Uri? url,
|
||||||
|
Map<String, String>? headers,
|
||||||
|
Object? body,
|
||||||
|
Encoding? encoding,
|
||||||
|
}) {
|
||||||
|
return UnfreezedRequest(
|
||||||
|
method: method ?? this.method,
|
||||||
|
url: url ?? this.url,
|
||||||
|
headers: headers ?? this.headers,
|
||||||
|
body: body ?? this.body,
|
||||||
|
encoding: encoding ?? this.encoding,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'UnfreezedRequest(method: $method, url: $url, headers: '
|
||||||
|
'$headers, body: $body, encoding: $encoding)';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'package:wyatt_http_client/src/middleware.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_context.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_request.dart';
|
||||||
|
import 'package:wyatt_http_client/src/models/middleware_response.dart';
|
||||||
|
|
||||||
|
class Pipeline {
|
||||||
|
final List<Middleware> _middlewares;
|
||||||
|
|
||||||
|
int get length => _middlewares.length;
|
||||||
|
|
||||||
|
Pipeline() : _middlewares = <Middleware>[];
|
||||||
|
Pipeline.fromIterable(Iterable<Middleware> middlewares)
|
||||||
|
: _middlewares = middlewares.toList();
|
||||||
|
|
||||||
|
/// Add a [Middleware] to this [Pipeline]
|
||||||
|
Pipeline addMiddleware(Middleware middleware) {
|
||||||
|
_middlewares.add(middleware);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create new [Pipeline] from the start or end to a specified [Middleware].
|
||||||
|
Pipeline sub(
|
||||||
|
Middleware middleware, {
|
||||||
|
bool include = false,
|
||||||
|
bool fromEnd = false,
|
||||||
|
}) {
|
||||||
|
final nodes = <Middleware>[];
|
||||||
|
final list = fromEnd ? _middlewares.reversed : _middlewares;
|
||||||
|
for (final m in list) {
|
||||||
|
if (m != middleware) {
|
||||||
|
nodes.add(m);
|
||||||
|
}
|
||||||
|
if (m == middleware) {
|
||||||
|
if (include) {
|
||||||
|
nodes.add(m);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Pipeline.fromIterable(fromEnd ? nodes.reversed : nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<MiddlewareRequest> onRequest(
|
||||||
|
MiddlewareContext context,
|
||||||
|
MiddlewareRequest request,
|
||||||
|
) async {
|
||||||
|
print('\n\nNEW REQUEST\n');
|
||||||
|
MiddlewareRequest req = request..apply();
|
||||||
|
MiddlewareContext ctx = context.copyWith(lastRequest: req);
|
||||||
|
for (final middleware in _middlewares) {
|
||||||
|
if (middleware is OnRequestMiddleware) {
|
||||||
|
req = await (middleware as OnRequestMiddleware)
|
||||||
|
.onRequest(ctx, request);
|
||||||
|
ctx = context.copyWith(lastRequest: req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<MiddlewareResponse> onResponse(
|
||||||
|
MiddlewareContext context,
|
||||||
|
MiddlewareResponse response,
|
||||||
|
) async {
|
||||||
|
print('\n\nNEW RESPONSE\n');
|
||||||
|
MiddlewareResponse res = response;
|
||||||
|
MiddlewareContext ctx = context.copyWith(lastResponse: res);
|
||||||
|
for (final middleware in _middlewares.reversed) {
|
||||||
|
if (middleware is OnResponseMiddleware) {
|
||||||
|
res = await (middleware as OnResponseMiddleware)
|
||||||
|
.onResponse(ctx, response);
|
||||||
|
ctx = context.copyWith(lastResponse: res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
final req = <String>[];
|
||||||
|
final res = <String>[];
|
||||||
|
for (final middleware in _middlewares) {
|
||||||
|
if (middleware is OnRequestMiddleware) {
|
||||||
|
req.add(middleware.getName());
|
||||||
|
}
|
||||||
|
if (middleware is OnResponseMiddleware) {
|
||||||
|
res.insert(0, middleware.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '[Req] -> ${req.join(' -> ')}\n[Res] -> ${res.join(' -> ')}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
class Convert {
|
class Convert {
|
||||||
static String toHex(List<int> bytes, {bool upperCase = false}) {
|
static String toHex(List<int> bytes, {bool upperCase = false}) {
|
||||||
final buffer = StringBuffer();
|
final buffer = StringBuffer();
|
||||||
@@ -29,6 +31,15 @@ class Convert {
|
|||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String mapToQuery(Map<String, String> map, {Encoding? encoding}) {
|
||||||
|
final pairs = <List<String>>[];
|
||||||
|
map.forEach((key, value) => pairs.add([
|
||||||
|
Uri.encodeQueryComponent(key, encoding: encoding ?? utf8),
|
||||||
|
Uri.encodeQueryComponent(value, encoding: encoding ?? utf8)
|
||||||
|
]),);
|
||||||
|
return pairs.map((pair) => '${pair[0]}=${pair[1]}').join('&');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UriX on Uri {
|
extension UriX on Uri {
|
||||||
|
|||||||
+16
-16
@@ -14,23 +14,23 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'package:http/http.dart';
|
import 'dart:core';
|
||||||
import 'package:wyatt_http_client/src/authentication/interfaces/authentication_client.dart';
|
import 'dart:math';
|
||||||
|
|
||||||
abstract class HeaderAuthenticationClient extends AuthenticationClient {
|
abstract class Delay {
|
||||||
HeaderAuthenticationClient(super.inner);
|
static Duration getRetryDelay(int attempt) {
|
||||||
|
assert(attempt >= 0, 'attempt cannot be negative');
|
||||||
|
if (attempt <= 0) {
|
||||||
|
return Duration.zero;
|
||||||
|
}
|
||||||
|
final rand = Random();
|
||||||
|
final Duration delayFactor = const Duration(milliseconds: 200);
|
||||||
|
final double randomizationFactor = 0.25;
|
||||||
|
final Duration maxDelay = const Duration(seconds: 30);
|
||||||
|
|
||||||
Map<String, String> modifyHeader(
|
final rf = randomizationFactor * (rand.nextDouble() * 2 - 1) + 1;
|
||||||
Map<String, String> header, [
|
final exp = min(attempt, 31); // prevent overflows.
|
||||||
BaseRequest? request,
|
final delay = delayFactor * pow(2.0, exp) * rf;
|
||||||
]) =>
|
return delay < maxDelay ? delay : maxDelay;
|
||||||
header;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<StreamedResponse> send(BaseRequest request) {
|
|
||||||
final newHeader = modifyHeader(Map.from(request.headers), request);
|
|
||||||
request.headers.clear();
|
|
||||||
request.headers.addAll(newHeader);
|
|
||||||
return super.send(request);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+9
-15
@@ -14,21 +14,15 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'package:wyatt_http_client/src/authentication/interfaces/header_authentication_client.dart';
|
enum HttpMethods {
|
||||||
|
head('HEAD'),
|
||||||
|
get('GET'),
|
||||||
|
post('POST'),
|
||||||
|
put('PUT'),
|
||||||
|
patch('PATCH'),
|
||||||
|
delete('DELETE');
|
||||||
|
|
||||||
typedef TokenParser = String Function(Map<String, dynamic>);
|
final String method;
|
||||||
|
|
||||||
abstract class Oauth2Client extends HeaderAuthenticationClient {
|
const HttpMethods(this.method);
|
||||||
Oauth2Client(super.inner);
|
|
||||||
|
|
||||||
Future<void> refresh() {
|
|
||||||
return Future.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> authorize(
|
|
||||||
Map<String, dynamic> body, {
|
|
||||||
Map<String, String>? headers,
|
|
||||||
}) {
|
|
||||||
return Future.value();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,83 +1,123 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License as published by
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// any later version.
|
// any later version.
|
||||||
//
|
//
|
||||||
// This program is distributed in the hope that it will be useful,
|
// This program is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
/// Status codes for HTTP responses. Extracted from dart:io
|
/// Status codes for HTTP responses. Extracted from dart:io
|
||||||
abstract class HttpStatus {
|
enum HttpStatus {
|
||||||
static const int continue_ = 100;
|
continue_(100),
|
||||||
static const int switchingProtocols = 101;
|
switchingProtocols(101),
|
||||||
static const int processing = 102;
|
processing(102),
|
||||||
static const int ok = 200;
|
ok(200),
|
||||||
static const int created = 201;
|
created(201),
|
||||||
static const int accepted = 202;
|
accepted(202),
|
||||||
static const int nonAuthoritativeInformation = 203;
|
nonAuthoritativeInformation(203),
|
||||||
static const int noContent = 204;
|
noContent(204),
|
||||||
static const int resetContent = 205;
|
resetContent(205),
|
||||||
static const int partialContent = 206;
|
partialContent(206),
|
||||||
static const int multiStatus = 207;
|
multiStatus(207),
|
||||||
static const int alreadyReported = 208;
|
alreadyReported(208),
|
||||||
static const int imUsed = 226;
|
imUsed(226),
|
||||||
static const int multipleChoices = 300;
|
multipleChoices(300),
|
||||||
static const int movedPermanently = 301;
|
movedPermanently(301),
|
||||||
static const int found = 302;
|
found(302),
|
||||||
static const int movedTemporarily = 302; // Common alias for found.
|
movedTemporarily(302), // Common alias for found.
|
||||||
static const int seeOther = 303;
|
seeOther(303),
|
||||||
static const int notModified = 304;
|
notModified(304),
|
||||||
static const int useProxy = 305;
|
useProxy(305),
|
||||||
static const int temporaryRedirect = 307;
|
temporaryRedirect(307),
|
||||||
static const int permanentRedirect = 308;
|
permanentRedirect(308),
|
||||||
static const int badRequest = 400;
|
badRequest(400),
|
||||||
static const int unauthorized = 401;
|
unauthorized(401),
|
||||||
static const int paymentRequired = 402;
|
paymentRequired(402),
|
||||||
static const int forbidden = 403;
|
forbidden(403),
|
||||||
static const int notFound = 404;
|
notFound(404),
|
||||||
static const int methodNotAllowed = 405;
|
methodNotAllowed(405),
|
||||||
static const int notAcceptable = 406;
|
notAcceptable(406),
|
||||||
static const int proxyAuthenticationRequired = 407;
|
proxyAuthenticationRequired(407),
|
||||||
static const int requestTimeout = 408;
|
requestTimeout(408),
|
||||||
static const int conflict = 409;
|
conflict(409),
|
||||||
static const int gone = 410;
|
gone(410),
|
||||||
static const int lengthRequired = 411;
|
lengthRequired(411),
|
||||||
static const int preconditionFailed = 412;
|
preconditionFailed(412),
|
||||||
static const int requestEntityTooLarge = 413;
|
requestEntityTooLarge(413),
|
||||||
static const int requestUriTooLong = 414;
|
requestUriTooLong(414),
|
||||||
static const int unsupportedMediaType = 415;
|
unsupportedMediaType(415),
|
||||||
static const int requestedRangeNotSatisfiable = 416;
|
requestedRangeNotSatisfiable(416),
|
||||||
static const int expectationFailed = 417;
|
expectationFailed(417),
|
||||||
static const int misdirectedRequest = 421;
|
misdirectedRequest(421),
|
||||||
static const int unprocessableEntity = 422;
|
unprocessableEntity(422),
|
||||||
static const int locked = 423;
|
locked(423),
|
||||||
static const int failedDependency = 424;
|
failedDependency(424),
|
||||||
static const int upgradeRequired = 426;
|
upgradeRequired(426),
|
||||||
static const int preconditionRequired = 428;
|
preconditionRequired(428),
|
||||||
static const int tooManyRequests = 429;
|
tooManyRequests(429),
|
||||||
static const int requestHeaderFieldsTooLarge = 431;
|
requestHeaderFieldsTooLarge(431),
|
||||||
static const int connectionClosedWithoutResponse = 444;
|
connectionClosedWithoutResponse(444),
|
||||||
static const int unavailableForLegalReasons = 451;
|
unavailableForLegalReasons(451),
|
||||||
static const int clientClosedRequest = 499;
|
clientClosedRequest(499),
|
||||||
static const int internalServerError = 500;
|
internalServerError(500),
|
||||||
static const int notImplemented = 501;
|
notImplemented(501),
|
||||||
static const int badGateway = 502;
|
badGateway(502),
|
||||||
static const int serviceUnavailable = 503;
|
serviceUnavailable(503),
|
||||||
static const int gatewayTimeout = 504;
|
gatewayTimeout(504),
|
||||||
static const int httpVersionNotSupported = 505;
|
httpVersionNotSupported(505),
|
||||||
static const int variantAlsoNegotiates = 506;
|
variantAlsoNegotiates(506),
|
||||||
static const int insufficientStorage = 507;
|
insufficientStorage(507),
|
||||||
static const int loopDetected = 508;
|
loopDetected(508),
|
||||||
static const int notExtended = 510;
|
notExtended(510),
|
||||||
static const int networkAuthenticationRequired = 511;
|
networkAuthenticationRequired(511),
|
||||||
// Client generated status code.
|
// Client generated status code.
|
||||||
static const int networkConnectTimeoutError = 599;
|
networkConnectTimeoutError(599);
|
||||||
|
|
||||||
|
final int statusCode;
|
||||||
|
|
||||||
|
const HttpStatus(this.statusCode);
|
||||||
|
|
||||||
|
bool equals(Object other) {
|
||||||
|
if (other is HttpStatus) {
|
||||||
|
return statusCode == other.statusCode;
|
||||||
|
}
|
||||||
|
if (other is int) {
|
||||||
|
return statusCode == other;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isInfo() {
|
||||||
|
return statusCode >= 100 && statusCode < 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSuccess() {
|
||||||
|
return statusCode >= 200 && statusCode < 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isRedirection() {
|
||||||
|
return statusCode >= 300 && statusCode < 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isClientError() {
|
||||||
|
return statusCode >= 400 && statusCode < 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isServerError() {
|
||||||
|
return statusCode >= 500 && statusCode < 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory HttpStatus.from(int status) {
|
||||||
|
return HttpStatus.values
|
||||||
|
.firstWhere((element) => element.statusCode == status);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,5 @@ enum Protocols {
|
|||||||
http,
|
http,
|
||||||
https;
|
https;
|
||||||
|
|
||||||
String get name => toString().split('.').last;
|
|
||||||
String get scheme => '$name://';
|
String get scheme => '$name://';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
|
// Please see the AUTHORS file for details.
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
|
||||||
|
abstract class RequestUtils {
|
||||||
|
static Request _copyNormalRequestWith(
|
||||||
|
Request original, {
|
||||||
|
String? method,
|
||||||
|
Uri? url,
|
||||||
|
Map<String, String>? headers,
|
||||||
|
int? maxRedirects,
|
||||||
|
bool? followRedirects,
|
||||||
|
bool? persistentConnection,
|
||||||
|
String? body,
|
||||||
|
}) {
|
||||||
|
final request = Request(method ?? original.method, url ?? original.url)
|
||||||
|
..followRedirects = followRedirects ?? original.followRedirects
|
||||||
|
..headers.addAll(headers ?? original.headers)
|
||||||
|
..maxRedirects = maxRedirects ?? original.maxRedirects
|
||||||
|
..persistentConnection =
|
||||||
|
persistentConnection ?? original.persistentConnection
|
||||||
|
..body = body ?? original.body;
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BaseRequest copyRequestWith(
|
||||||
|
BaseRequest original, {
|
||||||
|
String? method,
|
||||||
|
Uri? url,
|
||||||
|
Map<String, String>? headers,
|
||||||
|
int? maxRedirects,
|
||||||
|
bool? followRedirects,
|
||||||
|
bool? persistentConnection,
|
||||||
|
String? body,
|
||||||
|
}) {
|
||||||
|
if (original is Request) {
|
||||||
|
return _copyNormalRequestWith(
|
||||||
|
original,
|
||||||
|
method: method,
|
||||||
|
url: url,
|
||||||
|
headers: headers,
|
||||||
|
maxRedirects: maxRedirects,
|
||||||
|
followRedirects: followRedirects,
|
||||||
|
persistentConnection: persistentConnection,
|
||||||
|
body: body,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw UnimplementedError(
|
||||||
|
'Cannot handle requests of type ${original.runtimeType}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Request _copyNormalRequest(Request original) {
|
||||||
|
final request = Request(original.method, original.url)
|
||||||
|
..followRedirects = original.followRedirects
|
||||||
|
..headers.addAll(original.headers)
|
||||||
|
..maxRedirects = original.maxRedirects
|
||||||
|
..persistentConnection = original.persistentConnection
|
||||||
|
..body = original.body;
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BaseRequest copyRequest(BaseRequest original) {
|
||||||
|
if (original is Request) {
|
||||||
|
return _copyNormalRequest(original);
|
||||||
|
} else {
|
||||||
|
throw UnimplementedError(
|
||||||
|
'Cannot handle requests of type ${original.runtimeType}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,89 +1,23 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License as published by
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// any later version.
|
// any later version.
|
||||||
//
|
//
|
||||||
// This program is distributed in the hope that it will be useful,
|
// This program is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'package:http/http.dart';
|
export 'authentication_methods.dart';
|
||||||
|
export 'digest_auth.dart';
|
||||||
abstract class Utils {
|
export 'header_keys.dart';
|
||||||
static Request _copyNormalRequest(Request original) {
|
export 'http_methods.dart';
|
||||||
final request = Request(original.method, original.url)
|
export 'http_status.dart';
|
||||||
..followRedirects = original.followRedirects
|
export 'protocols.dart';
|
||||||
..headers.addAll(original.headers)
|
export 'request_utils.dart';
|
||||||
..maxRedirects = original.maxRedirects
|
|
||||||
..persistentConnection = original.persistentConnection
|
|
||||||
..body = original.body;
|
|
||||||
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Request _copyNormalRequestWith(
|
|
||||||
Request original, {
|
|
||||||
String? method,
|
|
||||||
Uri? url,
|
|
||||||
Map<String, String>? headers,
|
|
||||||
int? maxRedirects,
|
|
||||||
bool? followRedirects,
|
|
||||||
bool? persistentConnection,
|
|
||||||
String? body,
|
|
||||||
}) {
|
|
||||||
final request = Request(method ?? original.method, url ?? original.url)
|
|
||||||
..followRedirects = followRedirects ?? original.followRedirects
|
|
||||||
..headers.addAll(headers ?? original.headers)
|
|
||||||
..maxRedirects = maxRedirects ?? original.maxRedirects
|
|
||||||
..persistentConnection =
|
|
||||||
persistentConnection ?? original.persistentConnection
|
|
||||||
..body = body ?? original.body;
|
|
||||||
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
static BaseRequest copyRequest(BaseRequest original) {
|
|
||||||
if (original is Request) {
|
|
||||||
return _copyNormalRequest(original);
|
|
||||||
} else {
|
|
||||||
throw UnimplementedError(
|
|
||||||
'Cannot handle requests of type ${original.runtimeType}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static BaseRequest copyRequestWith(
|
|
||||||
BaseRequest original, {
|
|
||||||
String? method,
|
|
||||||
Uri? url,
|
|
||||||
Map<String, String>? headers,
|
|
||||||
int? maxRedirects,
|
|
||||||
bool? followRedirects,
|
|
||||||
bool? persistentConnection,
|
|
||||||
String? body,
|
|
||||||
}) {
|
|
||||||
if (original is Request) {
|
|
||||||
return _copyNormalRequestWith(
|
|
||||||
original,
|
|
||||||
method: method,
|
|
||||||
url: url,
|
|
||||||
headers: headers,
|
|
||||||
maxRedirects: maxRedirects,
|
|
||||||
followRedirects: followRedirects,
|
|
||||||
persistentConnection: persistentConnection,
|
|
||||||
body: body,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw UnimplementedError(
|
|
||||||
'Cannot handle requests of type ${original.runtimeType}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,17 +1,24 @@
|
|||||||
// Copyright (C) 2022 WYATT GROUP
|
// Copyright (C) 2022 WYATT GROUP
|
||||||
// Please see the AUTHORS file for details.
|
// Please see the AUTHORS file for details.
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License as published by
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// any later version.
|
// any later version.
|
||||||
//
|
//
|
||||||
// This program is distributed in the hope that it will be useful,
|
// This program is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
library wyatt_http_client;
|
library wyatt_http_client;
|
||||||
|
|
||||||
|
export 'src/middleware.dart';
|
||||||
|
export 'src/middleware_client.dart';
|
||||||
|
export 'src/middlewares/middlewares.dart';
|
||||||
|
export 'src/models/models.dart';
|
||||||
|
export 'src/pipeline.dart';
|
||||||
|
export 'src/utils/utils.dart';
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user