From 3d133a75d177908622a406e269acddbf412d19f3 Mon Sep 17 00:00:00 2001 From: Arun Prakash Date: Sun, 8 Sep 2024 14:59:20 +0530 Subject: [PATCH 1/6] Refactor cache exceptions and add memory cache store implementation | Docs update --- CHANGELOG.md | 162 +++++----- README.md | 50 +-- lib/src/authorization/authorization_base.dart | 103 +++++-- .../authorization/authorization_builder.dart | 55 +++- .../authorization/methods/app_password.dart | 26 +- lib/src/authorization/methods/basic_auth.dart | 46 ++- lib/src/authorization/methods/basic_jwt.dart | 83 ++++- lib/src/authorization/methods/useful_jwt.dart | 86 +++++- lib/src/bootstrap_builder.dart | 43 ++- lib/src/cache/cache_entry.dart | 24 ++ lib/src/cache/cache_manager_base.dart | 32 ++ .../exceptions/cache_exception_base.dart | 9 + .../exceptions/cache_expired_exception.dart | 8 + .../cache_not_exists_exception.dart | 8 + lib/src/cache/stores/memory_cache_store.dart | 37 +++ lib/src/client_configuration.dart | 56 +++- lib/src/constants.dart | 29 ++ lib/src/enums.dart | 241 +++++++++++++-- lib/src/interface/application_passwords.dart | 43 ++- lib/src/interface/category.dart | 35 ++- lib/src/interface/comments.dart | 38 ++- lib/src/interface/me.dart | 30 +- lib/src/interface/media.dart | 35 ++- lib/src/interface/page.dart | 37 ++- lib/src/interface/posts.dart | 37 ++- lib/src/interface/search.dart | 28 +- lib/src/interface/tags.dart | 26 +- lib/src/interface/users.dart | 38 ++- lib/src/interface_key.dart | 42 +++ lib/src/middleware/delegated_middleware.dart | 24 ++ .../models/middleware_raw_response.dart | 46 +++ .../middleware/wordpress_middleware_base.dart | 65 +++- lib/src/operations/create.dart | 22 +- lib/src/operations/custom.dart | 24 +- lib/src/operations/delete.dart | 25 +- lib/src/operations/list.dart | 24 +- lib/src/operations/retrieve.dart | 18 +- lib/src/operations/update.dart | 19 +- lib/src/parallel_wordpress/exports.dart | 1 + .../extensions/parallel_result_exts.dart | 46 +++ .../parallel_wordpress/parallel_result.dart | 32 +- .../parallel_wordpress.dart | 285 +++++++++++++++++- lib/src/parallel_wordpress/typedefs.dart | 13 +- lib/src/request_executor_base.dart | 73 ++++- lib/src/requests/wordpress_request.dart | 42 ++- .../application_password_response.dart | 63 ++++ lib/src/responses/category_response.dart | 35 +++ lib/src/responses/comment_response.dart | 43 +++ lib/src/responses/media_response.dart | 61 ++++ lib/src/responses/page_response.dart | 57 ++++ lib/src/responses/post_response.dart | 53 ++++ lib/src/responses/properties/author_meta.dart | 40 +++ lib/src/responses/properties/avatar_urls.dart | 34 +++ lib/src/responses/properties/content.dart | 34 +++ .../properties/extra_capabilities.dart | 27 ++ lib/src/responses/properties/image_meta.dart | 40 +++ .../responses/properties/link_container.dart | 48 +++ lib/src/responses/properties/links.dart | 49 +++ .../responses/properties/media_details.dart | 36 +++ .../properties/media_size_value.dart | 25 ++ lib/src/responses/search_response.dart | 28 ++ lib/src/responses/tag_response.dart | 28 ++ lib/src/responses/user_response.dart | 46 +++ .../wordpress_discovery_response.dart | 37 +++ lib/src/responses/wordpress_raw_response.dart | 131 ++++++-- lib/src/responses/wordpress_response.dart | 94 +++++- .../utilities/codable_map/codable_map.dart | 40 +++ lib/src/utilities/codable_map/type_key.dart | 18 ++ .../utilities/extensions/map_extensions.dart | 22 ++ .../parallel_result_extensions.dart | 4 +- lib/src/utilities/helpers.dart | 183 +++++++---- lib/src/utilities/request_url.dart | 51 +++- lib/src/utilities/self_representive_base.dart | 23 +- lib/src/utilities/wordpress_events.dart | 29 +- lib/src/wordpress_client_base.dart | 262 +++++++++++----- test/wordpress_client_test.dart | 2 +- 76 files changed, 3448 insertions(+), 441 deletions(-) create mode 100644 lib/src/cache/cache_entry.dart create mode 100644 lib/src/cache/cache_manager_base.dart create mode 100644 lib/src/cache/exceptions/cache_exception_base.dart create mode 100644 lib/src/cache/exceptions/cache_expired_exception.dart create mode 100644 lib/src/cache/exceptions/cache_not_exists_exception.dart create mode 100644 lib/src/cache/stores/memory_cache_store.dart create mode 100644 lib/src/parallel_wordpress/extensions/parallel_result_exts.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 051a2f3..d03b55e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,106 +1,106 @@ ## 8.5.3 -- ๐Ÿฉน Fix invalid base url on web +- Fix invalid base url on web ## 8.5.2 -- ๐Ÿฉน Fix versions +- Fix versions ## 8.5.1 -- ๐Ÿฉน Fix dio sendTimeout exception +- Fix dio sendTimeout exception ## 8.5.0 -- ๐ŸŽ‰ Added support for initialize the client without a base url. +- Added support for initializing the client without a base url. - Use the `WordpressClient.generic` constructor to initialize the client without a base url. - Use the `reconfigure` method to set the base url after initializing the client. - Failure to set the base url will throw an exception when making requests. -- ๐ŸŽ‰ Added `isAuthenticated` method to check if the current instance has a valid authentication. +- Added `isAuthenticated` method to check if the current instance has a valid authentication. - Optionally, pass an instance of `IAuthorization` to check if the client is authenticated with the given authorization. - ๐Ÿ’ฅ Deprecated `reconfigureClient` method in favour of `reconfigure` method. ## 8.4.10 -- ๐Ÿฉน Fix bug on clearing middleware list +- Fix bug on clearing middleware list ## 8.4.9 -- ๐Ÿ“ฆ Downgrade meta package to match flutter meta version +- Downgrade meta package to match flutter meta version ## 8.4.8 -- ๐Ÿฉน Bug fixes +- Bug fixes - ๐Ÿ’ฅ Deprecated `WordpressClient.initialize(...)` ctor and `initialize()` method -- ๐ŸŽ‰ Added `WordpressClient.fromDioInstance(...)` constructor +- Added `WordpressClient.fromDioInstance(...)` constructor ## 8.4.7 -- ๐Ÿฉน Bug fixes -- ๐Ÿ›  Fix validations for entered `baseUrl`; Supporting sites with custom REST Api paths +- Bug fixes +- Fix validations for entered `baseUrl`; Supporting sites with custom REST Api paths ## 8.4.6 -- ๐Ÿฉน Bug fixes -- ๐Ÿ›  Added validations for entered `baseUrl` +- Bug fixes +- Added validations for entered `baseUrl` - ๐Ÿ’ฅ Renamed `executeGuarded` to `guardAsync` and added `guard` method ## 8.4.5 -- ๐Ÿฉน Bug fixes +- Bug fixes ## 8.4.4 -- ๐Ÿฉน Bug fixes +- Bug fixes ## 8.4.3 -- ๐ŸŽ‰ New static method to check if a site is built using WordPress +- New static method to check if a site is built using WordPress ## 8.4.2 -- ๐Ÿฉน Bug fixes -- ๐ŸŽ‰ Added static methods to validate base URL and discover a website +- Bug fixes +- Added static methods to validate base URL and discover a website ## 8.4.1 -- ๐ŸŽ‰ Introduce `ParallelWordpress` class to generate and execute parallel requests -- ๐Ÿ›  Bug fixes +- Introduce `ParallelWordpress` class to generate and execute parallel requests +- Bug fixes ## 8.4.0 -- ๐ŸŽ‰ Added support for Middlewares +- Added support for Middlewares - ๐Ÿ’ฅ Removed dependency on `synchronised` package ## 8.3.10 -- ๐Ÿฉน Bug fixes +- Bug fixes ## 8.3.9 -- ๐Ÿฉน Bug fixes -- ๐ŸŽ‰ Iterate over the raw response of the endpoint using [] operator +- Bug fixes +- Iterate over the raw response of the endpoint using [] operator ## 8.3.8 -- ๐Ÿ› Renamed retrive -> retrieve. Fix typo +- Renamed retrive -> retrieve. Fix typo ## 8.3.7 -- ๐ŸŽ‰ Support for raw requests -- ๐Ÿ›  Bug fixes +- Support for raw requests +- Bug fixes ## 8.3.6 -- ๐Ÿฉน Bug fixes on enum parsing +- Bug fixes on enum parsing ## 8.3.5 -- ๐Ÿฉน Bug fixes on comment list request +- Bug fixes on comment list request ## 8.3.4 -- ๐Ÿ› Media response model null exception when parsing if media details is empty +- Media response model null exception when parsing if media details is empty ## 8.3.3 @@ -120,73 +120,73 @@ ## 8.3.0 -- ๐ŸŽ‰ Supports Application Password endpoint +- Supports Application Password endpoint - Packages update ## 8.2.2 -- ๐Ÿ›  Bug fixes +- Bug fixes ## 8.2.1 -- ๐Ÿ›  Fixed exporting WordPressDiscovery class +- Fixed exporting WordPressDiscovery class ## 8.2.0 -- ๐ŸŽ‰ Added support for Pages endpoint +- Added support for Pages endpoint ## 8.1.0 -- ๐ŸŽ‰ Added ability to fetch and cache the discovery endpoint of WordPress REST API +- Added ability to fetch and cache the discovery endpoint of WordPress REST API ## 8.0.11 -- โž• Added `extra` property to all request classes -- โž• Added `addAllIfNotNull(...)` extension method +- Added `extra` property to all request classes +- Added `addAllIfNotNull(...)` extension method ## 8.0.10 -- ๐Ÿ›  `featured_media_src_url` key now decodes as expected -- โž• Added `decodeByMultiKeys` method +- `featured_media_src_url` key now decodes as expected +- Added `decodeByMultiKeys` method ## 8.0.9 -- โž• Added App Password support +- Added App Password support ## 8.0.8 -- ๐Ÿ”ง Integrated new lint rules and code refactors +- Integrated new lint rules and code refactors ## 8.0.7 -- ๐Ÿ› Bug fixes -- โž• Introduced `RequestErrorType` for failure responses -- โž• Introduced `mapGuarded(...)` and `executeGuarded(...)` methods -- ๐Ÿ”ง Usual refactors and improvements +- Bug fixes +- Introduced `RequestErrorType` for failure responses +- Introduced `mapGuarded(...)` and `executeGuarded(...)` methods +- Usual refactors and improvements ## 8.0.6 -- ๐Ÿ“š Docs update +- Docs update ## 8.0.5 -- ๐Ÿฉน Bug fixes and improvements +- Bug fixes and improvements ## 8.0.4 -- ๐Ÿฉน Bug fixes and improvements +- Bug fixes and improvements ## 8.0.3 -- ๐Ÿ“ค Export response extensions +- Export response extensions ## 8.0.2 -- ๐Ÿ”ฝ Downgrade collection version +- Downgrade collection version ## 8.0.1 -- ๐Ÿ“š Docs update +- Docs update ## 8.0.0 @@ -198,114 +198,114 @@ ## 6.3.1 -- ๐Ÿ” Implemented search endpoint +- Implemented search endpoint ## 6.3.0 -- ๐Ÿš€ Major changes in the API +- Major changes in the API ## 6.2.1-pre -- ๐Ÿงช Misc changes +- Misc changes ## 6.2.0-pre -- ๐Ÿ”ง Refactoring, Request Synchronization, and Debug Mode +- Refactoring, Request Synchronization, and Debug Mode ## 6.1.7-pre to 6.1.9-pre -- ๐Ÿ”ง Refactoring & Bug fixes +- Refactoring & Bug fixes ## 6.1.6-pre -- ๐Ÿ”— Support 3xx series responses (Cached Response) +- Support 3xx series responses (Cached Response) ## 6.1.5-pre -- โž• Added Post extension for Media and Author +- Added Post extension for Media and Author ## 6.1.3-pre & 6.1.4-pre -- ๐Ÿฉน Bug fixes +- Bug fixes ## 6.1.2-pre -- ๐Ÿ“Œ Version fix +- Version fix ## 6.1.1-pre -- ๐Ÿ—‘๏ธ Removed test package +- Removed test package ## 6.1.0-pre -- ๐Ÿš€ Entire API changed -- ๐ŸŒŠ Fluency maintained using Dart's cascading operator -- โšก Performance and memory consumption improvements +- Entire API changed +- Fluency maintained using Dart's cascading operator +- Performance and memory consumption improvements ## 5.4.3 -- ๐Ÿ“œ Total pages parsing fix +- Total pages parsing fix ## 5.4.2 -- ๐Ÿ“ฆ Packages fix +- Packages fix ## 5.4.1 -- ๐Ÿ”’ Null safety fix +- Null safety fix ## 5.4.0 -- ๐Ÿ“ฆ Packages update +- Packages update ## 5.3.1 -- ๐Ÿ“œ Response structure fix +- Response structure fix ## 5.3.0 -- ๐Ÿฉน Bug fix +- Bug fix ## 5.2.9 -- ๐Ÿฉน Bug Fix +- Bug Fix ## 5.2.8 -- ๐Ÿ”„ Revert author meta & featured image removal +- Revert author meta & featured image removal ## 5.2.7 -- ๐Ÿฉน Bug fix +- Bug fix ## 5.2.6 -- โš™๏ธ Experimental Request Caching system +- Experimental Request Caching system ## 5.2.5 -- ๐Ÿ”„ BREAKING CHANGE: Remove Author Meta & Featured Image URL Fields from Post response +- ๐Ÿ’ฅ BREAKING CHANGE: Remove Author Meta & Featured Image URL Fields from Post response ## 5.2.4 -- ๐Ÿ—‘๏ธ Remove unused package +- Remove unused package ## 5.2.3 -- ๐Ÿ”„ BREAKING CHANGE: Request API Change +- ๐Ÿ’ฅ BREAKING CHANGE: Request API Change ## 5.1.1 -- ๐Ÿ“ Formatting changes +- Formatting changes ## 5.1.0 -- ๐Ÿ”„ BREAKING CHANGE: Authorization API Change +- ๐Ÿ’ฅ BREAKING CHANGE: Authorization API Change ## 5.0.4 -- ๐Ÿ”’ Fixed Authorization Bugs +- Fixed Authorization Bugs ## 4.0.0 -- ๐Ÿš€ Initial version, created by Stagehand +- Initial version, created by Stagehand diff --git a/README.md b/README.md index 24276f3..5824aef 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,25 @@

WordPress Client

- - Pub Version - + + Pub Version + + + License + + + Stars +
- Dart - Flutter - WordPress + Dart + Flutter + WordPress
- A powerful and easy-to-use WordPress REST API client for Dart & Flutter. - -

+ A powerful and easy-to-use WordPress REST API client for Dart & Flutter. +

-## โœจ Features +## ๐Ÿš€ Features - ๐Ÿ“ฆ API discovery support. - โฒ๏ธ Measures request completion time. @@ -32,19 +37,21 @@ If you find any functionality which you require is missing from the package and you are not able to work it out using built in options like raw requests etc, then please share the functionality in details as a comment here: https://github.com/ArunPrakashG/wordpress_client/discussions/55 -## ๐Ÿ“– How to Use +## ๐Ÿ“ฆ Installation -### **1. Setup** - -Add `wordpress_client` in your `pubspec.yaml`: +Add `wordpress_client` to your `pubspec.yaml`: ```dart dependencies: - wordpress_client: ^8.4.8 + wordpress_client: ^8.5.3 ``` > ๐Ÿ’ก Ensure you get the [latest version here](https://pub.dev/packages/wordpress_client). +Then run `flutter pub get` to install the package. + +## ๐Ÿ”ง Usage + Import the package where you need: ```dart @@ -153,23 +160,22 @@ By Useful Team, this is another implementation using JWT for authentication purp Learn how to implement [Custom Requests here](https://github.com/ArunPrakashG/wordpress_client/wiki/Using-Custom-Requests). -## ๐Ÿ“ฃ Feedback +## ๐Ÿค Feedback & Contributing - ๐Ÿ› For bugs or feature requests, use the [issue tracker][tracker]. - ๐Ÿ’ก Contributions are always appreciated. PRs are welcome! -## ๐Ÿ“œ License +## ๐Ÿ“„ License -Licensed under [MIT](https://github.com/ArunPrakashG/wordpress_client/blob/master/LICENSE). - -[tracker]: https://github.com/ArunPrakashG/wordpress_client/issues +This project is [MIT](https://github.com/ArunPrakashG/wordpress_client/blob/master/LICENSE) licensed. ---
- -Support Me: + If you find this package helpful, consider supporting the development: [![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/arunprakashg)
+ +[tracker]: https://github.com/ArunPrakashG/wordpress_client/issues diff --git a/lib/src/authorization/authorization_base.dart b/lib/src/authorization/authorization_base.dart index eb6a0fa..277825b 100644 --- a/lib/src/authorization/authorization_base.dart +++ b/lib/src/authorization/authorization_base.dart @@ -4,13 +4,38 @@ import 'package:meta/meta.dart'; import '../../wordpress_client.dart'; import '../utilities/helpers.dart'; -/// Base class for all authorization types. +/// Base class for all authorization types in the WordPress client. /// -/// To implement a custom authorization system, You _must_ extend this class. +/// To implement a custom authorization system, you must extend this class. /// -/// Note that, there is no storage system internally to store and retrive +/// Note: There is no built-in storage system to store and retrieve credentials. +/// You need to implement your own storage mechanism if required. +/// +/// Example of a custom authorization implementation: +/// ```dart +/// class CustomAuth extends IAuthorization { +/// CustomAuth({required String userName, required String password}) +/// : super(userName: userName, password: password); +/// +/// @override +/// String get scheme => 'Custom'; +/// +/// @override +/// Future authorize() async { +/// // Implement your custom authorization logic here +/// return true; +/// } +/// +/// // Implement other required methods... +/// } +/// ``` abstract base class IAuthorization { /// Creates a new instance of [IAuthorization] with the given username and password. + /// + /// [userName]: The username for authentication. + /// [password]: The password for authentication. + /// [headerKey]: The HTTP header key to use for authorization (default is 'Authorization'). + /// [events]: Optional [WordpressEvents] to listen to during the authorization process. IAuthorization({ required this.userName, required this.password, @@ -18,35 +43,38 @@ abstract base class IAuthorization { this.events, }); - /// The base url of the wordpress site. + /// The base URL of the WordPress site. late final Uri baseUrl; - /// The username + /// The username for authentication. final String userName; - /// The password + /// The password for authentication. final String password; - /// The header key to use for authorization. + /// The HTTP header key to use for authorization. final String headerKey; - /// The events to listen to. + /// Optional events to listen to during the authorization process. WordpressEvents? events; - /// Gets if this authorization instance has valid authentication nounce. (token/encryptedToken) + /// Indicates if this authorization instance has a valid authentication nonce (token/encryptedToken). bool get isValidAuth; - /// Gets if this is an invalid or default authorization instance without username or password fields. + /// Indicates if this is an invalid or default authorization instance without username or password fields. bool get isDefault => isNullOrEmpty(userName) || isNullOrEmpty(password); - /// Gets the authorization scheme. + /// Gets the authorization scheme (e.g., 'Bearer' for JWT, 'Basic' for Basic Auth). String get scheme; - /// Helps to initialize authorization instance with internal requesting client passed as a parameter. + /// Initializes the authorization instance with the internal requesting client. /// - /// This function is called only if there is no valid nounce available i.e., when isAuthenticated() returns false. + /// This method is called only if there is no valid nonce available (i.e., when [isAuthenticated] returns false). + /// [authorize] and [validate] methods will not be called before calling [initialize]. /// - /// `authorize()` / `validate()` functions will not be called before calling `initialize()` function. + /// [baseUrl]: The base URL of the WordPress site. + /// + /// Returns a [Future] indicating whether initialization was successful. @mustCallSuper Future initialize({ required Uri baseUrl, @@ -56,39 +84,50 @@ abstract base class IAuthorization { } /// Provides this instance of [IAuthorization] with the Dio client instance for requests. + /// + /// [client]: The Dio client instance to use for making HTTP requests. void clientFactoryProvider(Dio client); - /// Called to validate token. (such as in JWT auth) - /// - /// As of right now, this function is not called outside of this instance. This can change in the future if there is a requirement to validate the nounce from the core client itself. - /// Therefore, be sure to implement this with valid logic for the validation process. + /// Validates the authentication token (e.g., JWT token). /// - /// Example 1: JWT authentication token can be validated through an endpoint, you can implement that validation logic inside this. + /// This method is not called outside of this instance by default, but it may be used in the future. + /// Implement this method with valid logic for the validation process. /// - /// Example 2: Basic Auth does not require any validation, therefore you can simply return true or if still require some custom logic, you can implement that as well! + /// Example for JWT: + /// ```dart + /// @override + /// Future validate() async { + /// try { + /// final response = await _client.post('/validate-token', data: {'token': _token}); + /// return response.statusCode == 200; + /// } catch (e) { + /// return false; + /// } + /// } + /// ``` Future validate(); - /// Called to check if this instance has a valid authentication nounce and generateAuthUrl() won't return null. + /// Checks if this instance has a valid authentication nonce and [generateAuthUrl] won't return null. /// - /// This function will be called before init() function, therefore if you are using client instance passed through init() then there will be NullReferenceException. + /// This method is called before [initialize], so if you need to use the client instance, + /// you should implement custom logic to handle potential null references. /// - /// If you require HTTP requests in this method, then you need to implement custom logic. + /// Returns a [Future] indicating whether the instance is authenticated. Future isAuthenticated(); - /// Called to authorize a request if the request requires authentication. + /// Authorizes a request if authentication is required. /// - /// Returning true means the request should be authorized, false means authorization failed. + /// Returns a [Future] indicating whether authorization was successful (true) or failed (false). Future authorize(); - /// After `authorize()` is called, to get the authorization header string, (ie, '{scheme} {token}') the client calls this method to generate the raw string. - /// - /// The returning string formate must always be like - /// - /// {scheme} {token} + /// Generates the authorization header string after [authorize] is called. /// - /// - Example 1: In case of JWT, `Bearer {jwt_token}` + /// The returned string format must always be: "{scheme} {token}" /// - /// - Example 2: In case of Basic Auth, `Basic {Base64UsernamePassword}` + /// Examples: + /// - JWT: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + /// - Basic Auth: "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" /// + /// Returns a [Future] containing the authorization header string, or null if not authorized. Future generateAuthUrl(); } diff --git a/lib/src/authorization/authorization_builder.dart b/lib/src/authorization/authorization_builder.dart index d7982d6..26c2f39 100644 --- a/lib/src/authorization/authorization_builder.dart +++ b/lib/src/authorization/authorization_builder.dart @@ -8,33 +8,86 @@ import 'methods/basic_jwt.dart'; import 'methods/useful_jwt.dart'; /// Creates a new instance of [AuthorizationBuilder]. +/// +/// This class uses the builder pattern to construct an authorization instance. +/// Use the various 'with' methods to set the required parameters, then call +/// [build] to create the authorization object. +/// +/// Example usage: +/// ```dart +/// final auth = AuthorizationBuilder() +/// .withUserName('myusername') +/// .withPassword('mypassword') +/// .withType(AuthorizationType.useful_jwt) +/// .build(); +/// ``` final class AuthorizationBuilder { String _userName = ''; String _password = ''; AuthorizationType? _type; WordpressEvents? _events; + /// Sets the username for the authorization. + /// + /// Example: + /// ```dart + /// builder.withUserName('myusername'); + /// ``` AuthorizationBuilder withUserName(String userName) { _userName = userName; return this; } + /// Sets the password for the authorization. + /// + /// Example: + /// ```dart + /// builder.withPassword('mypassword'); + /// ``` AuthorizationBuilder withPassword(String password) { _password = password; return this; } + /// Sets the authorization type. + /// + /// If not set, it defaults to [AuthorizationType.useful_jwt]. + /// + /// Example: + /// ```dart + /// builder.withType(AuthorizationType.basic_jwt); + /// ``` AuthorizationBuilder withType(AuthorizationType type) { _type = type; return this; } + /// Sets the WordPress events for the authorization. + /// + /// This is optional and can be used to handle specific WordPress events. + /// + /// Example: + /// ```dart + /// builder.withEvents(myWordPressEvents); + /// ``` AuthorizationBuilder withEvents(WordpressEvents events) { _events = events; return this; } - /// Builds the authorization instance. + /// Builds and returns the authorization instance based on the set parameters. + /// + /// If no type is specified, it defaults to [AuthorizationType.useful_jwt]. + /// + /// Example: + /// ```dart + /// final auth = builder.build(); + /// ``` + /// + /// Returns an instance of [IAuthorization] which can be one of: + /// - [BasicJwtAuth] + /// - [UsefulJwtAuth] + /// - [AppPasswordAuth] IAuthorization build() { _type ??= AuthorizationType.useful_jwt; diff --git a/lib/src/authorization/methods/app_password.dart b/lib/src/authorization/methods/app_password.dart index 5ecb1c6..554f753 100644 --- a/lib/src/authorization/methods/app_password.dart +++ b/lib/src/authorization/methods/app_password.dart @@ -3,8 +3,24 @@ import 'package:dio/dio.dart'; import '../../../wordpress_client.dart'; import '../../utilities/helpers.dart'; -/// Authentication using Application Passwords which are supported on all Wordpress installations version 5.6 or higher. +/// Authentication using Application Passwords, supported on WordPress installations version 5.6 or higher. +/// +/// This class provides a way to authenticate with WordPress using Application Passwords, +/// which is a secure method for third-party applications to access WordPress sites. +/// +/// Example usage: +/// ```dart +/// final auth = AppPasswordAuth( +/// userName: 'your_username', +/// password: 'your_app_password', +/// ); +/// final class AppPasswordAuth extends IAuthorization { + /// Creates an instance of [AppPasswordAuth]. + /// + /// [userName]: The WordPress username. + /// [password]: The application password generated for this user in WordPress. + /// [events]: Optional [WordpressEvents] to listen to during the authorization process. AppPasswordAuth({ required super.userName, required super.password, @@ -13,16 +29,19 @@ final class AppPasswordAuth extends IAuthorization { @override Future authorize() async { + // Application Passwords don't require an explicit authorization step return true; } @override Future generateAuthUrl() async { + // Generates the Basic Auth header value return '$scheme ${base64Encode('$userName:$password')}'; } @override Future isAuthenticated() async { + // Application Passwords are always considered authenticated if provided return true; } @@ -32,11 +51,14 @@ final class AppPasswordAuth extends IAuthorization { @override Future validate() async { + // Application Passwords don't require validation return true; } @override - void clientFactoryProvider(Dio client) {} + void clientFactoryProvider(Dio client) { + // No additional configuration needed for Dio client + } @override String get scheme => 'Basic'; diff --git a/lib/src/authorization/methods/basic_auth.dart b/lib/src/authorization/methods/basic_auth.dart index 05fdade..00966a0 100644 --- a/lib/src/authorization/methods/basic_auth.dart +++ b/lib/src/authorization/methods/basic_auth.dart @@ -7,11 +7,31 @@ import '../authorization_base.dart'; /// The most basic authentication system using username and password. /// -/// Implemented on basis of https://github.com/WP-API/Basic-Auth wordpress plugin. +/// This class implements Basic Authentication for WordPress API, based on +/// the https://github.com/WP-API/Basic-Auth WordPress plugin. /// -/// Make sure to only use this method for testing purposes as this isn't secure. +/// WARNING: This method should only be used for testing purposes as it is not secure +/// for production environments. +/// +/// Example usage: +/// ```dart +/// final auth = BasicAuth(userName: 'myuser', password: 'mypassword'); +/// final isAuthorized = await auth.authorize(); +/// if (isAuthorized) { +/// final authHeader = await auth.generateAuthUrl(); +/// // Use authHeader in your API requests +/// } +/// ``` +/// +/// @deprecated Use AppPasswordAuth instead for more secure authentication. @Deprecated('Use AppPasswordAuth instead') final class BasicAuth extends IAuthorization { + /// Creates a new BasicAuth instance. + /// + /// [userName] and [password] are required parameters. + /// [events] is an optional parameter for authentication events. + /// + /// @deprecated Use AppPasswordAuth instead for more secure authentication. @Deprecated('Use AppPasswordAuth instead') BasicAuth({ required super.userName, @@ -19,32 +39,54 @@ final class BasicAuth extends IAuthorization { super.events, }); + /// Authorizes the user. Always returns true for BasicAuth. + /// + /// This method is part of the IAuthorization interface but doesn't perform + /// any actual authorization for BasicAuth. @override Future authorize() async { return true; } + /// Generates the authorization header value. + /// + /// Returns a Future containing the Basic Auth header value. + /// The returned string is in the format: "Basic base64EncodedCredentials" @override Future generateAuthUrl() async { return '$scheme ${base64Encode('$userName:$password')}'; } + /// Checks if the user is authenticated. Always returns true for BasicAuth. + /// + /// This method is part of the IAuthorization interface but doesn't perform + /// any actual authentication check for BasicAuth. @override Future isAuthenticated() async { return true; } + /// Indicates whether the authentication is valid. Always true for BasicAuth. @override bool get isValidAuth => true; + /// Validates the authentication. Always returns true for BasicAuth. + /// + /// This method is part of the IAuthorization interface but doesn't perform + /// any actual validation for BasicAuth. @override Future validate() async { return true; } + /// Provides a way to modify the Dio client. Not used in BasicAuth. + /// + /// This method is part of the IAuthorization interface but doesn't perform + /// any modifications to the Dio client for BasicAuth. @override void clientFactoryProvider(Dio client) {} + /// The authentication scheme used. Returns 'Basic' for BasicAuth. @override String get scheme => 'Basic'; } diff --git a/lib/src/authorization/methods/basic_jwt.dart b/lib/src/authorization/methods/basic_jwt.dart index fcd709d..8cff06b 100644 --- a/lib/src/authorization/methods/basic_jwt.dart +++ b/lib/src/authorization/methods/basic_jwt.dart @@ -8,14 +8,38 @@ import '../../utilities/helpers.dart'; import '../authorization_base.dart'; import 'useful_jwt.dart'; -/// Most widely used authentication system, which is most easy to integrate and secure (when compared with basic auth) +/// BasicJwtAuth implements JWT (JSON Web Token) authentication for WordPress APIs. /// -/// Implemented on basis of https://github.com/Tmeister/wp-api-jwt-auth wordpress plugin. +/// This class is based on the WordPress plugin https://github.com/Tmeister/wp-api-jwt-auth. /// -/// ### NOTE +/// ### Usage Example: /// -/// This plugin isn't in active development and may contain lots of bugs/issues. It is recommended to use [UsefulJwtAuth] instead. +/// ```dart +/// final auth = BasicJwtAuth( +/// userName: 'your_username', +/// password: 'your_password', +/// ); +/// +/// // Authorize the user +/// bool isAuthorized = await auth.authorize(); +/// +/// if (isAuthorized) { +/// print('Successfully authorized!'); +/// // Use the auth object for subsequent API calls +/// } else { +/// print('Authorization failed.'); +/// } +/// ``` +/// +/// ### Important Note: +/// +/// This implementation relies on a WordPress plugin that is not actively maintained. +/// For a more robust and up-to-date JWT authentication, consider using [UsefulJwtAuth] instead. final class BasicJwtAuth extends IAuthorization { + /// Creates a new instance of BasicJwtAuth. + /// + /// [userName] and [password] are required for authentication. + /// [events] is optional and can be used to handle authentication events. BasicJwtAuth({ required super.userName, required super.password, @@ -27,17 +51,33 @@ final class BasicJwtAuth extends IAuthorization { bool _hasValidatedOnce = false; Dio? _client; + /// Number of days until the token expires. static const int DAYS_UNTIL_TOKEN_EXPIRY = 3; + /// Checks if the current authentication is valid. @override bool get isValidAuth => !isNullOrEmpty(_encryptedAccessToken); + /// Checks if the current authentication has expired. bool get _isAuthExpiried { return _lastAuthorizedTime != null && DateTime.now().difference(_lastAuthorizedTime!).inHours > (DAYS_UNTIL_TOKEN_EXPIRY * 24); } + /// Authorizes the user with the provided credentials. + /// + /// Returns `true` if authorization is successful, `false` otherwise. + /// + /// Example: + /// ```dart + /// bool success = await auth.authorize(); + /// if (success) { + /// print('Authorization successful'); + /// } else { + /// print('Authorization failed'); + /// } + /// ``` @override Future authorize() async { if (isValidAuth) { @@ -86,6 +126,15 @@ final class BasicJwtAuth extends IAuthorization { return _hasValidatedOnce = !isNullOrEmpty(_encryptedAccessToken); } + /// Checks if the user is currently authenticated. + /// + /// Returns `true` if the user is authenticated, `false` otherwise. + /// + /// Example: + /// ```dart + /// bool isAuth = await auth.isAuthenticated(); + /// print(isAuth ? 'User is authenticated' : 'User is not authenticated'); + /// ``` @override Future isAuthenticated() async { if (_hasValidatedOnce && !isNullOrEmpty(_encryptedAccessToken)) { @@ -95,6 +144,15 @@ final class BasicJwtAuth extends IAuthorization { return false; } + /// Validates the current authentication token. + /// + /// Returns `true` if the token is valid, `false` otherwise. + /// + /// Example: + /// ```dart + /// bool isValid = await auth.validate(); + /// print(isValid ? 'Token is valid' : 'Token is invalid'); + /// ``` @override Future validate() async { if (_client == null || isNullOrEmpty(_encryptedAccessToken)) { @@ -122,6 +180,19 @@ final class BasicJwtAuth extends IAuthorization { (response.data['code'] as String) == 'jwt_auth_valid_token'; } + /// Generates an authentication URL with the current token. + /// + /// Returns the authentication URL as a string, or `null` if not authenticated. + /// + /// Example: + /// ```dart + /// String? authUrl = await auth.generateAuthUrl(); + /// if (authUrl != null) { + /// print('Auth URL: $authUrl'); + /// } else { + /// print('Not authenticated'); + /// } + /// ``` @override Future generateAuthUrl() async { if (!await isAuthenticated()) { @@ -131,11 +202,15 @@ final class BasicJwtAuth extends IAuthorization { return '$scheme $_encryptedAccessToken'; } + /// Sets the Dio client for making HTTP requests. + /// + /// This method is called internally to set up the HTTP client. @override void clientFactoryProvider(Dio client) { _client = client; } + /// The authentication scheme used for this method (Bearer). @override String get scheme => 'Bearer'; } diff --git a/lib/src/authorization/methods/useful_jwt.dart b/lib/src/authorization/methods/useful_jwt.dart index 12456fc..5e7c0e3 100644 --- a/lib/src/authorization/methods/useful_jwt.dart +++ b/lib/src/authorization/methods/useful_jwt.dart @@ -7,12 +7,42 @@ import 'package:dio/dio.dart'; import '../../enums.dart'; import '../../utilities/helpers.dart'; import '../authorization_base.dart'; -import 'basic_jwt.dart'; -/// Similar to [BasicJwtAuth], this plugin is in active development and has much more features than the previous one. It is recommended to use this plugin instead of the previous one. +/// UsefulJwtAuth implements JWT (JSON Web Token) authentication for WordPress APIs. /// -/// Implemented on basis of https://github.com/usefulteam/jwt-auth wordpress plugin. +/// This class is based on the WordPress plugin https://github.com/usefulteam/jwt-auth, +/// which is actively maintained and offers more features compared to the BasicJwtAuth. +/// +/// ### Usage Example: +/// +/// ```dart +/// final auth = UsefulJwtAuth( +/// userName: 'your_username', +/// password: 'your_password', +/// ); +/// +/// // Authorize the user +/// bool isAuthorized = await auth.authorize(); +/// +/// if (isAuthorized) { +/// print('Successfully authorized!'); +/// // Use the auth object for subsequent API calls +/// } else { +/// print('Authorization failed.'); +/// } +/// +/// // Generate auth URL for API requests +/// String? authHeader = await auth.generateAuthUrl(); +/// if (authHeader != null) { +/// // Use authHeader in your API requests +/// print('Auth header: $authHeader'); +/// } +/// ``` final class UsefulJwtAuth extends IAuthorization { + /// Creates a new instance of UsefulJwtAuth. + /// + /// [userName] and [password] are required for authentication. + /// [events] is optional and can be used to handle authentication events. UsefulJwtAuth({ required super.userName, required super.password, @@ -24,16 +54,32 @@ final class UsefulJwtAuth extends IAuthorization { bool _hasValidatedOnce = false; Dio? _client; + /// Number of days until the token expires. static const int DAYS_UNTILS_TOKEN_EXPIRY = 3; + /// Checks if the current authentication is valid. @override bool get isValidAuth => !isNullOrEmpty(_encryptedAccessToken); + /// Checks if the current authentication has expired. bool get _isAuthExpiried => _lastAuthorizedTime != null && DateTime.now().difference(_lastAuthorizedTime!).inHours > (DAYS_UNTILS_TOKEN_EXPIRY * 24); + /// Authorizes the user with the provided credentials. + /// + /// Returns `true` if authorization is successful, `false` otherwise. + /// + /// Example: + /// ```dart + /// bool success = await auth.authorize(); + /// if (success) { + /// print('Authorization successful'); + /// } else { + /// print('Authorization failed'); + /// } + /// ``` @override Future authorize() async { if (isValidAuth) { @@ -79,6 +125,15 @@ final class UsefulJwtAuth extends IAuthorization { return _hasValidatedOnce = !isNullOrEmpty(_encryptedAccessToken); } + /// Checks if the user is currently authenticated. + /// + /// Returns `true` if the user is authenticated, `false` otherwise. + /// + /// Example: + /// ```dart + /// bool isAuth = await auth.isAuthenticated(); + /// print(isAuth ? 'User is authenticated' : 'User is not authenticated'); + /// ``` @override Future isAuthenticated() async { if (_hasValidatedOnce && !isNullOrEmpty(_encryptedAccessToken)) { @@ -88,6 +143,15 @@ final class UsefulJwtAuth extends IAuthorization { return false; } + /// Validates the current authentication token. + /// + /// Returns `true` if the token is valid, `false` otherwise. + /// + /// Example: + /// ```dart + /// bool isValid = await auth.validate(); + /// print(isValid ? 'Token is valid' : 'Token is invalid'); + /// ``` @override Future validate() async { if (isNullOrEmpty(_encryptedAccessToken)) { @@ -114,6 +178,18 @@ final class UsefulJwtAuth extends IAuthorization { (response.data['code'] as String) == 'jwt_auth_valid_token'; } + /// Generates an authentication URL with the current token. + /// + /// Returns the authentication header as a string, or `null` if not authenticated. + /// + /// Example: + /// ```dart + /// String? authHeader = await auth.generateAuthUrl(); + /// if (authHeader != null) { + /// print('Auth header: $authHeader'); + /// // Use authHeader in your API requests + /// } + /// ``` @override Future generateAuthUrl() async { if (!await isAuthenticated()) { @@ -123,11 +199,15 @@ final class UsefulJwtAuth extends IAuthorization { return '$scheme $_encryptedAccessToken'; } + /// Configures the Dio client for this authentication method. + /// + /// This method is called internally by the WordPress client. @override void clientFactoryProvider(Dio client) { _client = client; } + /// The authentication scheme used. Returns 'Bearer' for UsefulJwtAuth. @override String get scheme => 'Bearer'; } diff --git a/lib/src/bootstrap_builder.dart b/lib/src/bootstrap_builder.dart index 3d951c0..927b93d 100644 --- a/lib/src/bootstrap_builder.dart +++ b/lib/src/bootstrap_builder.dart @@ -1,4 +1,4 @@ -// ignore_for_file: avoid_positional_boolean_parameters, avoid_returning_this +// ignore_for_file: avoid_positional_boolean_parameters import 'package:dio/dio.dart'; @@ -9,9 +9,17 @@ import 'constants.dart'; import 'middleware/wordpress_middleware_base.dart'; import 'utilities/typedefs.dart'; +/// A builder class for creating a [BootstrapConfiguration] with a fluent API. +/// +/// This class allows for easy configuration of various WordPress client settings +/// through method chaining. class BootstrapBuilder { + /// Creates a new [BootstrapBuilder] instance. BootstrapBuilder(); + /// Creates a [BootstrapBuilder] instance from an existing [BootstrapConfiguration]. + /// + /// This constructor initializes the builder with the values from the provided configuration. BootstrapBuilder.fromConfiguration(BootstrapConfiguration config) { _debugMode = config.enableDebugMode; _statisticsDelegate = config.statisticsDelegate; @@ -25,8 +33,7 @@ class BootstrapBuilder { _followRedirects = config.shouldFollowRedirects; _middlewares = config.middlewares; } - - Duration _defaultRequestTimeout = DEFAULT_REQUEST_TIMEOUT; // 60 seconds + Duration _defaultRequestTimeout = DEFAULT_REQUEST_TIMEOUT; bool Function(dynamic)? _responsePreprocessorDelegate; IAuthorization? _defaultAuthorization; String? _defaultUserAgent; @@ -38,52 +45,61 @@ class BootstrapBuilder { bool _debugMode = false; List? _middlewares; - /// Attaches [LogInterceptor] to the [Dio] instance. + /// Enables or disables debug mode. + /// + /// When enabled, this attaches a [LogInterceptor] to the [Dio] instance. BootstrapBuilder withDebugMode(bool value) { _debugMode = value; return this; } + /// Adds a single middleware to the configuration. BootstrapBuilder withMiddleware(IWordpressMiddleware middleware) { _middlewares ??= []; _middlewares!.add(middleware); return this; } + /// Adds multiple middlewares to the configuration. BootstrapBuilder withMiddlewares(Iterable middlewares) { _middlewares ??= []; _middlewares!.addAll(middlewares); return this; } + /// Adds a Dio interceptor to the configuration. BootstrapBuilder withDioInterceptor(Interceptor interceptor) { _interceptors ??= []; _interceptors!.add(interceptor); return this; } + /// Sets the statistics delegate for collecting request statistics. BootstrapBuilder withStatisticDelegate(StatisticsCallback? delegate) { _statisticsDelegate = delegate; return this; } + /// Sets the default request timeout. BootstrapBuilder withRequestTimeout(Duration timeout) { _defaultRequestTimeout = timeout; return this; } + /// Sets a response preprocessor function. BootstrapBuilder withResponsePreprocessor( - bool Function(dynamic) responsePreprocessor, - ) { - _responsePreprocessorDelegate = responsePreprocessor; + bool Function(dynamic) preprocessor) { + _responsePreprocessorDelegate = preprocessor; return this; } + /// Sets the default authorization for requests. BootstrapBuilder withDefaultAuthorization(IAuthorization authorization) { _defaultAuthorization = authorization; return this; } + /// Sets the default authorization using a builder function. BootstrapBuilder withDefaultAuthorizationBuilder( IAuthorization Function(AuthorizationBuilder) builder, ) { @@ -91,26 +107,31 @@ class BootstrapBuilder { return this; } + /// Sets the default User-Agent header for requests. BootstrapBuilder withDefaultUserAgent(String userAgent) { _defaultUserAgent = userAgent; return this; } + /// Sets default headers for all requests. BootstrapBuilder withDefaultHeaders(Map headers) { _defaultHeaders = headers; return this; } - BootstrapBuilder withFollowRedirects(bool followRedirects) { - _followRedirects = followRedirects; + /// Configures whether to follow redirects automatically. + BootstrapBuilder withFollowRedirects(bool follow) { + _followRedirects = follow; return this; } - BootstrapBuilder withDefaultMaxRedirects(int defaultMaxRedirects) { - _defaultMaxRedirects = defaultMaxRedirects; + /// Sets the maximum number of redirects to follow. + BootstrapBuilder withMaxRedirects(int maxRedirects) { + _defaultMaxRedirects = maxRedirects; return this; } + /// Builds and returns a [BootstrapConfiguration] instance with the configured settings. BootstrapConfiguration build() { return BootstrapConfiguration( receiveTimeout: _defaultRequestTimeout, diff --git a/lib/src/cache/cache_entry.dart b/lib/src/cache/cache_entry.dart new file mode 100644 index 0000000..ff8d3c6 --- /dev/null +++ b/lib/src/cache/cache_entry.dart @@ -0,0 +1,24 @@ +import 'package:meta/meta.dart'; + +/// Represents an entry in the cache with a value and optional expiry time. +@immutable +class CacheEntry { + /// Creates a new [CacheEntry] with the given [value] and optional [expiryTime]. + /// + /// [value] The data to be stored in the cache. + /// [expiryTime] Optional. The time at which this entry should be considered expired. + const CacheEntry(this.value, this.expiryTime); + + /// The data stored in this cache entry. + final T value; + + /// The time at which this entry should be considered expired. + /// If null, the entry does not expire. + final DateTime? expiryTime; + + /// Checks if the cache entry has expired. + /// + /// Returns true if [expiryTime] is set and has passed, false otherwise. + bool get isExpired => + expiryTime != null && DateTime.now().isAfter(expiryTime!); +} diff --git a/lib/src/cache/cache_manager_base.dart b/lib/src/cache/cache_manager_base.dart new file mode 100644 index 0000000..a466f01 --- /dev/null +++ b/lib/src/cache/cache_manager_base.dart @@ -0,0 +1,32 @@ +import 'dart:async'; + +/// An abstract class defining the interface for a cache manager. +/// +/// This interface provides methods for basic cache operations such as +/// setting, getting, removing, and clearing cache entries. +abstract class ICacheManager { + const ICacheManager(); + + /// Stores a value in the cache with the specified key. + /// + /// [key] The unique identifier for the cache entry. + /// [value] The value to be stored in the cache. + /// [expiry] Optional duration after which the cache entry should expire. + FutureOr set(String key, T value, {Duration? expiry}); + + /// Retrieves a value from the cache using the specified key. + /// + /// [key] The unique identifier for the cache entry. + /// [T] The expected type of the cached value. + /// + /// Returns a [FutureOr] that resolves to the cached value of type [T]. + FutureOr get(String key); + + /// Removes a specific entry from the cache. + /// + /// [key] The unique identifier of the cache entry to be removed. + FutureOr remove(String key); + + /// Clears all entries from the cache. + FutureOr clear(); +} diff --git a/lib/src/cache/exceptions/cache_exception_base.dart b/lib/src/cache/exceptions/cache_exception_base.dart new file mode 100644 index 0000000..750f394 --- /dev/null +++ b/lib/src/cache/exceptions/cache_exception_base.dart @@ -0,0 +1,9 @@ +abstract base class CacheExceptionBase implements Exception { + const CacheExceptionBase({ + required this.message, + this.cause, + }); + + final String message; + final Exception? cause; +} diff --git a/lib/src/cache/exceptions/cache_expired_exception.dart b/lib/src/cache/exceptions/cache_expired_exception.dart new file mode 100644 index 0000000..8c59152 --- /dev/null +++ b/lib/src/cache/exceptions/cache_expired_exception.dart @@ -0,0 +1,8 @@ +import 'cache_exception_base.dart'; + +final class CacheExpiredException extends CacheExceptionBase { + const CacheExpiredException({ + super.cause, + super.message = 'Cache expired', + }); +} diff --git a/lib/src/cache/exceptions/cache_not_exists_exception.dart b/lib/src/cache/exceptions/cache_not_exists_exception.dart new file mode 100644 index 0000000..9311da8 --- /dev/null +++ b/lib/src/cache/exceptions/cache_not_exists_exception.dart @@ -0,0 +1,8 @@ +import 'cache_exception_base.dart'; + +final class CacheNotExistsException extends CacheExceptionBase { + const CacheNotExistsException({ + super.cause, + super.message = 'Cache with the specified key does not exist.', + }); +} diff --git a/lib/src/cache/stores/memory_cache_store.dart b/lib/src/cache/stores/memory_cache_store.dart new file mode 100644 index 0000000..a3a1247 --- /dev/null +++ b/lib/src/cache/stores/memory_cache_store.dart @@ -0,0 +1,37 @@ +import '../cache_entry.dart'; +import '../cache_manager_base.dart'; + +class MemoryCacheStore implements ICacheManager { + MemoryCacheStore(); + + final Map> _cache = {}; + + @override + void set(String key, T value, {Duration? expiry}) { + final expiryTime = expiry != null ? DateTime.now().add(expiry) : null; + _cache[key] = CacheEntry(value, expiryTime); + } + + @override + T? get(String key) { + final entry = _cache[key]; + + if (entry == null || entry.isExpired) { + _cache.remove(key); + + return null; + } + + return entry.value; + } + + @override + void remove(String key) { + _cache.remove(key); + } + + @override + void clear() { + _cache.clear(); + } +} diff --git a/lib/src/client_configuration.dart b/lib/src/client_configuration.dart index 34218a8..a2c1904 100644 --- a/lib/src/client_configuration.dart +++ b/lib/src/client_configuration.dart @@ -5,8 +5,13 @@ import 'package:meta/meta.dart'; import '../wordpress_client.dart'; import 'constants.dart'; +/// Configuration class for bootstrapping the WordPress client. +/// +/// This class provides a fluent API for setting up various configuration options +/// for the WordPress client, including timeouts, authorization, headers, and more. @immutable final class BootstrapConfiguration { + /// Creates a new instance of [BootstrapConfiguration] with default or specified values. const BootstrapConfiguration({ this.receiveTimeout = DEFAULT_REQUEST_TIMEOUT, this.connectTimeout = DEFAULT_CONNECT_TIMEOUT, @@ -22,17 +27,40 @@ final class BootstrapConfiguration { this.middlewares, }); + /// Enables or disables debug mode. final bool enableDebugMode; + + /// The timeout duration for receiving a response. final Duration receiveTimeout; + + /// The timeout duration for establishing a connection. final Duration connectTimeout; + + /// A function to preprocess the response before it's handled by the client. final bool Function(dynamic)? responsePreprocessorDelegate; + + /// The default authorization to use for requests. final IAuthorization? defaultAuthorization; + + /// The default User-Agent header to use for requests. final String? defaultUserAgent; + + /// Default headers to include in all requests. final Map? defaultHeaders; + + /// Whether to follow redirects automatically. final bool shouldFollowRedirects; + + /// The maximum number of redirects to follow. final int maxRedirects; + + /// A list of interceptors to use for requests. final List? interceptors; + + /// A callback for collecting statistics about requests. final StatisticsCallback? statisticsDelegate; + + /// A list of middlewares to apply to requests. final List? middlewares; @override @@ -58,20 +86,23 @@ final class BootstrapConfiguration { @override int get hashCode { - return enableDebugMode.hashCode ^ - receiveTimeout.hashCode ^ - responsePreprocessorDelegate.hashCode ^ - defaultAuthorization.hashCode ^ - defaultUserAgent.hashCode ^ - defaultHeaders.hashCode ^ - shouldFollowRedirects.hashCode ^ - maxRedirects.hashCode ^ - interceptors.hashCode ^ - middlewares.hashCode ^ - connectTimeout.hashCode ^ - statisticsDelegate.hashCode; + return Object.hash( + enableDebugMode, + receiveTimeout, + responsePreprocessorDelegate, + defaultAuthorization, + defaultUserAgent, + defaultHeaders, + shouldFollowRedirects, + maxRedirects, + interceptors, + middlewares, + connectTimeout, + statisticsDelegate, + ); } + /// Creates a copy of this configuration with the given fields replaced with new values. BootstrapConfiguration copyWith({ bool? enableDebugMode, Duration? receiveTimeout, @@ -82,7 +113,6 @@ final class BootstrapConfiguration { bool? shouldFollowRedirects, int? maxRedirects, List? interceptors, - bool? synchronized, StatisticsCallback? statisticsDelegate, List? middlewares, Duration? connectTimeout, diff --git a/lib/src/constants.dart b/lib/src/constants.dart index 4e7dd0f..9d701cf 100644 --- a/lib/src/constants.dart +++ b/lib/src/constants.dart @@ -1,3 +1,32 @@ +/// The default timeout duration for HTTP requests. +/// +/// This constant defines the maximum amount of time allowed for an HTTP request +/// to complete before it times out. If a request takes longer than this duration, +/// it will be terminated, and an error will be thrown. +/// +/// The value is set to 30 seconds, which is generally sufficient for most API +/// calls, but can be adjusted if needed for specific use cases. const Duration DEFAULT_REQUEST_TIMEOUT = Duration(seconds: 30); + +/// The default timeout duration for establishing a connection. +/// +/// This constant specifies the maximum time allowed for the initial connection +/// to be established with the server. If the connection cannot be made within +/// this timeframe, the request will fail with a connection timeout error. +/// +/// Like the request timeout, this is also set to 30 seconds, providing a +/// balance between allowing enough time for connections in various network +/// conditions and failing quickly in case of connectivity issues. const Duration DEFAULT_CONNECT_TIMEOUT = Duration(seconds: 30); + +/// The header key used to identify local middleware. +/// +/// This constant defines the HTTP header key that is used to indicate that +/// a request has been processed by local middleware. It allows for tracking +/// and managing the flow of requests through various middleware components +/// in the WordPress client. +/// +/// The value 'X-Local-Middleware' follows the convention of using 'X-' prefix +/// for custom headers, making it clear that this is a non-standard header +/// specific to this WordPress client implementation. const String MIDDLEWARE_HEADER_KEY = 'X-Local-Middleware'; diff --git a/lib/src/enums.dart b/lib/src/enums.dart index fd6ea85..9aac39e 100644 --- a/lib/src/enums.dart +++ b/lib/src/enums.dart @@ -1,163 +1,332 @@ // ignore_for_file: constant_identifier_names +/// Represents various error types that can occur in the WordPress client. enum ErrorType { + /// Interface does not exist interfaceNotExist, + + /// Interface already exists interfaceAlreadyExist, + + /// Request failed internally requestFailedInternally, + + /// Request failed requestFailed, + + /// Client is not ready clientNotReady, + + /// Authorization failed authorizationFailed, + + /// Bootstrap process failed bootstrapFailed, + + /// File doesn't exist fileDoesntExist, + + /// Interface does not exist interfaceDoNotExist, + + /// Interface exists interfaceExist, + + /// Interface is not initialized interfaceNotInitialized, + + /// Invalid interface invalidInterface, + + /// Discovery is pending discoveryPending, + + /// Discovery failed discoveryFailed, + + /// Null reference encountered nullReference, + + /// Request URI parsing failed requestUriParsingFailed, } +/// Represents specific error types that can occur during a request. enum RequestErrorType { + /// No error occurred noError, + + /// Unknown error unknown, + + /// Internal generic error internalGenericError, + + /// Authorization module not found authorizationModuleNotFound, + + /// Authorization failed with provided credentials authorizationFailedWithProvidedCredentials, + + /// Connection failed connectionFailed, + + /// Request was cancelled requestCancelled, + + /// Invalid status code received invalidStatusCode, + + /// Middleware aborted the request middlewareAborted, + + /// Middleware execution failed middlewareExecutionFailed, } +/// Represents different types of search operations. enum SearchType { + /// Search for posts post, + + /// Search for terms term, + + /// Search for post formats postFormat, } +/// Represents the status of an item (e.g., a post or comment). enum Status { + /// Item is open open, + + /// Item is closed closed, } +/// Represents the status of a comment. enum CommentStatus { + /// Comment is to be approved approve, + + /// Comment is approved approved, + + /// Comment is pending approval pending, } -/// Different HTTP Methods which is supported by the client. +/// Represents different HTTP methods supported by the client. enum HttpMethod { - /// Put Method + /// PUT method put, - /// Post Method + /// POST method post, - /// Get Method + /// GET method get, - /// Delete Method + /// DELETE method delete, - /// Update Method + /// UPDATE method update, - /// Head Method + /// HEAD method head, - /// Options Method + /// OPTIONS method options, - /// Patch Method + /// PATCH method patch, - /// Trace Method + /// TRACE method trace, } +/// Represents the order of results (ascending or descending). enum Order { + /// Ascending order asc, + + /// Descending order desc, } +/// Represents different criteria for ordering results. enum OrderBy { + /// Order by date date, + + /// Order by author author, + + /// Order by ID id, + + /// Order by included items include, + + /// Order by modification date modified, + + /// Order by parent parent, + + /// Order by relevance relevance, + + /// Order by slug slug, + + /// Order by included slugs include_slugs, + + /// Order by title title, + + /// Order by email email, + + /// Order by URL url, + + /// Order by name name, + + /// Order by registration date registered_date, + + /// Order by term group term_group, + + /// Order by description description, + + /// Order by count count, } +/// Represents different contexts for a request. enum RequestContext { + /// View context view, + + /// Embed context embed, + + /// Edit context edit, } +/// Represents the status for media filtering. enum MediaFilterStatus { + /// Inherit status from parent inherit, } +/// Represents the relation between taxonomy terms. enum TaxonomyRelation { + /// AND relation and, + + /// OR relation or, } +/// Represents different statuses for content (e.g., posts). enum ContentStatus { + /// Published content publish, + + /// Scheduled content future, + + /// Draft content draft, + + /// Pending content pending, + + /// Private content private, } +/// Represents different post formats. enum PostFormat { + /// Standard post format standard, + + /// Aside post format aside, + + /// Chat post format chat, + + /// Gallery post format gallery, + + /// Link post format link, + + /// Image post format image, + + /// Quote post format quote, + + /// Status post format status, + + /// Video post format video, + + /// Audio post format audio, } +/// Represents different types of authorization. enum AuthorizationType { + /// Basic JWT authorization basic_jwt, + + /// Useful JWT authorization useful_jwt, + + /// Basic authorization basic, } +/// Represents different locales. enum Locale { + /// English (United States) en_US, } +/// Represents different types of media. enum MediaType { + /// Image media type image, + + /// Video media type video, + + /// Text media type text, + + /// Application media type application, + + /// Audio media type audio, } +/// Converts a string value to a [ContentStatus] enum. +/// +/// If the value is null or doesn't match any enum value, returns [defaultValue]. ContentStatus getContentStatusFromValue( String? value, { ContentStatus defaultValue = ContentStatus.pending, @@ -166,12 +335,15 @@ ContentStatus getContentStatusFromValue( return defaultValue; } - return ContentStatus.values - .where((element) => element.name.toLowerCase() == value.toLowerCase()) - .firstOrNull ?? - defaultValue; + return ContentStatus.values.firstWhere( + (element) => element.name.toLowerCase() == value.toLowerCase(), + orElse: () => defaultValue, + ); } +/// Converts a string value to a [CommentStatus] enum. +/// +/// If the value is null or doesn't match any enum value, returns [defaultValue]. CommentStatus getCommentStatusFromValue( String? value, { CommentStatus defaultValue = CommentStatus.pending, @@ -180,12 +352,15 @@ CommentStatus getCommentStatusFromValue( return defaultValue; } - return CommentStatus.values - .where((element) => element.name.toLowerCase() == value.toLowerCase()) - .firstOrNull ?? - defaultValue; + return CommentStatus.values.firstWhere( + (element) => element.name.toLowerCase() == value.toLowerCase(), + orElse: () => defaultValue, + ); } +/// Converts a string value to a [MediaFilterStatus] enum. +/// +/// If the value is null or doesn't match any enum value, returns [defaultValue]. MediaFilterStatus getMediaFilterStatusFromValue( String? value, { MediaFilterStatus defaultValue = MediaFilterStatus.inherit, @@ -194,12 +369,15 @@ MediaFilterStatus getMediaFilterStatusFromValue( return defaultValue; } - return MediaFilterStatus.values - .where((element) => element.name.toLowerCase() == value.toLowerCase()) - .firstOrNull ?? - defaultValue; + return MediaFilterStatus.values.firstWhere( + (element) => element.name.toLowerCase() == value.toLowerCase(), + orElse: () => defaultValue, + ); } +/// Converts a string value to a [PostFormat] enum. +/// +/// If the value is null or doesn't match any enum value, returns [defaultValue]. PostFormat getFormatFromValue( String? value, { PostFormat defaultValue = PostFormat.standard, @@ -208,19 +386,22 @@ PostFormat getFormatFromValue( return defaultValue; } - return PostFormat.values - .where((e) => e.name.toLowerCase() == value.toLowerCase()) - .firstOrNull ?? - defaultValue; + return PostFormat.values.firstWhere( + (e) => e.name.toLowerCase() == value.toLowerCase(), + orElse: () => defaultValue, + ); } +/// Converts a string value to a [Status] enum. +/// +/// If the value is null, empty, or doesn't match any enum value, returns [defaultValue]. Status getStatusFromValue(String? value, {Status defaultValue = Status.open}) { if (value == null || value.isEmpty) { return defaultValue; } - return Status.values - .where((element) => element.name.toLowerCase() == value.toLowerCase()) - .firstOrNull ?? - defaultValue; + return Status.values.firstWhere( + (element) => element.name.toLowerCase() == value.toLowerCase(), + orElse: () => defaultValue, + ); } diff --git a/lib/src/interface/application_passwords.dart b/lib/src/interface/application_passwords.dart index d821a3e..9dc1305 100644 --- a/lib/src/interface/application_passwords.dart +++ b/lib/src/interface/application_passwords.dart @@ -1,6 +1,47 @@ import '../../wordpress_client.dart'; -/// Represents the application password interface. +/// Represents the application password interface for WordPress. +/// +/// This interface provides methods to manage application passwords, including: +/// - Creating new application passwords +/// - Deleting existing application passwords +/// - Listing all application passwords +/// - Retrieving specific application passwords +/// - Updating existing application passwords +/// +/// Example usage: +/// ```dart +/// final wpClient = WordPressClient('https://your-wordpress-site.com'); +/// final appPasswords = wpClient.applicationPasswords; +/// +/// // Create a new application password +/// final newPassword = await appPasswords.create(CreateApplicationPasswordRequest( +/// name: 'My App Password', +/// user: 1, // User ID +/// )); +/// +/// // List all application passwords +/// final passwords = await appPasswords.list(ListApplicationPasswordRequest()); +/// +/// // Retrieve a specific application password +/// final password = await appPasswords.retrieve(RetriveApplicationPasswordRequest( +/// id: newPassword.id, +/// user: 1, // User ID +/// )); +/// +/// // Update an application password +/// final updatedPassword = await appPasswords.update(UpdateApplicationPasswordRequest( +/// id: newPassword.id, +/// user: 1, // User ID +/// name: 'Updated App Password', +/// )); +/// +/// // Delete an application password +/// await appPasswords.delete(DeleteApplicationPasswordRequest( +/// id: newPassword.id, +/// user: 1, // User ID +/// )); +/// ``` final class ApplicationPasswordsInterface extends IRequestInterface with CreateOperation, diff --git a/lib/src/interface/category.dart b/lib/src/interface/category.dart index acbfc5c..df8cf15 100644 --- a/lib/src/interface/category.dart +++ b/lib/src/interface/category.dart @@ -1,6 +1,39 @@ import '../../wordpress_client.dart'; -/// Represents the category interface. +/// Represents the category interface for interacting with WordPress categories. +/// +/// This interface provides CRUD (Create, Read, Update, Delete) operations for categories. +/// It extends [IRequestInterface] and mixes in various operations to handle different +/// category-related tasks. +/// +/// Example usage: +/// ```dart +/// final wordpress = WordPressClient('https://your-wordpress-site.com/wp-json'); +/// final categoryInterface = wordpress.categories; +/// +/// // Create a new category +/// final newCategory = await categoryInterface.create( +/// CreateCategoryRequest(name: 'New Category'), +/// ); +/// +/// // Retrieve a category +/// final category = await categoryInterface.retrieve( +/// RetrieveCategoryRequest(id: 123), +/// ); +/// +/// // Update a category +/// final updatedCategory = await categoryInterface.update( +/// UpdateCategoryRequest(id: 123, name: 'Updated Category Name'), +/// ); +/// +/// // Delete a category +/// await categoryInterface.delete(DeleteCategoryRequest(id: 123)); +/// +/// // List categories +/// final categories = await categoryInterface.list( +/// ListCategoryRequest(perPage: 10, page: 1), +/// ); +/// ``` final class CategoryInterface extends IRequestInterface with CreateOperation, diff --git a/lib/src/interface/comments.dart b/lib/src/interface/comments.dart index 479f2ba..d0933b8 100644 --- a/lib/src/interface/comments.dart +++ b/lib/src/interface/comments.dart @@ -1,6 +1,42 @@ import '../../wordpress_client.dart'; -/// Represents the comment interface. +/// Represents the comment interface for interacting with WordPress comments. +/// +/// This interface provides methods for creating, deleting, retrieving, updating, +/// and listing comments in a WordPress site. +/// +/// Example usage: +/// +/// ```dart +/// final wp = WordPressClient('https://your-wordpress-site.com/wp-json'); +/// final commentInterface = wp.comments; +/// +/// // Create a new comment +/// final newComment = await commentInterface.create(CreateCommentRequest( +/// content: 'Great post!', +/// post: 123, +/// author: 'John Doe', +/// authorEmail: 'john@example.com', +/// )); +/// +/// // Retrieve a comment +/// final comment = await commentInterface.retrieve(RetrieveCommentRequest(id: 456)); +/// +/// // Update a comment +/// final updatedComment = await commentInterface.update(UpdateCommentRequest( +/// id: 456, +/// content: 'Updated comment content', +/// )); +/// +/// // Delete a comment +/// await commentInterface.delete(DeleteCommentRequest(id: 456)); +/// +/// // List comments +/// final comments = await commentInterface.list(ListCommentRequest( +/// post: 123, +/// status: 'approved', +/// )); +/// ``` final class CommentInterface extends IRequestInterface with CreateOperation, diff --git a/lib/src/interface/me.dart b/lib/src/interface/me.dart index 09efdc3..efeb16e 100644 --- a/lib/src/interface/me.dart +++ b/lib/src/interface/me.dart @@ -1,6 +1,34 @@ import '../../wordpress_client.dart'; -/// Represents the current user interface. +/// Represents the current user interface for interacting with the WordPress API. +/// +/// This class provides operations to manage the current user's account, including: +/// - Retrieving user information +/// - Updating user details +/// - Deleting the user account +/// +/// Usage examples: +/// +/// Retrieve current user information: +/// ```dart +/// final user = await interface.retrieve(RetrieveMeRequest()); +/// print(user.username); +/// ``` +/// +/// Update user information: +/// ```dart +/// final updatedUser = await interface.update(UpdateMeRequest( +/// firstName: 'John', +/// lastName: 'Doe', +/// )); +/// print('Updated name: ${updatedUser.firstName} ${updatedUser.lastName}'); +/// ``` +/// +/// Delete user account: +/// ```dart +/// await interface.delete(DeleteMeRequest()); +/// print('User account deleted'); +/// ``` final class MeInterface extends IRequestInterface with DeleteOperation, diff --git a/lib/src/interface/media.dart b/lib/src/interface/media.dart index 6b2d62f..1929a95 100644 --- a/lib/src/interface/media.dart +++ b/lib/src/interface/media.dart @@ -1,6 +1,39 @@ import '../../wordpress_client.dart'; -/// Represents the media interface. +/// Represents the media interface for interacting with WordPress media items. +/// +/// This interface provides operations to manage media files such as images, +/// videos, and documents in a WordPress site. +/// +/// Example usage: +/// ```dart +/// final wp = WordPressClient('https://example.com/wp-json'); +/// final mediaInterface = wp.media; +/// +/// // Create a new media item +/// final newMedia = await mediaInterface.create(CreateMediaRequest( +/// file: File('image.jpg'), +/// title: 'My Image', +/// )); +/// +/// // Retrieve a media item +/// final media = await mediaInterface.retrieve(RetrieveMediaRequest(id: 123)); +/// +/// // Update a media item +/// final updatedMedia = await mediaInterface.update(UpdateMediaRequest( +/// id: 123, +/// title: 'Updated Image Title', +/// )); +/// +/// // Delete a media item +/// await mediaInterface.delete(DeleteMediaRequest(id: 123)); +/// +/// // List media items +/// final mediaList = await mediaInterface.list(ListMediaRequest( +/// perPage: 10, +/// page: 1, +/// )); +/// ``` final class MediaInterface extends IRequestInterface with CreateOperation, diff --git a/lib/src/interface/page.dart b/lib/src/interface/page.dart index 9321ad1..daf579b 100644 --- a/lib/src/interface/page.dart +++ b/lib/src/interface/page.dart @@ -1,6 +1,41 @@ import '../../wordpress_client.dart'; -/// Represents the page interface. +/// Represents the interface for interacting with WordPress pages. +/// +/// This class provides methods for creating, retrieving, updating, deleting, +/// and listing pages in a WordPress site. +/// +/// Example usage: +/// +/// ```dart +/// final wordpress = WordPressClient('https://your-site.com/wp-json'); +/// final pagesInterface = wordpress.pages; +/// +/// // Create a new page +/// final newPage = await pagesInterface.create(CreatePageRequest( +/// title: 'My New Page', +/// content: 'This is the content of my new page.', +/// status: 'publish', +/// )); +/// +/// // Retrieve a page +/// final page = await pagesInterface.retrieve(RetrievePageRequest(id: 123)); +/// +/// // Update a page +/// final updatedPage = await pagesInterface.update(UpdatePageRequest( +/// id: 123, +/// title: 'Updated Page Title', +/// )); +/// +/// // Delete a page +/// await pagesInterface.delete(DeletePageRequest(id: 123)); +/// +/// // List pages +/// final pages = await pagesInterface.list(ListPageRequest( +/// perPage: 10, +/// page: 1, +/// )); +/// ``` final class PagesInterface extends IRequestInterface with CreateOperation, diff --git a/lib/src/interface/posts.dart b/lib/src/interface/posts.dart index 8e9355d..9bd3a10 100644 --- a/lib/src/interface/posts.dart +++ b/lib/src/interface/posts.dart @@ -1,6 +1,41 @@ import '../../wordpress_client.dart'; -/// Represents the post interface. +/// Represents the interface for interacting with WordPress posts. +/// +/// This class provides methods for creating, retrieving, updating, deleting, +/// and listing posts in a WordPress site. +/// +/// Example usage: +/// +/// ```dart +/// final wordpress = WordPressClient('https://your-wordpress-site.com'); +/// final postsInterface = wordpress.posts; +/// +/// // Create a new post +/// final newPost = await postsInterface.create(CreatePostRequest( +/// title: 'My New Post', +/// content: 'This is the content of my new post.', +/// status: 'publish', +/// )); +/// +/// // Retrieve a post +/// final post = await postsInterface.retrieve(RetrievePostRequest(id: 123)); +/// +/// // Update a post +/// final updatedPost = await postsInterface.update(UpdatePostRequest( +/// id: 123, +/// title: 'Updated Post Title', +/// )); +/// +/// // Delete a post +/// await postsInterface.delete(DeletePostRequest(id: 123)); +/// +/// // List posts +/// final posts = await postsInterface.list(ListPostRequest( +/// perPage: 10, +/// page: 1, +/// )); +/// ``` final class PostsInterface extends IRequestInterface with CreateOperation, diff --git a/lib/src/interface/search.dart b/lib/src/interface/search.dart index f37d4c7..d7924ae 100644 --- a/lib/src/interface/search.dart +++ b/lib/src/interface/search.dart @@ -1,5 +1,31 @@ import '../library_exports.dart'; -/// Represents the search interface. +/// Represents the search interface for interacting with WordPress search functionality. +/// +/// This interface provides methods for searching content across a WordPress site. +/// +/// Example usage: +/// +/// ```dart +/// final wordpress = WordPressClient('https://your-wordpress-site.com/wp-json'); +/// final searchInterface = wordpress.search; +/// +/// // Perform a search +/// final searchResults = await searchInterface.list(ListSearchRequest( +/// search: 'example query', +/// perPage: 10, +/// page: 1, +/// )); +/// +/// // Process search results +/// for (var result in searchResults) { +/// print('Title: ${result.title}'); +/// print('Type: ${result.type}'); +/// print('URL: ${result.url}'); +/// } +/// ``` +/// +/// The SearchInterface uses the ListOperation to perform searches, returning +/// a list of Search objects that match the given criteria. final class SearchInterface extends IRequestInterface with ListOperation {} diff --git a/lib/src/interface/tags.dart b/lib/src/interface/tags.dart index 6860e94..f02ac1f 100644 --- a/lib/src/interface/tags.dart +++ b/lib/src/interface/tags.dart @@ -1,6 +1,30 @@ import '../../wordpress_client.dart'; -/// Represents the tag interface. +/// Represents the tag interface for interacting with WordPress tags. +/// +/// This interface provides CRUD (Create, Read, Update, Delete) operations for tags. +/// It extends [IRequestInterface] and mixes in various operations to handle tag-related tasks. +/// +/// Example usage: +/// ```dart +/// final wordpress = WordPressClient('https://your-wordpress-site.com/wp-json'); +/// final tagInterface = wordpress.tags; +/// +/// // Create a new tag +/// final newTag = await tagInterface.create(CreateTagRequest(name: 'New Tag')); +/// +/// // Retrieve a tag +/// final tag = await tagInterface.retrieve(RetrieveTagRequest(id: 123)); +/// +/// // Update a tag +/// final updatedTag = await tagInterface.update(UpdateTagRequest(id: 123, name: 'Updated Tag')); +/// +/// // Delete a tag +/// await tagInterface.delete(DeleteTagRequest(id: 123)); +/// +/// // List tags +/// final tags = await tagInterface.list(ListTagRequest()); +/// ``` final class TagInterface extends IRequestInterface with CreateOperation, diff --git a/lib/src/interface/users.dart b/lib/src/interface/users.dart index b4d74e6..6ea3911 100644 --- a/lib/src/interface/users.dart +++ b/lib/src/interface/users.dart @@ -1,6 +1,42 @@ import '../../wordpress_client.dart'; -/// Represents the user interface. +/// Represents the user interface for managing WordPress users. +/// +/// This class provides methods for creating, deleting, retrieving, updating, +/// and listing users in a WordPress site. +/// +/// Example usage: +/// +/// ```dart +/// final wordpress = WordPressClient('https://your-site.com'); +/// final usersInterface = wordpress.users; +/// +/// // Create a new user +/// final newUser = await usersInterface.create(CreateUserRequest( +/// username: 'newuser', +/// email: 'newuser@example.com', +/// password: 'securepassword', +/// )); +/// +/// // Retrieve a user +/// final user = await usersInterface.retrieve(RetrieveUserRequest(id: 1)); +/// +/// // Update a user +/// final updatedUser = await usersInterface.update(UpdateUserRequest( +/// id: 1, +/// firstName: 'John', +/// lastName: 'Doe', +/// )); +/// +/// // Delete a user +/// await usersInterface.delete(DeleteUserRequest(id: 1)); +/// +/// // List users +/// final users = await usersInterface.list(ListUserRequest( +/// page: 1, +/// perPage: 10, +/// )); +/// ``` final class UsersInterface extends IRequestInterface with CreateOperation, diff --git a/lib/src/interface_key.dart b/lib/src/interface_key.dart index fefebf9..19922d2 100644 --- a/lib/src/interface_key.dart +++ b/lib/src/interface_key.dart @@ -2,26 +2,68 @@ import 'package:meta/meta.dart'; import 'utilities/helpers.dart'; +/// A class representing a unique key for an interface of type T. +/// +/// This class is used to create a unique identifier for interfaces, +/// combining the type T with an optional string key. +/// +/// Example: +/// ```dart +/// final userKey = InterfaceKey('primary'); +/// final postKey = InterfaceKey(); +/// ``` @immutable final class InterfaceKey { + /// Creates an [InterfaceKey] with an optional string key. + /// + /// If no key is provided, an empty string is used as the default. + /// + /// Example: + /// ```dart + /// final key1 = InterfaceKey('custom'); + /// final key2 = InterfaceKey(); // Uses default empty string + /// ``` const InterfaceKey([this._key = '']); + /// The type of the interface this key represents. Type get _type => typeOf(); + + /// An optional string to further specify the key. final String? _key; + /// Compares this [InterfaceKey] with another object for equality. + /// + /// Two [InterfaceKey]s are considered equal if they have the same hash code. @override bool operator ==(Object other) => hashCode == other.hashCode; + /// Generates a hash code for this [InterfaceKey]. + /// + /// The hash code is a combination of the type's hash code and the optional key's hash code. @override int get hashCode { return _type.hashCode ^ (_key?.hashCode ?? 0); } + /// Returns a string representation of this [InterfaceKey]. + /// + /// Example: + /// ```dart + /// final key = InterfaceKey('admin'); + /// print(key.toString()); // Outputs: InterfaceKeyadmin + /// ``` @override String toString() { return 'InterfaceKey<$_type>$_key'; } + /// Returns a more detailed string representation for debugging purposes. + /// + /// Example: + /// ```dart + /// final key = InterfaceKey('admin'); + /// print(key.toDebugString()); // Outputs: InterfaceKey(User, admin) + /// ``` String toDebugString() { final tag = _key == null ? '' : ', $_key'; return 'InterfaceKey<$_type>($_type$tag)'; diff --git a/lib/src/middleware/delegated_middleware.dart b/lib/src/middleware/delegated_middleware.dart index 52277a7..da873b3 100644 --- a/lib/src/middleware/delegated_middleware.dart +++ b/lib/src/middleware/delegated_middleware.dart @@ -2,21 +2,36 @@ import '../requests/wordpress_request.dart'; import '../responses/wordpress_raw_response.dart'; import 'middleware_exports.dart'; +/// A function type for modifying a WordPress request before it's sent. typedef OnRequestDelegate = Future Function( WordpressRequest request, ); +/// A function type for processing a WordPress response after it's received. typedef OnResponseDelegate = Future Function( WordpressRawResponse response, ); +/// A function type for initializing the middleware. typedef InitializeDelegate = Future Function(); + +/// A function type for cleaning up when the middleware is removed. typedef OnRemovedDelegate = Future Function(); + +/// A function type for custom execution logic in the middleware. typedef OnExecuteDelegate = Future Function( WordpressRequest request, ); +/// A middleware that delegates its functionality to provided functions. +/// +/// This allows for flexible and customizable middleware behavior without +/// needing to create a new class for each variation. final class DelegatedMiddleware extends IWordpressMiddleware { + /// Creates a new [DelegatedMiddleware] instance. + /// + /// [onRequestDelegate] and [onResponseDelegate] are required. + /// Other delegates are optional and will only be called if provided. const DelegatedMiddleware({ required this.onRequestDelegate, required this.onResponseDelegate, @@ -25,10 +40,19 @@ final class DelegatedMiddleware extends IWordpressMiddleware { this.onRemovedDelegate, }); + /// Called when the middleware is loaded. final InitializeDelegate? initializeDelegate; + + /// Called for each request passing through the middleware. final OnRequestDelegate onRequestDelegate; + + /// Called for each response passing through the middleware. final OnResponseDelegate onResponseDelegate; + + /// Called when the middleware is removed. final OnRemovedDelegate? onRemovedDelegate; + + /// Called for custom execution logic, if provided. final OnExecuteDelegate? onExecuteDelegate; @override diff --git a/lib/src/middleware/models/middleware_raw_response.dart b/lib/src/middleware/models/middleware_raw_response.dart index 9fc5f7e..958292c 100644 --- a/lib/src/middleware/models/middleware_raw_response.dart +++ b/lib/src/middleware/models/middleware_raw_response.dart @@ -1,4 +1,22 @@ +/// Represents a raw response from a middleware operation. +/// +/// This class encapsulates various components of an HTTP response, including +/// status code, headers, body, and additional metadata. final class MiddlewareRawResponse { + /// Creates a new [MiddlewareRawResponse] instance. + /// + /// [statusCode] and [body] are required parameters, while [message], [headers], + /// and [extra] are optional. + /// + /// Example: + /// ```dart + /// final response = MiddlewareRawResponse( + /// statusCode: 200, + /// body: {'data': 'example'}, + /// headers: {'Content-Type': 'application/json'}, + /// message: 'Success', + /// ); + /// ``` const MiddlewareRawResponse({ required this.statusCode, required this.body, @@ -7,16 +25,44 @@ final class MiddlewareRawResponse { this.extra, }); + /// Creates a default instance of [MiddlewareRawResponse] with a status code of -99 + /// and a null body. + /// + /// This can be used as a placeholder or for initialization purposes. + /// + /// Example: + /// ```dart + /// final defaultResponse = MiddlewareRawResponse.defaultInstance(); + /// print(defaultResponse.statusCode); // Outputs: -99 + /// ``` factory MiddlewareRawResponse.defaultInstance() { return const MiddlewareRawResponse(statusCode: -99, body: null); } + /// The HTTP status code of the response. final int statusCode; + + /// The headers of the HTTP response, if any. final Map? headers; + + /// Additional metadata or context information about the response. final Map? extra; + + /// The body of the HTTP response. Can be of any type. final dynamic body; + + /// An optional message associated with the response. final String? message; + /// Indicates whether the response contains valid data. + /// + /// Returns true if the body is not null and the status code is in the 2xx range. + /// + /// Example: + /// ```dart + /// final response = MiddlewareRawResponse(statusCode: 200, body: {'key': 'value'}); + /// print(response.hasData); // Outputs: true + /// ``` bool get hasData => body != null && statusCode >= 200 && statusCode < 300; @override diff --git a/lib/src/middleware/wordpress_middleware_base.dart b/lib/src/middleware/wordpress_middleware_base.dart index 4d46810..5638301 100644 --- a/lib/src/middleware/wordpress_middleware_base.dart +++ b/lib/src/middleware/wordpress_middleware_base.dart @@ -3,30 +3,93 @@ import 'dart:async'; import '../library_exports.dart'; /// The base interface for WordPress middleware. +/// +/// Middleware allows you to intercept and modify requests and responses +/// in the WordPress API client. This can be useful for tasks such as +/// authentication, caching, logging, or modifying request/response data. abstract class IWordpressMiddleware { const IWordpressMiddleware(); /// The name of the middleware. + /// + /// This should be a unique identifier for the middleware. + /// Example: 'AuthenticationMiddleware' String get name; /// Called when the middleware is loaded. + /// + /// Use this method to initialize any resources needed by the middleware. + /// Example: + /// ```dart + /// @override + /// Future onLoad() async { + /// await _initializeCache(); + /// } + /// ``` Future onLoad(); /// Called before sending a request to the WordPress server. + /// + /// This method allows you to modify the outgoing request. + /// Example: Adding an authentication token to the request headers + /// ```dart + /// @override + /// Future onRequest(WordpressRequest request) async { + /// return request.copyWith( + /// headers: {...request.headers, 'Authorization': 'Bearer $token'}, + /// ); + /// } + /// ``` Future onRequest(WordpressRequest request); /// Called before executing a request to the WordPress server. /// + /// This method can be used to return a custom response (e.g., a cached response) + /// or to cancel the request by throwing an exception. + /// /// By default, it returns a [MiddlewareRawResponse] with default values. /// - /// This can used for returning a custom response (cached response), or to cancel the request (By throwing an exception). + /// Example: Returning a cached response + /// ```dart + /// @override + /// Future onExecute(WordpressRequest request) async { + /// final cachedResponse = await _cache.get(request.url); + /// if (cachedResponse != null) { + /// return MiddlewareRawResponse( + /// statusCode: 200, + /// body: cachedResponse, + /// headers: {'X-Cache': 'HIT'}, + /// ); + /// } + /// return MiddlewareRawResponse.defaultInstance(); + /// } + /// ``` Future onExecute(WordpressRequest request) async { return MiddlewareRawResponse.defaultInstance(); } /// Called after receiving a response from the WordPress server. + /// + /// This method allows you to modify or process the incoming response. + /// Example: Logging the response + /// ```dart + /// @override + /// Future onResponse(WordpressRawResponse response) async { + /// _logger.info('Received response: ${response.statusCode}'); + /// return response; + /// } + /// ``` Future onResponse(WordpressRawResponse response); /// Called when the middleware is unloaded. + /// + /// Use this method to clean up any resources used by the middleware. + /// Example: + /// ```dart + /// @override + /// Future onUnload() async { + /// await _closeConnections(); + /// } + /// ``` Future onUnload(); } diff --git a/lib/src/operations/create.dart b/lib/src/operations/create.dart index e501b89..e8b0635 100644 --- a/lib/src/operations/create.dart +++ b/lib/src/operations/create.dart @@ -1,14 +1,32 @@ import '../library_exports.dart'; -/// Represents the create operation. +/// Represents the create operation for WordPress API requests. +/// +/// This mixin provides methods to create new resources using the WordPress API. +/// It is generic over the type [T] of the response data and [R] which extends [IRequest]. base mixin CreateOperation on IRequestInterface { + /// Creates a new resource using the provided [request]. + /// + /// This method sends a create request to the WordPress API and returns + /// a [WordpressResponse] containing the created resource of type [T]. + /// + /// [request] is an instance of [R] that contains the necessary data for the create operation. + /// + /// Returns a [Future] that resolves to a [WordpressResponse]. Future> create(R request) async { final wpRequest = await request.build(baseUrl); return executor.create(wpRequest); } - /// Returns the raw response for the given [request]. + /// Creates a new resource and returns the raw response for the given [request]. + /// + /// This method is similar to [create], but instead of parsing the response, + /// it returns the raw response from the WordPress API. + /// + /// [request] is an instance of [R] that contains the necessary data for the create operation. + /// + /// Returns a [Future] that resolves to a [WordpressRawResponse]. Future createRaw(R request) async { final wpRequest = await request.build(baseUrl); diff --git a/lib/src/operations/custom.dart b/lib/src/operations/custom.dart index 20acec4..61d3c1b 100644 --- a/lib/src/operations/custom.dart +++ b/lib/src/operations/custom.dart @@ -1,10 +1,21 @@ import '../library_exports.dart'; -/// Represents the custom operation. This mixin is used to create custom operations. +/// Represents a custom operation for interacting with WordPress APIs. +/// This mixin is used to create custom operations that can execute requests +/// and handle responses. base mixin CustomOperation on IRequestInterface { + /// Decodes the JSON response into the desired type [T]. + /// Implement this method to define how the API response should be parsed. T decode(dynamic json); - /// Executes the given [request] and returns the response. + /// Executes the given [request] and returns a typed response. + /// + /// This method performs the following steps: + /// 1. Builds the WordPress request using the provided [request] object. + /// 2. Executes the request using the [executor]. + /// 3. Decodes the response using the [decode] method. + /// + /// Returns a [WordpressResponse] containing the decoded data of type [T]. Future> request(R request) async { final wpRequest = await request.build(baseUrl); @@ -13,7 +24,14 @@ base mixin CustomOperation on IRequestInterface { return response.asResponse(decoder: decode); } - /// Returns the raw response for the given [request]. + /// Returns the raw response for the given [request] without decoding. + /// + /// This method is useful when you need access to the unprocessed API response. + /// It performs the following steps: + /// 1. Builds the WordPress request using the provided [request] object. + /// 2. Executes the request using the [executor] in raw mode. + /// + /// Returns a [WordpressRawResponse] containing the unprocessed API response. Future raw(R request) async { final wpRequest = await request.build(baseUrl); diff --git a/lib/src/operations/delete.dart b/lib/src/operations/delete.dart index f6de97d..1aa5783 100644 --- a/lib/src/operations/delete.dart +++ b/lib/src/operations/delete.dart @@ -1,16 +1,35 @@ import '../library_exports.dart'; -/// Represents the delete operation. +/// Represents the delete operation for WordPress API requests. +/// +/// This mixin provides methods to delete resources using the WordPress API. +/// It is generic over [R] which extends [IRequest]. base mixin DeleteOperation on IRequestInterface { + /// Deletes a resource using the provided [request]. + /// + /// This method sends a delete request to the WordPress API and returns + /// a [WordpressResponse] containing a boolean indicating the success of the operation. + /// + /// [request] is an instance of [R] that contains the necessary data for the delete operation. + /// + /// Returns a [Future] that resolves to a [WordpressResponse]. + /// The boolean value is typically true if the deletion was successful. Future> delete(R request) async { final wpRequest = await request.build(baseUrl); return executor.delete(wpRequest); } - /// Returns the raw response for the given [request]. + /// Deletes a resource and returns the raw response for the given [request]. + /// + /// This method is similar to [delete], but instead of parsing the response, + /// it returns the raw response from the WordPress API. + /// + /// [request] is an instance of [R] that contains the necessary data for the delete operation. + /// + /// Returns a [Future] that resolves to a [WordpressRawResponse]. /// - /// **Note that for delete responses, Wordpress API returns an empty response body.** + /// **Note: For delete operations, the WordPress API typically returns an empty response body.** Future deleteRaw(R request) async { final wpRequest = await request.build(baseUrl); diff --git a/lib/src/operations/list.dart b/lib/src/operations/list.dart index 4a9f2a8..72dcc6c 100644 --- a/lib/src/operations/list.dart +++ b/lib/src/operations/list.dart @@ -1,7 +1,19 @@ import '../../wordpress_client.dart'; -/// Represents the list operation. +/// Represents the list operation for WordPress API requests. +/// +/// This mixin provides methods to retrieve lists of resources using the WordPress API. +/// It is generic over the type [T] of the response data and [R] which extends [IRequest]. base mixin ListOperation on IRequestInterface { + /// Retrieves a list of resources using the provided [request]. + /// + /// This method sends a list request to the WordPress API and returns + /// a [WordpressResponse] containing a list of resources of type [T]. + /// + /// [request] is an instance of [R] that contains the necessary parameters for the list operation, + /// such as pagination, filtering, or sorting options. + /// + /// Returns a [Future] that resolves to a [WordpressResponse>]. Future>> list( R request, ) async { @@ -10,7 +22,15 @@ base mixin ListOperation on IRequestInterface { return executor.list(wpRequest); } - /// Returns the raw response for the given [request]. + /// Retrieves a list of resources and returns the raw response for the given [request]. + /// + /// This method is similar to [list], but instead of parsing the response, + /// it returns the raw response from the WordPress API. + /// + /// [request] is an instance of [R] that contains the necessary parameters for the list operation. + /// + /// Returns a [Future] that resolves to a [WordpressRawResponse]. + /// This can be useful for debugging or when you need access to the full, unprocessed API response. Future listRaw( R request, ) async { diff --git a/lib/src/operations/retrieve.dart b/lib/src/operations/retrieve.dart index 589492b..70a0ecd 100644 --- a/lib/src/operations/retrieve.dart +++ b/lib/src/operations/retrieve.dart @@ -1,14 +1,28 @@ import '../../wordpress_client.dart'; -/// Represents the retrive operation. +/// Represents the retrieve operation for WordPress API requests. +/// +/// This mixin provides methods to retrieve data from a WordPress site +/// using the WordPress REST API. base mixin RetrieveOperation on IRequestInterface { + /// Retrieves data from the WordPress API and returns a typed response. + /// + /// [request] is the request object containing the necessary parameters. + /// + /// Returns a [Future] that resolves to a [WordpressResponse] containing + /// the retrieved data of type [T]. Future> retrieve(R request) async { final wpRequest = await request.build(baseUrl); return executor.retrive(wpRequest); } - /// Returns the raw response for the given [request]. + /// Retrieves raw data from the WordPress API. + /// + /// [request] is the request object containing the necessary parameters. + /// + /// Returns a [Future] that resolves to a [WordpressRawResponse] containing + /// the raw response data from the API. Future retrieveRaw(R request) async { final wpRequest = await request.build(baseUrl); diff --git a/lib/src/operations/update.dart b/lib/src/operations/update.dart index a16afa9..ad8fb53 100644 --- a/lib/src/operations/update.dart +++ b/lib/src/operations/update.dart @@ -1,14 +1,29 @@ import '../../wordpress_client.dart'; -/// Represents the update operation. +/// Represents the update operation for WordPress API requests. +/// +/// This mixin provides methods to perform update operations on WordPress resources. +/// It is designed to be used with classes that implement [IRequestInterface]. base mixin UpdateOperation on IRequestInterface { + /// Performs an update operation using the provided [request]. + /// + /// [T] is the type of the expected response data. + /// [R] is the type of the request, which must extend [IRequest]. + /// + /// Returns a [Future] that resolves to a [WordpressResponse] containing + /// the updated resource data. Future> update(R request) async { final wpRequest = await request.build(baseUrl); return executor.update(wpRequest); } - /// Returns the raw response for the given [request]. + /// Performs an update operation and returns the raw response for the given [request]. + /// + /// This method is useful when you need access to the full, unprocessed API response. + /// + /// Returns a [Future] that resolves to a [WordpressRawResponse] containing + /// the raw API response data. Future updateRaw(R request) async { final wpRequest = await request.build(baseUrl); diff --git a/lib/src/parallel_wordpress/exports.dart b/lib/src/parallel_wordpress/exports.dart index f228d09..582b116 100644 --- a/lib/src/parallel_wordpress/exports.dart +++ b/lib/src/parallel_wordpress/exports.dart @@ -1,3 +1,4 @@ +export 'extensions/parallel_result_exts.dart'; export 'parallel_request.dart'; export 'parallel_result.dart'; export 'parallel_wordpress.dart'; diff --git a/lib/src/parallel_wordpress/extensions/parallel_result_exts.dart b/lib/src/parallel_wordpress/extensions/parallel_result_exts.dart new file mode 100644 index 0000000..d526457 --- /dev/null +++ b/lib/src/parallel_wordpress/extensions/parallel_result_exts.dart @@ -0,0 +1,46 @@ +import '../parallel_result.dart'; + +/// Extension methods for [Iterable>]. +extension ParallelResultExts on Iterable> { + /// Converts this iterable of [ParallelResult] into a [Stream]. + /// + /// This method allows you to work with the results as a stream, which can be + /// useful for processing results asynchronously or applying stream operations. + /// + /// Example: + /// ```dart + /// final results = [ParallelResult(1), ParallelResult(2), ParallelResult(3)]; + /// final stream = results.streamed(); + /// await for (final result in stream) { + /// print(result.value); + /// } + /// ``` + Stream> streamed() { + return Stream.fromIterable(this); + } +} + +/// Extension methods for [Iterable>]. +extension ParallelIterableResultExts on Iterable> { + /// Converts this iterable of [ParallelIterableResult] into a [Stream]. + /// + /// This method is particularly useful when working with results that contain + /// iterables, allowing you to process them as a stream. + /// + /// Example: + /// ```dart + /// final results = [ + /// ParallelIterableResult(['a', 'b']), + /// ParallelIterableResult(['c', 'd']), + /// ]; + /// final stream = results.streamed(); + /// await for (final result in stream) { + /// for (final item in result.value) { + /// print(item); + /// } + /// } + /// ``` + Stream> streamed() { + return Stream.fromIterable(this); + } +} diff --git a/lib/src/parallel_wordpress/parallel_result.dart b/lib/src/parallel_wordpress/parallel_result.dart index dcce6a8..8d1df73 100644 --- a/lib/src/parallel_wordpress/parallel_result.dart +++ b/lib/src/parallel_wordpress/parallel_result.dart @@ -1,8 +1,8 @@ import 'package:meta/meta.dart'; @immutable -final class ParallelResult { - const ParallelResult({ +final class ParallelIterableResult { + const ParallelIterableResult({ required this.page, required this.results, }); @@ -16,7 +16,7 @@ final class ParallelResult { final Iterable results; @override - bool operator ==(covariant ParallelResult other) { + bool operator ==(covariant ParallelIterableResult other) { if (identical(this, other)) return true; return other.page == page && other.results == results; @@ -27,3 +27,29 @@ final class ParallelResult { T operator [](int index) => results.elementAt(index); } + +@immutable +final class ParallelResult { + const ParallelResult({ + required this.page, + required this.result, + }); + + /// The page number of the results. + /// + /// Note that, if this instance contains results from the `initial()` method, then the `page` will be `0`. + final int page; + + /// The results of the request. + final T result; + + @override + bool operator ==(covariant ParallelResult other) { + if (identical(this, other)) return true; + + return other.page == page && other.result == result; + } + + @override + int get hashCode => page.hashCode ^ result.hashCode; +} diff --git a/lib/src/parallel_wordpress/parallel_wordpress.dart b/lib/src/parallel_wordpress/parallel_wordpress.dart index f64bb14..d75b4e9 100644 --- a/lib/src/parallel_wordpress/parallel_wordpress.dart +++ b/lib/src/parallel_wordpress/parallel_wordpress.dart @@ -5,8 +5,8 @@ import 'package:collection/collection.dart'; import '../library_exports.dart'; import 'parallel_raw_result.dart'; -/// `ParallelWordpress` is a class that uses a `WordpressClient` to fetch data from a WordPress site. -/// It provides a method to fetch a list of items in parallel, which can significantly speed up the fetching process. +/// `ParallelWordpress` is a class that uses a `WordpressClient` to perform parallel operations on a WordPress site. +/// It provides methods to fetch, create, update, delete, and retrieve items in parallel, which can significantly speed up the process. final class ParallelWordpress { /// Constructs a `ParallelWordpress` instance. /// @@ -15,22 +15,24 @@ final class ParallelWordpress { required this.client, }); + /// The WordPress client used to make API requests. final WordpressClient client; /// Fetches a list of items of type `T` in parallel. /// - /// The `requestBuilder` parameter is a function that builds a list of requests to be made. - /// The `interface` parameter is a function that performs the list operation for each request. - /// The `transformer` parameter is an optional function that transforms the results of each request. - /// The `onException` parameter is an optional function that handles exceptions that occur during the processing of the requests. - /// The `initial` parameter is an optional function that provides an initial list of items. + /// Parameters: + /// - `requestBuilder`: A function that builds a list of requests to be made. + /// - `interface`: A function that performs the list operation for each request. + /// - `transformer`: An optional function that transforms the results of each request. + /// - `onException`: An optional function that handles exceptions that occur during the processing of the requests. + /// - `initial`: An optional function that provides an initial list of items. /// - /// Returns a `Future` that completes with a list of items of type `T`. - Future>> list({ + /// Returns a `Future` that completes with an `Iterable` of `ParallelIterableResult`. + Future>> list({ required RequestBuilder requestBuilder, required ListOperation interface, - ParallelResultTransformer? transformer, - ParallelExceptionHandler? onException, + ParallelIterableResultTransformer? transformer, + ParallelIterableExceptionHandler? onException, ParallelInitialItems? initial, }) async { final requests = await requestBuilder(); @@ -54,7 +56,7 @@ final class ParallelWordpress { final successResponse = response.results.asSuccess(); - return ParallelResult( + return ParallelIterableResult( page: response.page, results: successResponse.data, ); @@ -76,8 +78,265 @@ final class ParallelWordpress { return sortedResults; } - final initialItems = ParallelResult(page: 0, results: await initial()); + final initialItems = + ParallelIterableResult(page: 0, results: await initial()); return [initialItems, ...sortedResults]; } + + /// Creates multiple items of type `T` in parallel. + /// + /// Parameters: + /// - `requestBuilder`: A function that builds a list of requests to be made. + /// - `interface`: A function that performs the create operation for each request. + /// - `transformer`: An optional function that transforms the results of each request. + /// - `onException`: An optional function that handles exceptions that occur during the processing of the requests. + /// - `initial`: An optional function that provides an initial item. + /// + /// Returns a `Future` that completes with an `Iterable` of `ParallelResult`. + Future>> create({ + required RequestBuilder requestBuilder, + required CreateOperation interface, + ParallelResultTransformer? transformer, + ParallelExceptionHandler? onException, + ParallelInitialItem? initial, + }) async { + final requests = await requestBuilder(); + + final responses = await Future.wait( + requests.map((x) async { + return ParallelRawResult( + page: x.page, + results: await interface.create(x.request), + ); + }), + eagerError: true, + ); + + final results = await responses.mapAsync( + (response) async => guardAsync( + function: () async { + if (transformer != null) { + return await transformer(response.results); + } + + final successResponse = response.results.asSuccess(); + + return ParallelResult( + page: response.page, + result: successResponse.data, + ); + }, + onError: (error, stackTrace) async { + if (onException != null) { + return await onException(error); + } + + throw ParallelProcessingException(error, stackTrace); + }, + ), + ); + + final sortedResults = + results.sorted((a, b) => a.page.compareTo(b.page)).map((e) => e); + + if (initial == null) { + return sortedResults; + } + + final initialItem = ParallelResult(page: 0, result: await initial()); + return [initialItem, ...sortedResults]; + } + + /// Updates multiple items of type `T` in parallel. + /// + /// Parameters: + /// - `requestBuilder`: A function that builds a list of requests to be made. + /// - `interface`: A function that performs the update operation for each request. + /// - `transformer`: An optional function that transforms the results of each request. + /// - `onException`: An optional function that handles exceptions that occur during the processing of the requests. + /// - `initial`: An optional function that provides an initial item. + /// + /// Returns a `Future` that completes with an `Iterable` of `ParallelResult`. + Future>> update({ + required RequestBuilder requestBuilder, + required UpdateOperation interface, + ParallelResultTransformer? transformer, + ParallelExceptionHandler? onException, + ParallelInitialItem? initial, + }) async { + final requests = await requestBuilder(); + + final responses = await Future.wait( + requests.map((x) async { + return ParallelRawResult( + page: x.page, + results: await interface.update(x.request), + ); + }), + eagerError: true, + ); + + final results = await responses.mapAsync( + (response) async => guardAsync( + function: () async { + if (transformer != null) { + return await transformer(response.results); + } + + final successResponse = response.results.asSuccess(); + + return ParallelResult( + page: response.page, + result: successResponse.data, + ); + }, + onError: (error, stackTrace) async { + if (onException != null) { + return await onException(error); + } + + throw ParallelProcessingException(error, stackTrace); + }, + ), + ); + + final sortedResults = + results.sorted((a, b) => a.page.compareTo(b.page)).map((e) => e); + + if (initial == null) { + return sortedResults; + } + + final initialItem = ParallelResult(page: 0, result: await initial()); + return [initialItem, ...sortedResults]; + } + + /// Deletes multiple items in parallel. + /// + /// Parameters: + /// - `requestBuilder`: A function that builds a list of requests to be made. + /// - `interface`: A function that performs the delete operation for each request. + /// - `transformer`: An optional function that transforms the results of each request. + /// - `onException`: An optional function that handles exceptions that occur during the processing of the requests. + /// - `initial`: An optional function that provides an initial boolean value. + /// + /// Returns a `Future` that completes with an `Iterable` of `ParallelResult`. + Future>> delete({ + required RequestBuilder requestBuilder, + required DeleteOperation interface, + ParallelResultTransformer? transformer, + ParallelExceptionHandler? onException, + ParallelInitialItem? initial, + }) async { + final requests = await requestBuilder(); + + final responses = await Future.wait( + requests.map((x) async { + return ParallelRawResult( + page: x.page, + results: await interface.delete(x.request), + ); + }), + eagerError: true, + ); + + final results = await responses.mapAsync( + (response) async => guardAsync( + function: () async { + if (transformer != null) { + return await transformer(response.results); + } + + final successResponse = response.results.asSuccess(); + + return ParallelResult( + page: response.page, + result: successResponse.data, + ); + }, + onError: (error, stackTrace) async { + if (onException != null) { + return await onException(error); + } + + throw ParallelProcessingException(error, stackTrace); + }, + ), + ); + + final sortedResults = + results.sorted((a, b) => a.page.compareTo(b.page)).map((e) => e); + + if (initial == null) { + return sortedResults; + } + + final initialItem = ParallelResult(page: 0, result: await initial()); + return [initialItem, ...sortedResults]; + } + + /// Retrieves multiple items of type `T` in parallel. + /// + /// Parameters: + /// - `requestBuilder`: A function that builds a list of requests to be made. + /// - `interface`: A function that performs the retrieve operation for each request. + /// - `transformer`: An optional function that transforms the results of each request. + /// - `onException`: An optional function that handles exceptions that occur during the processing of the requests. + /// - `initial`: An optional function that provides an initial item. + /// + /// Returns a `Future` that completes with an `Iterable` of `ParallelResult`. + Future>> retrieve({ + required RequestBuilder requestBuilder, + required RetrieveOperation interface, + ParallelResultTransformer? transformer, + ParallelExceptionHandler? onException, + ParallelInitialItem? initial, + }) async { + final requests = await requestBuilder(); + + final responses = await Future.wait( + requests.map((x) async { + return ParallelRawResult( + page: x.page, + results: await interface.retrieve(x.request), + ); + }), + eagerError: true, + ); + + final results = await responses.mapAsync( + (response) async => guardAsync( + function: () async { + if (transformer != null) { + return await transformer(response.results); + } + + final successResponse = response.results.asSuccess(); + + return ParallelResult( + page: response.page, + result: successResponse.data, + ); + }, + onError: (error, stackTrace) async { + if (onException != null) { + return await onException(error); + } + + throw ParallelProcessingException(error, stackTrace); + }, + ), + ); + + final sortedResults = + results.sorted((a, b) => a.page.compareTo(b.page)).map((e) => e); + + if (initial == null) { + return sortedResults; + } + + final initialItem = ParallelResult(page: 0, result: await initial()); + return [initialItem, ...sortedResults]; + } } diff --git a/lib/src/parallel_wordpress/typedefs.dart b/lib/src/parallel_wordpress/typedefs.dart index 8d33625..dd7bbdf 100644 --- a/lib/src/parallel_wordpress/typedefs.dart +++ b/lib/src/parallel_wordpress/typedefs.dart @@ -2,10 +2,20 @@ import 'dart:async'; import '../../wordpress_client.dart'; -typedef ParallelResultTransformer = FutureOr> Function( +typedef ParallelIterableResultTransformer + = FutureOr> Function( WordpressResponse> response, ); +typedef ParallelResultTransformer = FutureOr> Function( + WordpressResponse response, +); + +typedef ParallelIterableExceptionHandler + = FutureOr> Function( + Object error, +); + typedef ParallelExceptionHandler = FutureOr> Function( Object error, ); @@ -13,3 +23,4 @@ typedef ParallelExceptionHandler = FutureOr> Function( typedef RequestBuilder = FutureOr>> Function(); typedef ParallelInitialItems = FutureOr> Function(); +typedef ParallelInitialItem = FutureOr Function(); diff --git a/lib/src/request_executor_base.dart b/lib/src/request_executor_base.dart index f7672b9..b16f057 100644 --- a/lib/src/request_executor_base.dart +++ b/lib/src/request_executor_base.dart @@ -6,13 +6,30 @@ import 'library_exports.dart'; import 'responses/wordpress_error.dart'; import 'utilities/codable_map/codable_map.dart'; +/// Base class for request executors in the WordPress API client. +/// +/// This class provides the core functionality for executing requests, +/// handling middleware, and processing responses. abstract base class IRequestExecutor { + /// The base URL for the WordPress API. Uri get baseUrl; + /// The list of middleware to be applied to requests and responses. Iterable get middlewares; + /// Configures the executor with the given bootstrap configuration. + /// + /// This method should be called before making any requests. + /// + /// [configuration] The configuration to apply. void configure(BootstrapConfiguration configuration); + /// Applies request middleware to the given request. + /// + /// [request] The original request. + /// [middlewares] The list of middleware to apply. + /// + /// Returns the modified request after applying all middleware. Future _handleRequestMiddlewares({ required WordpressRequest request, required Iterable middlewares, @@ -23,6 +40,12 @@ abstract base class IRequestExecutor { ); } + /// Applies response middleware to the given response. + /// + /// [middlewares] The list of middleware to apply. + /// [response] The original response. + /// + /// Returns the modified response after applying all middleware. Future _handleResponseMiddlewares({ required Iterable middlewares, required WordpressRawResponse response, @@ -33,6 +56,13 @@ abstract base class IRequestExecutor { ); } + /// Executes a raw request and returns the response. + /// + /// This method applies middleware, executes the request, and handles errors. + /// + /// [request] The request to execute. + /// + /// Returns a [WordpressRawResponse] containing the raw response data. @internal Future raw(WordpressRequest request) async { return guardAsync( @@ -68,6 +98,13 @@ abstract base class IRequestExecutor { ); } + /// Creates a new resource using the given request. + /// + /// This method is used for POST requests to create new items in the WordPress API. + /// + /// [request] The request containing the data for the new resource. + /// + /// Returns a [WordpressResponse] with the created item of type [T]. @internal Future> create( WordpressRequest request, @@ -140,6 +177,13 @@ abstract base class IRequestExecutor { ); } + /// Retrieves a resource using the given request. + /// + /// This method is used for GET requests to fetch existing items from the WordPress API. + /// + /// [request] The request specifying the resource to retrieve. + /// + /// Returns a [WordpressResponse] with the retrieved item of type [T]. @internal Future> retrive( WordpressRequest request, @@ -212,6 +256,13 @@ abstract base class IRequestExecutor { ); } + /// Deletes a resource using the given request. + /// + /// This method is used for DELETE requests to remove items from the WordPress API. + /// + /// [request] The request specifying the resource to delete. + /// + /// Returns a [WordpressResponse] with a boolean indicating success or failure. @internal Future> delete( WordpressRequest request, @@ -281,6 +332,13 @@ abstract base class IRequestExecutor { ); } + /// Retrieves a list of resources using the given request. + /// + /// This method is used for GET requests to fetch multiple items from the WordPress API. + /// + /// [request] The request specifying the resources to list. + /// + /// Returns a [WordpressResponse] with a list of items of type [T]. @internal Future>> list( WordpressRequest request, @@ -355,6 +413,13 @@ abstract base class IRequestExecutor { ); } + /// Updates an existing resource using the given request. + /// + /// This method is used for PUT or PATCH requests to modify existing items in the WordPress API. + /// + /// [request] The request containing the updated data for the resource. + /// + /// Returns a [WordpressResponse] with the updated item of type [T]. @internal Future> update( WordpressRequest request, @@ -427,7 +492,13 @@ abstract base class IRequestExecutor { ); } - /// Executes the given [request] on the associated base url and returns the result in raw format. + /// Executes the given [request] on the associated base URL and returns the result in raw format. + /// + /// This method should be implemented by subclasses to perform the actual HTTP request. + /// + /// [request] The request to execute. + /// + /// Returns a [WordpressRawResponse] containing the raw response data. @internal Future execute(WordpressRequest request); } diff --git a/lib/src/requests/wordpress_request.dart b/lib/src/requests/wordpress_request.dart index 2c688e4..10faf51 100644 --- a/lib/src/requests/wordpress_request.dart +++ b/lib/src/requests/wordpress_request.dart @@ -3,8 +3,15 @@ import 'package:dio/dio.dart'; import '../../wordpress_client.dart'; import '../constants.dart'; -/// Represents a request to the Wordpress REST API. +/// Represents a request to the WordPress REST API. +/// +/// This class encapsulates all the necessary information for making a request +/// to the WordPress REST API, including the URL, method, headers, and other +/// configuration options. final class WordpressRequest { + /// Creates a new [WordpressRequest] instance. + /// + /// [url] and [method] are required parameters. All other parameters are optional. const WordpressRequest({ required this.url, required this.method, @@ -20,51 +27,54 @@ final class WordpressRequest { this.receiveTimeout = DEFAULT_REQUEST_TIMEOUT, }); - /// The request url. + /// The URL for the request. final RequestUrl url; - /// The request method. + /// The HTTP method for the request (e.g., GET, POST, PUT, DELETE). final HttpMethod method; - /// The request headers. + /// Optional headers to be included with the request. final Map? headers; - /// The request query parameters. + /// Optional query parameters to be appended to the URL. final Map? queryParameters; - /// The request body. + /// The body of the request, typically used for POST or PUT requests. final dynamic body; - /// Specifies if this request requires authentication. + /// Indicates whether this request requires authentication. final bool requireAuth; - /// The cancel token. + /// A token that can be used to cancel the request. final CancelToken? cancelToken; - /// The authorization instance. + /// The authorization instance to be used for this request, if required. final IAuthorization? authorization; - /// The request send timeout. + /// The timeout duration for sending the request. final Duration sendTimeout; - /// The request receive timeout. + /// The timeout duration for receiving the response. final Duration receiveTimeout; - /// The events instance. + /// An optional [WordpressEvents] instance for handling request lifecycle events. final WordpressEvents? events; - /// The validator callback. + /// An optional callback function for custom validation of the response. final ValidatorCallback? validator; - /// Gets if this request has events. + /// Indicates whether this request has associated events. bool get hasEvents => events != null; - /// Gets if this request has a validator overload. + /// Indicates whether this request has a custom validator. bool get hasValidator => validator != null; - /// Gets if this request has a authorization module. + /// Indicates whether this request has an associated authorization module. bool get hasAuthorization => authorization != null; + /// Creates a copy of this [WordpressRequest] with the given fields replaced with new values. + /// + /// This method is useful for modifying a request while keeping the original intact. WordpressRequest copyWith({ RequestUrl? url, HttpMethod? method, diff --git a/lib/src/responses/application_password_response.dart b/lib/src/responses/application_password_response.dart index 0376bc9..ab4da7d 100644 --- a/lib/src/responses/application_password_response.dart +++ b/lib/src/responses/application_password_response.dart @@ -3,8 +3,22 @@ import 'package:meta/meta.dart'; import '../library_exports.dart'; import '../utilities/self_representive_base.dart'; +/// Represents an application password in the WordPress system. +/// +/// Application passwords allow authentication for non-interactive systems, such as mobile or desktop applications, +/// without providing your actual password. Each application password has limited capabilities compared to your main account password. @immutable final class ApplicationPassword implements ISelfRespresentive { + /// Creates a new [ApplicationPassword] instance. + /// + /// - [uuid]: The unique identifier for this application password. + /// - [appId]: The ID of the application associated with this password. + /// - [name]: The name of the application password. + /// - [created]: The date and time when the password was created. + /// - [lastUsed]: The date and time when the password was last used. + /// - [lastIp]: The IP address from which the password was last used. + /// - [password]: The actual password string (only available when first created). + /// - [self]: The raw JSON representation of this object. const ApplicationPassword({ required this.uuid, required this.appId, @@ -16,6 +30,9 @@ final class ApplicationPassword implements ISelfRespresentive { this.password, }); + /// Creates an [ApplicationPassword] instance from a JSON map. + /// + /// [json] is the JSON map containing the application password data. factory ApplicationPassword.fromJson(Map json) { return ApplicationPassword( uuid: castOrElse(json['uuid']), @@ -29,17 +46,32 @@ final class ApplicationPassword implements ISelfRespresentive { ); } + /// The unique identifier for this application password. final String uuid; + + /// The ID of the application associated with this password. final String? appId; + + /// The name of the application password. final String name; + + /// The date and time when the password was created. final DateTime? created; + + /// The date and time when the password was last used. final DateTime? lastUsed; + + /// The IP address from which the password was last used. final String? lastIp; + + /// The actual password string (only available when first created). final String? password; + /// The raw JSON representation of this object. @override final Map self; + /// Converts this [ApplicationPassword] instance to a JSON map. Map toJson() { return { 'uuid': uuid, @@ -51,4 +83,35 @@ final class ApplicationPassword implements ISelfRespresentive { 'password': password, }; } + + @override + bool operator ==(covariant ApplicationPassword other) { + if (identical(this, other)) { + return true; + } + + return other.uuid == uuid && + other.appId == appId && + other.name == name && + other.created == created && + other.lastUsed == lastUsed && + other.lastIp == lastIp && + other.password == password; + } + + @override + int get hashCode { + return uuid.hashCode ^ + appId.hashCode ^ + name.hashCode ^ + created.hashCode ^ + lastUsed.hashCode ^ + lastIp.hashCode ^ + password.hashCode; + } + + @override + String toString() { + return 'ApplicationPassword(uuid: $uuid, appId: $appId, name: $name, created: $created, lastUsed: $lastUsed, lastIp: $lastIp, password: $password)'; + } } diff --git a/lib/src/responses/category_response.dart b/lib/src/responses/category_response.dart index 8df2164..6a960e5 100644 --- a/lib/src/responses/category_response.dart +++ b/lib/src/responses/category_response.dart @@ -4,8 +4,23 @@ import 'package:meta/meta.dart'; import '../utilities/helpers.dart'; import '../utilities/self_representive_base.dart'; +/// Represents a category in WordPress. +/// +/// Categories are a taxonomy in WordPress used to group and organize content. +/// They can be hierarchical, allowing for parent-child relationships between categories. @immutable class Category implements ISelfRespresentive { + /// Creates a new [Category] instance. + /// + /// - [id]: Unique identifier for the category. + /// - [count]: Number of posts in the category. + /// - [description]: HTML description of the category. + /// - [link]: URL to the category's archive page. + /// - [slug]: An alphanumeric identifier for the category unique to its type. + /// - [taxonomy]: Type of taxonomy. For categories, this is always "category". + /// - [parent]: The parent category ID, if any. + /// - [self]: The raw JSON representation of this object. + /// - [name]: HTML title for the category. const Category({ required this.id, required this.count, @@ -18,6 +33,9 @@ class Category implements ISelfRespresentive { this.name, }); + /// Creates a [Category] instance from a JSON map. + /// + /// [json] is the JSON map containing the category data. factory Category.fromJson(dynamic json) { return Category( id: castOrElse(json['id']), @@ -32,18 +50,35 @@ class Category implements ISelfRespresentive { ); } + /// Unique identifier for the category. final int id; + + /// Number of posts in the category. final int count; + + /// HTML description of the category. final String? description; + + /// URL to the category's archive page. final String? link; + + /// HTML title for the category. final String? name; + + /// An alphanumeric identifier for the category unique to its type. final String slug; + + /// Type of taxonomy. For categories, this is always "category". final String? taxonomy; + + /// The parent category ID, if any. final int? parent; + /// The raw JSON representation of this object. @override final Map self; + /// Converts this [Category] instance to a JSON map. Map toJson() { return { 'id': id, diff --git a/lib/src/responses/comment_response.dart b/lib/src/responses/comment_response.dart index 3774c5d..eae9a7c 100644 --- a/lib/src/responses/comment_response.dart +++ b/lib/src/responses/comment_response.dart @@ -7,8 +7,15 @@ import '../utilities/self_representive_base.dart'; import 'properties/avatar_urls.dart'; import 'properties/content.dart'; +/// Represents a comment in the WordPress system. +/// +/// This class encapsulates all the properties of a comment as defined in the WordPress REST API. +/// For more details, see: https://developer.wordpress.org/rest-api/reference/comments/ @immutable final class Comment implements ISelfRespresentive { + /// Creates a new [Comment] instance. + /// + /// All parameters correspond to the properties of a comment as defined in the WordPress REST API. const Comment({ required this.self, required this.status, @@ -29,6 +36,9 @@ final class Comment implements ISelfRespresentive { this.authorAvatarUrls, }); + /// Creates a [Comment] instance from a JSON map. + /// + /// [json] is the JSON map containing the comment data. factory Comment.fromJson(Map json) { return Comment( id: castOrElse(json['id']), @@ -59,26 +69,59 @@ final class Comment implements ISelfRespresentive { ); } + /// Unique identifier for the comment. final int id; + + /// The ID of the associated post. final int? post; + + /// The ID of the parent comment (if this is a reply). final int? parent; + + /// The ID of the user who authored the comment (if they were logged in). final int? author; + + /// Display name of the comment author. final String? authorName; + + /// Email address of the comment author. final String? authorEmail; + + /// URL provided by the comment author. final String authorUrl; + + /// IP address of the comment author. final String? authorIp; + + /// User agent of the browser used to submit the comment. final String? authorUserAgent; + + /// The date the comment was published, in the site's timezone. final DateTime? date; + + /// The date the comment was published, as GMT. final DateTime? dateGmt; + + /// The content of the comment. final Content? content; + + /// URL to the comment. final String link; + + /// Status of the comment (approved, pending, spam). final CommentStatus status; + + /// Type of the comment (typically 'comment'). final String type; + + /// Avatar URLs for the comment author. final AvatarUrls? authorAvatarUrls; + /// The raw JSON representation of this object. @override final Map self; + /// Converts this [Comment] instance to a JSON map. Map toJson() { return { 'id': id, diff --git a/lib/src/responses/media_response.dart b/lib/src/responses/media_response.dart index 5e14e8d..19a42eb 100644 --- a/lib/src/responses/media_response.dart +++ b/lib/src/responses/media_response.dart @@ -7,8 +7,17 @@ import '../utilities/self_representive_base.dart'; import 'properties/content.dart'; import 'properties/media_details.dart'; +/// Represents a media item in the WordPress system. +/// +/// This class encapsulates all the properties of a media item as defined in the WordPress REST API. +/// Media items are attachments, such as images, documents, or videos. +/// +/// For more details, see: https://developer.wordpress.org/rest-api/reference/media/ @immutable final class Media implements ISelfRespresentive { + /// Creates a new [Media] instance. + /// + /// All parameters correspond to the properties of a media item as defined in the WordPress REST API. const Media({ required this.self, required this.id, @@ -37,6 +46,9 @@ final class Media implements ISelfRespresentive { this.sourceUrl, }); + /// Creates a [Media] instance from a JSON map. + /// + /// [json] is the JSON map containing the media item data. factory Media.fromJson(Map json) { return Media( id: castOrElse(json['id']), @@ -84,34 +96,83 @@ final class Media implements ISelfRespresentive { ); } + /// Unique identifier for the media item. final int id; + + /// The date the media item was created, in the site's timezone. final DateTime? date; + + /// The date the media item was created, as GMT. final DateTime? dateGmt; + + /// The globally unique identifier for the media item. final Content? guid; + + /// The date the media item was last modified, in the site's timezone. final DateTime? modified; + + /// The date the media item was last modified, as GMT. final DateTime? modifiedGmt; + + /// An alphanumeric identifier for the media item unique to its type. final String? slug; + + /// The current status of the media item. final MediaFilterStatus? status; + + /// Type of media item. final String? type; + + /// URL to the media item. final String? link; + + /// The title of the media item. final Content? title; + + /// The ID of the user who uploaded the media item. final int? author; + + /// Whether or not comments are open on the media item. final Status? commentStatus; + + /// Whether or not the media item can be pinged. final Status? pingStatus; + + /// The theme file to use to display the media item. final String? template; + + /// Meta fields associated with the media item. final dynamic meta; + + /// The description of the media item. final Content? description; + + /// The caption for the media item. final Content? caption; + + /// The alternative text for the media item. final String? altText; + + /// The media type of the media item. final String? mediaType; + + /// The MIME type of the media item. final String? mimeType; + + /// Details about the media file, specific to its type. final MediaDetails? mediaDetails; + + /// The ID of the associated post of the media item. final int? post; + + /// The source URL of the media item. final String? sourceUrl; + /// The raw JSON representation of this object. @override final Map self; + /// Converts this [Media] instance to a JSON map. Map toJson() { return { 'id': id, diff --git a/lib/src/responses/page_response.dart b/lib/src/responses/page_response.dart index 5a1e615..b52b3cc 100644 --- a/lib/src/responses/page_response.dart +++ b/lib/src/responses/page_response.dart @@ -5,8 +5,19 @@ import '../../wordpress_client.dart'; import '../utilities/self_representive_base.dart'; import 'properties/content.dart'; +/// Represents a WordPress page. +/// +/// This class encapsulates all the properties of a WordPress page as defined in the +/// WordPress REST API. It provides methods to create, manipulate, and serialize page data. +/// +/// For more information, see the WordPress REST API handbook: +/// https://developer.wordpress.org/rest-api/reference/pages/ @immutable final class Page implements ISelfRespresentive { + /// Creates a new [Page] instance. + /// + /// All parameters correspond to the properties of a WordPress page as defined + /// in the REST API. const Page({ required this.id, required this.slug, @@ -30,6 +41,10 @@ final class Page implements ISelfRespresentive { this.featuredMedia, }); + /// Creates a [Page] instance from a JSON map. + /// + /// This factory constructor is used to deserialize page data received from + /// the WordPress REST API. factory Page.fromJson(Map json) { return Page( id: castOrElse(json['id']), @@ -64,29 +79,68 @@ final class Page implements ISelfRespresentive { ); } + /// The unique identifier for the page. final int id; + + /// The date the page was published, in the site's timezone. final DateTime? date; + + /// The date the page was published, as GMT. final DateTime? dateGmt; + + /// The globally unique identifier for the page. final Content? guid; + + /// The date the page was last modified, in the site's timezone. final DateTime? modified; + + /// The date the page was last modified, as GMT. final DateTime? modifiedGmt; + + /// An alphanumeric identifier for the page unique to its type. final String slug; + + /// A named status for the page. final ContentStatus status; + + /// Type of Post for the page. final String? type; + + /// URL to the page. final String? link; + + /// The title of the page. final Content? title; + + /// The content of the page. final Content? content; + + /// The ID of the user who published the page. final int author; + + /// The ID of the featured media for the page. final int? featuredMedia; + + /// Whether or not comments are open on the page. final Status commentStatus; + + /// Whether or not the page can be pinged. final Status pingStatus; + + /// The theme file to use to display the page. final String template; + + /// The ID of the parent page. final int parent; + + /// The order of the page in relation to other pages. final int menuOrder; + /// The raw data of the page as received from the API. @override final Map self; + /// Creates a copy of this [Page] but with the given fields replaced with the new values. Page copyWith({ int? id, DateTime? date, @@ -133,6 +187,9 @@ final class Page implements ISelfRespresentive { ); } + /// Converts this [Page] instance to a JSON map. + /// + /// This method is used to serialize the page data for sending to the WordPress REST API. Map toJson() { return { 'id': id, diff --git a/lib/src/responses/post_response.dart b/lib/src/responses/post_response.dart index f3519c6..a97f576 100644 --- a/lib/src/responses/post_response.dart +++ b/lib/src/responses/post_response.dart @@ -7,8 +7,14 @@ import '../utilities/self_representive_base.dart'; import 'properties/author_meta.dart'; import 'properties/content.dart'; +/// Represents a WordPress post. +/// +/// This class encapsulates all the properties of a post as defined in the +/// WordPress REST API. For more details, see: +/// https://developer.wordpress.org/rest-api/reference/posts/ @immutable class Post implements ISelfRespresentive { + /// Creates a new [Post] instance. const Post({ required this.id, required this.slug, @@ -38,6 +44,7 @@ class Post implements ISelfRespresentive { this.authorMeta, }); + /// Creates a [Post] instance from a JSON map. factory Post.fromJson(Map json) { return Post( id: castOrElse(json['id']), @@ -92,28 +99,73 @@ class Post implements ISelfRespresentive { ); } + /// The unique identifier for the post. final int id; + + /// The date the post was published, in the site's timezone. final DateTime? date; + + /// The date the post was published, as GMT. final DateTime? dateGmt; + + /// The globally unique identifier for the post. final Content? guid; + + /// A password to protect access to the content and excerpt. final String? password; + + /// The date the post was last modified, in the site's timezone. final DateTime? modified; + + /// The date the post was last modified, as GMT. final DateTime? modifiedGmt; + + /// An alphanumeric identifier for the post unique to its type. final String slug; + + /// A named status for the post. final ContentStatus status; + + /// Type of post. final String? type; + + /// URL to the post. final String link; + + /// The title for the post. final Content? title; + + /// The content for the post. final Content? content; + + /// The excerpt for the post. final Content? excerpt; + + /// The ID for the author of the post. final int author; + + /// The ID of the featured media for the post. final int? featuredMedia; + + /// Whether or not comments are open on the post. final Status commentStatus; + + /// Whether or not the post can accept pings. final Status pingStatus; + + /// Whether or not the post should be treated as sticky. final bool sticky; + + /// The theme file to use to display the post. final String? template; + + /// The format for the post. final PostFormat format; + + /// The terms assigned to the post in the category taxonomy. final List? categories; + + /// The terms assigned to the post in the tag taxonomy. final List? tags; /// Field generated by https://wordpress.org/plugins/rest-api-featured-image/ plugin @@ -125,6 +177,7 @@ class Post implements ISelfRespresentive { @override final Map self; + /// Converts the [Post] instance to a JSON map. Map toJson() { return { 'id': id, diff --git a/lib/src/responses/properties/author_meta.dart b/lib/src/responses/properties/author_meta.dart index 5fd5877..4c688a8 100644 --- a/lib/src/responses/properties/author_meta.dart +++ b/lib/src/responses/properties/author_meta.dart @@ -2,8 +2,20 @@ import 'package:meta/meta.dart'; import '../../utilities/helpers.dart'; +/// Represents metadata for an author in the WordPress REST API. +/// +/// This class encapsulates various properties related to an author. It provides a structured way to handle +/// author metadata in Dart applications interfacing with WordPress. +/// +/// This class is only used if you have the additional plugin integrated in your wordpress site. +/// +/// Reference: https://developer.wordpress.org/rest-api/reference/users/ @immutable final class AuthorMeta { + /// Creates an instance of [AuthorMeta]. + /// + /// [id] is required and represents the unique identifier for the author. + /// Other parameters are optional and correspond to various author attributes. const AuthorMeta({ required this.id, this.userNicename, @@ -11,6 +23,12 @@ final class AuthorMeta { this.displayName, }); + /// Creates an [AuthorMeta] instance from a JSON map. + /// + /// This factory constructor is used to deserialize author metadata from + /// the WordPress REST API response. + /// + /// [json] is a map containing the author metadata as returned by the API. factory AuthorMeta.fromJson(Map json) { return AuthorMeta( id: castOrElse(json['ID']), @@ -20,11 +38,33 @@ final class AuthorMeta { ); } + /// The unique identifier for the author. + /// + /// This corresponds to the 'ID' field in the WordPress REST API. final String id; + + /// The 'nice name' of the author, used for URLs and slugs. + /// + /// This corresponds to the 'user_nicename' field in the WordPress REST API. + /// It's typically a URL-friendly version of the user's name or username. final String? userNicename; + + /// The date and time when the author's account was registered. + /// + /// This corresponds to the 'user_registered' field in the WordPress REST API. + /// It represents the date and time when the user account was created. final DateTime? userRegistered; + + /// The display name of the author. + /// + /// This corresponds to the 'display_name' field in the WordPress REST API. + /// It's the name chosen by the user to be displayed publicly on the site. final String? displayName; + /// Converts the [AuthorMeta] instance to a JSON map. + /// + /// This method is used for serializing the author metadata, which can be + /// useful when sending data back to the WordPress REST API or storing it. Map toJson() { return { 'ID': id, diff --git a/lib/src/responses/properties/avatar_urls.dart b/lib/src/responses/properties/avatar_urls.dart index 231064a..d8478ae 100644 --- a/lib/src/responses/properties/avatar_urls.dart +++ b/lib/src/responses/properties/avatar_urls.dart @@ -2,14 +2,36 @@ import 'package:meta/meta.dart'; import '../../library_exports.dart'; +/// Represents the avatar URLs for a user in the WordPress REST API. +/// +/// This class encapsulates the URLs for different sizes of a user's avatar image. +/// The WordPress REST API provides avatar URLs in three standard sizes: 24x24, 48x48, and 96x96 pixels. +/// +/// According to the WordPress REST API Handbook: +/// - Avatar URLs are typically generated using the Gravatar service. +/// - The size of the avatar can be adjusted by modifying the URL parameters. +/// - If no avatar is available, a default image or generated avatar may be provided. +/// +/// For more information, see: +/// https://developer.wordpress.org/rest-api/reference/users/#schema-avatar_urls @immutable final class AvatarUrls { + /// Creates a new [AvatarUrls] instance. + /// + /// All parameters are optional and can be null if not provided. + /// - [small24]: URL for the 24x24 pixel avatar image. + /// - [medium48]: URL for the 48x48 pixel avatar image. + /// - [large96]: URL for the 96x96 pixel avatar image. const AvatarUrls({ this.small24, this.medium48, this.large96, }); + /// Creates an [AvatarUrls] instance from a JSON map. + /// + /// This factory constructor is used to deserialize JSON data returned by the WordPress REST API. + /// It uses the [castOrElse] utility function to safely cast values or provide defaults. factory AvatarUrls.fromJson(Map json) { return AvatarUrls( small24: castOrElse(json['24']), @@ -18,10 +40,18 @@ final class AvatarUrls { ); } + /// The URL for the 24x24 pixel avatar image. final String? small24; + + /// The URL for the 48x48 pixel avatar image. final String? medium48; + + /// The URL for the 96x96 pixel avatar image. final String? large96; + /// Converts this [AvatarUrls] instance to a JSON-compatible map. + /// + /// This method is useful for serializing the object, for example, when sending data to the API. Map toJson() { return { '24': small24, @@ -30,6 +60,10 @@ final class AvatarUrls { }; } + /// Creates a copy of this [AvatarUrls] instance with the given fields replaced with new values. + /// + /// This method is useful for updating specific fields of an [AvatarUrls] instance + /// while keeping the other fields unchanged. AvatarUrls copyWith({ String? small24, String? medium48, diff --git a/lib/src/responses/properties/content.dart b/lib/src/responses/properties/content.dart index 33bced8..0f4aa5c 100644 --- a/lib/src/responses/properties/content.dart +++ b/lib/src/responses/properties/content.dart @@ -2,13 +2,32 @@ import 'package:meta/meta.dart'; import '../../utilities/helpers.dart'; +/// Represents the content of a WordPress post or page. +/// +/// This class encapsulates the content data as returned by the WordPress REST API. +/// It includes both the rendered content and a flag indicating whether the content is protected. +/// +/// According to the WordPress REST API Handbook: +/// - The 'rendered' field contains the HTML content of the post, transformed for display. +/// - The 'protected' field indicates whether the content is password-protected. +/// +/// For more information, see: +/// https://developer.wordpress.org/rest-api/reference/posts/#schema-content @immutable final class Content { + /// Creates a new [Content] instance. + /// + /// [protected] is required and indicates whether the content is password-protected. + /// [rendered] is optional and contains the HTML content of the post. const Content({ required this.protected, this.rendered, }); + /// Creates a [Content] instance from a JSON map. + /// + /// This factory constructor is used to deserialize JSON data returned by the WordPress REST API. + /// It handles potential null values and type casting for the 'rendered' and 'protected' fields. factory Content.fromJson(Map json) { return Content( rendered: castOrElse(json['rendered']), @@ -16,11 +35,26 @@ final class Content { ); } + /// The HTML content of the post, transformed for display. + /// + /// This field may be null if the content is not available or not requested. final String? rendered; + + /// Indicates whether the content is password-protected. + /// + /// If true, the full content may not be available without authentication. final bool protected; + /// Returns the parsed text content, stripped of HTML tags. + /// + /// This getter uses the [parseHtmlString] utility function to convert the rendered HTML + /// to plain text. Note that this will throw an error if [rendered] is null. String get parsedText => parseHtmlString(rendered!); + /// Converts the [Content] instance to a JSON map. + /// + /// This method is useful for serializing the content data, potentially for sending + /// updates back to the WordPress REST API. Map toJson() { return { 'rendered': rendered, diff --git a/lib/src/responses/properties/extra_capabilities.dart b/lib/src/responses/properties/extra_capabilities.dart index d52d40d..458cc6d 100644 --- a/lib/src/responses/properties/extra_capabilities.dart +++ b/lib/src/responses/properties/extra_capabilities.dart @@ -2,20 +2,47 @@ import 'package:meta/meta.dart'; import '../../utilities/helpers.dart'; +/// Represents the extra capabilities of a user in the WordPress REST API. +/// +/// This class encapsulates additional capabilities that a user might have, +/// beyond the standard WordPress roles. In the current implementation, +/// it focuses on the 'administrator' capability, but it can be extended +/// to include other custom capabilities as needed. +/// +/// According to the WordPress REST API Handbook: +/// - Extra capabilities are specific to the site's configuration and plugins. +/// - They are typically used to grant or restrict access to certain features or content. +/// - The 'administrator' capability is one of the most powerful, granting full access to all areas of the WordPress admin. +/// +/// For more information, see: +/// https://developer.wordpress.org/rest-api/reference/users/#schema-extra_capabilities @immutable final class ExtraCapabilities { + /// Creates a new [ExtraCapabilities] instance. + /// + /// [administrator] is a required parameter indicating whether the user has administrator capabilities. const ExtraCapabilities({ required this.administrator, }); + /// Creates an [ExtraCapabilities] instance from a JSON map. + /// + /// This factory constructor is used to deserialize JSON data returned by the WordPress REST API. + /// It uses the [castOrElse] utility function to safely cast the 'administrator' value or provide a default. factory ExtraCapabilities.fromJson(Map json) { return ExtraCapabilities( administrator: castOrElse(json['administrator'], orElse: () => false)!, ); } + /// Indicates whether the user has administrator capabilities. + /// + /// If true, the user has full access to all areas of the WordPress admin. final bool administrator; + /// Converts this [ExtraCapabilities] instance to a JSON-compatible map. + /// + /// This method is useful for serializing the object, for example, when sending data to the API. Map toJson() { return { 'administrator': administrator, diff --git a/lib/src/responses/properties/image_meta.dart b/lib/src/responses/properties/image_meta.dart index c590e28..a8033a4 100644 --- a/lib/src/responses/properties/image_meta.dart +++ b/lib/src/responses/properties/image_meta.dart @@ -3,8 +3,17 @@ import 'package:meta/meta.dart'; import '../../utilities/helpers.dart'; +/// Represents metadata associated with an image in the WordPress REST API. +/// +/// This class encapsulates various metadata properties of an image, including +/// camera settings, copyright information, and descriptive details. +/// +/// Reference: https://developer.wordpress.org/rest-api/reference/media/#schema @immutable final class ImageMeta { + /// Creates an instance of [ImageMeta]. + /// + /// All parameters are optional and can be null if not provided. const ImageMeta({ this.aperture, this.credit, @@ -20,6 +29,10 @@ final class ImageMeta { this.keywords = const [], }); + /// Creates an [ImageMeta] instance from a JSON map. + /// + /// This factory constructor is used to deserialize JSON data into an [ImageMeta] object. + /// It uses the [castOrElse] utility function to safely cast values or provide defaults. factory ImageMeta.fromJson(Map json) { return ImageMeta( aperture: castOrElse(json['aperture']), @@ -43,19 +56,46 @@ final class ImageMeta { ); } + /// The aperture setting of the camera when the image was taken. final String? aperture; + + /// The credit or attribution for the image. final String? credit; + + /// The camera model used to take the image. final String? camera; + + /// The caption or description of the image. final String? caption; + + /// The timestamp when the image was created, typically in UNIX timestamp format. final String? createdTimestamp; + + /// The copyright information for the image. final String? copyright; + + /// The focal length of the camera lens when the image was taken. final String? focalLength; + + /// The ISO speed of the camera when the image was taken. final String? iso; + + /// The shutter speed of the camera when the image was taken. final String? shutterSpeed; + + /// The title of the image. final String? title; + + /// The orientation of the image (e.g., landscape, portrait). final String? orientation; + + /// A list of keywords or tags associated with the image. final List keywords; + /// Converts the [ImageMeta] instance to a JSON map. + /// + /// This method is used for serializing the object into a format that can be + /// easily converted to JSON for API requests or storage. Map toJson() { return { 'aperture': aperture, diff --git a/lib/src/responses/properties/link_container.dart b/lib/src/responses/properties/link_container.dart index 1aaef10..b255e73 100644 --- a/lib/src/responses/properties/link_container.dart +++ b/lib/src/responses/properties/link_container.dart @@ -2,8 +2,19 @@ import 'package:meta/meta.dart'; import '../../utilities/helpers.dart'; +/// Represents a link container in the WordPress REST API. +/// +/// This class encapsulates the properties of a link, which can be used in various +/// contexts within the WordPress ecosystem, such as taxonomies, post types, or +/// other linkable entities. +/// +/// For more information, see the WordPress REST API Handbook: +/// https://developer.wordpress.org/rest-api/ @immutable final class LinkContainer { + /// Creates a new [LinkContainer] instance. + /// + /// All parameters are optional and can be null. const LinkContainer({ this.id, this.name, @@ -14,6 +25,12 @@ final class LinkContainer { this.type, }); + /// Creates a [LinkContainer] instance from a JSON map. + /// + /// This factory constructor is used to deserialize JSON data received from + /// the WordPress REST API into a [LinkContainer] object. + /// + /// [json] is a map containing the JSON data. factory LinkContainer.fromJson(Map json) { return LinkContainer( id: castOrElse(json['id']), @@ -26,14 +43,45 @@ final class LinkContainer { ); } + /// The unique identifier for the link. + /// + /// This is typically an integer value assigned by WordPress. final int? id; + + /// The human-readable name of the link. + /// + /// This could be the name of a taxonomy term, post type, or other entity. final String? name; + + /// The taxonomy to which this link belongs, if applicable. + /// + /// For taxonomy terms, this will be the taxonomy slug (e.g., 'category', 'tag'). final String? taxonomy; + + /// Indicates whether the linked resource supports embedding. + /// + /// If true, the resource can be embedded in responses using the '_embed' parameter. final bool? embeddable; + + /// The count of items associated with this link, if applicable. + /// + /// For taxonomy terms, this might represent the number of posts in that term. final int? count; + + /// The URL of the linked resource. + /// + /// This is the full URL to the resource in the WordPress REST API. final String? href; + + /// The type of the linked resource. + /// + /// This could be 'post_type', 'taxonomy', or other resource types defined in WordPress. final String? type; + /// Converts the [LinkContainer] instance to a map. + /// + /// This method is useful for serializing the object, for example, when sending + /// data back to the WordPress REST API. Map toMap() { return { 'id': id, diff --git a/lib/src/responses/properties/links.dart b/lib/src/responses/properties/links.dart index 09f05b9..b38cd3f 100644 --- a/lib/src/responses/properties/links.dart +++ b/lib/src/responses/properties/links.dart @@ -6,8 +6,19 @@ import 'package:meta/meta.dart'; import '../../utilities/helpers.dart'; import 'link_container.dart'; +/// Represents the links associated with a WordPress REST API response. +/// +/// The `Links` class encapsulates various types of links that can be present in +/// a WordPress REST API response. These links provide navigation and relation +/// information for the current resource. +/// +/// For more information, see the WordPress REST API Handbook: +/// https://developer.wordpress.org/rest-api/reference/ @immutable final class Links { + /// Creates a new [Links] instance. + /// + /// All parameters are optional and can be null. const Links({ this.self, this.collection, @@ -22,6 +33,10 @@ final class Links { this.curies, }); + /// Creates a [Links] instance from a JSON map. + /// + /// This factory constructor parses the JSON map and creates the corresponding + /// [LinkContainer] objects for each link type. factory Links.fromJson(Map json) { return Links( self: mapIterableWithChecks( @@ -71,18 +86,43 @@ final class Links { ); } + /// The link to the resource itself. final List? self; + + /// The link to the collection containing the resource. final List? collection; + + /// The link to the resource's description. final List? about; + + /// The link to the author of the resource. final List? author; + + /// The link to the comments or replies for the resource. final List? replies; + + /// The link to the version history of the resource. final List? versionHistory; + + /// The link to the previous version of the resource. final List? predecessorVersion; + + /// The link to the featured media (usually an image) for the resource. final List? wpFeaturedmedia; + + /// The link to attachments associated with the resource. final List? wpAttachment; + + /// The link to terms (categories, tags, etc.) associated with the resource. final List? wpTerm; + + /// CURIE links, which are compact URI expressions for link relations. final List? curies; + /// Converts the [Links] instance to a JSON map. + /// + /// This method is useful for serializing the object, for example when sending + /// data to an API or storing it in a database. Map toJson() { return { 'self': self?.map((e) => e.toMap()).toList(), @@ -99,6 +139,9 @@ final class Links { }; } + /// Compares this [Links] instance to another object for equality. + /// + /// Two [Links] instances are considered equal if all their properties are equal. @override bool operator ==(covariant Links other) { if (identical(this, other)) { @@ -120,6 +163,9 @@ final class Links { listEquals(other.curies, curies); } + /// Generates a hash code for this [Links] instance. + /// + /// The hash code is based on all properties of the instance. @override int get hashCode { return self.hashCode ^ @@ -135,6 +181,9 @@ final class Links { curies.hashCode; } + /// Returns a string representation of the [Links] instance. + /// + /// This method is useful for debugging and logging purposes. @override String toString() { return 'Links(self: $self, collection: $collection, about: $about, author: $author, replies: $replies, versionHistory: $versionHistory, predecessorVersion: $predecessorVersion, wpFeaturedmedia: $wpFeaturedmedia, wpAttachment: $wpAttachment, wpTerm: $wpTerm, curies: $curies)'; diff --git a/lib/src/responses/properties/media_details.dart b/lib/src/responses/properties/media_details.dart index 9b3b533..d8a107b 100644 --- a/lib/src/responses/properties/media_details.dart +++ b/lib/src/responses/properties/media_details.dart @@ -5,8 +5,24 @@ import '../../library_exports.dart'; import 'image_meta.dart'; import 'media_size_value.dart'; +/// Represents the details of a media item in the WordPress REST API. +/// +/// This class encapsulates various properties of a media item, including its dimensions, +/// file information, different size variations, and metadata. +/// +/// According to the WordPress REST API Handbook: +/// - The `width` and `height` represent the dimensions of the full-size image. +/// - The `file` property contains the relative path to the uploaded file. +/// - The `sizes` property is a map of available sizes for the image, each represented by a [SizeValue]. +/// - The `image_meta` property contains additional metadata about the image. +/// +/// For more information, see: +/// https://developer.wordpress.org/rest-api/reference/media/#schema-media_details @immutable final class MediaDetails { + /// Creates a new [MediaDetails] instance. + /// + /// All parameters are optional and can be null. const MediaDetails({ this.width, this.height, @@ -15,6 +31,9 @@ final class MediaDetails { this.imageMeta, }); + /// Creates a [MediaDetails] instance from a JSON map. + /// + /// This factory constructor is used to deserialize JSON data returned by the WordPress REST API. factory MediaDetails.fromJson(Map json) { return MediaDetails( width: castOrElse(json['width']), @@ -39,12 +58,29 @@ final class MediaDetails { ); } + /// The width of the full-size image, in pixels. final int? width; + + /// The height of the full-size image, in pixels. final int? height; + + /// The relative path to the uploaded file. final String? file; + + /// A map of available sizes for the image. + /// + /// Each key represents a size name (e.g., 'thumbnail', 'medium', 'large'), + /// and the corresponding value is a [SizeValue] object containing details about that size. final Map? sizes; + + /// Additional metadata about the image. + /// + /// This includes information such as camera settings, copyright, and other EXIF data. final ImageMeta? imageMeta; + /// Converts this [MediaDetails] instance to a JSON map. + /// + /// This method is used for serializing the object, typically when sending data back to the WordPress REST API. Map toJson() { return { 'width': width, diff --git a/lib/src/responses/properties/media_size_value.dart b/lib/src/responses/properties/media_size_value.dart index 45fd9c3..5d1d0e9 100644 --- a/lib/src/responses/properties/media_size_value.dart +++ b/lib/src/responses/properties/media_size_value.dart @@ -2,8 +2,18 @@ import 'package:meta/meta.dart'; import '../../library_exports.dart'; +/// Represents the size details of a media item in the WordPress REST API. +/// +/// This class encapsulates information about a specific size variant of a media item, +/// including its file name, dimensions, MIME type, and source URL. +/// +/// For more information, see the WordPress REST API Media Endpoint documentation: +/// https://developer.wordpress.org/rest-api/reference/media/ @immutable final class SizeValue { + /// Creates a new [SizeValue] instance. + /// + /// All parameters are optional and can be null. const SizeValue({ this.file, this.width, @@ -12,6 +22,9 @@ final class SizeValue { this.sourceUrl, }); + /// Creates a [SizeValue] instance from a JSON map. + /// + /// This factory constructor is used to deserialize JSON data returned by the WordPress REST API. factory SizeValue.fromJson(Map json) { return SizeValue( file: castOrElse(json['file']), @@ -22,12 +35,24 @@ final class SizeValue { ); } + /// The name of the file for this size variant. final String? file; + + /// The width of the media item in pixels. final int? width; + + /// The height of the media item in pixels. final int? height; + + /// The MIME type of the media item (e.g., 'image/jpeg', 'image/png'). final String? mimeType; + + /// The full URL to the media item file. final String? sourceUrl; + /// Converts this [SizeValue] instance to a JSON-compatible map. + /// + /// This method is useful for serializing the object, for example, when sending data to the API. Map toMap() { return { 'file': file, diff --git a/lib/src/responses/search_response.dart b/lib/src/responses/search_response.dart index 28f8083..6c73644 100644 --- a/lib/src/responses/search_response.dart +++ b/lib/src/responses/search_response.dart @@ -4,8 +4,24 @@ import 'package:meta/meta.dart'; import '../utilities/helpers.dart'; import '../utilities/self_representive_base.dart'; +/// Represents a search result from the WordPress REST API. +/// +/// This class encapsulates the data returned by the WordPress REST API +/// for a search query. It includes information about the found item such as +/// its ID, title, type, and URL. +/// +/// For more information, see: +/// https://developer.wordpress.org/rest-api/reference/search-results/ @immutable final class Search implements ISelfRespresentive { + /// Creates a new [Search] instance. + /// + /// [id] is the unique identifier for the search result. + /// [type] is the object type (e.g., post, page, attachment). + /// [subType] is the object subtype (e.g., post format or taxonomy). + /// [url] is the full URL to view the object on the site. + /// [self] is the raw JSON response from the API. + /// [title] is the title of the object, if available. const Search({ required this.id, required this.type, @@ -15,6 +31,7 @@ final class Search implements ISelfRespresentive { this.title, }); + /// Creates a [Search] instance from a JSON map. factory Search.fromJson(Map json) { return Search( id: castOrElse(json['id'])!, @@ -26,15 +43,26 @@ final class Search implements ISelfRespresentive { ); } + /// The unique identifier for the object. final int id; + + /// The title for the object, if available. final String? title; + + /// Type of object (e.g., post, page, attachment). final String type; + + /// Object subtype (e.g., post format or taxonomy). final String subType; + + /// Full URL to view the object on the site. final String url; + /// The raw JSON response from the API. @override final Map self; + /// Converts the [Search] instance to a JSON map. Map toJson() { return { 'id': id, diff --git a/lib/src/responses/tag_response.dart b/lib/src/responses/tag_response.dart index b0d1f1c..bd477e5 100644 --- a/lib/src/responses/tag_response.dart +++ b/lib/src/responses/tag_response.dart @@ -4,8 +4,17 @@ import 'package:meta/meta.dart'; import '../utilities/helpers.dart'; import '../utilities/self_representive_base.dart'; +/// Represents a tag in the WordPress system. +/// +/// This class encapsulates all the properties of a tag as defined in the WordPress REST API. +/// Tags are a taxonomy that can be used to classify posts (and sometimes other content types). +/// +/// For more details, see: https://developer.wordpress.org/rest-api/reference/tags/ @immutable final class Tag implements ISelfRespresentive { + /// Creates a new [Tag] instance. + /// + /// All parameters correspond to the properties of a tag as defined in the WordPress REST API. const Tag({ required this.id, required this.count, @@ -17,6 +26,10 @@ final class Tag implements ISelfRespresentive { this.name, }); + /// Creates a [Tag] instance from a JSON map. + /// + /// This factory constructor is used to deserialize tag data received from + /// the WordPress REST API. factory Tag.fromJson(Map json) { return Tag( id: castOrElse(json['id']), @@ -30,17 +43,32 @@ final class Tag implements ISelfRespresentive { ); } + /// Unique identifier for the tag. final int id; + + /// Number of posts associated with the tag. final int count; + + /// HTML description of the tag. final String? description; + + /// URL of the tag. final String link; + + /// HTML title for the tag. final String? name; + + /// An alphanumeric identifier for the tag unique to its type. final String slug; + + /// Type of taxonomy. Always "post_tag" for tags. final String taxonomy; + /// The raw JSON response from the API. @override final Map self; + /// Converts the [Tag] instance to a JSON map. Map toJson() { return { 'id': id, diff --git a/lib/src/responses/user_response.dart b/lib/src/responses/user_response.dart index d9d4047..cc92db7 100644 --- a/lib/src/responses/user_response.dart +++ b/lib/src/responses/user_response.dart @@ -6,8 +6,16 @@ import '../utilities/self_representive_base.dart'; import 'properties/avatar_urls.dart'; import 'properties/extra_capabilities.dart'; +/// Represents a WordPress user. +/// +/// This class encapsulates all the properties of a WordPress user as defined in the +/// WordPress REST API. It provides a structured way to work with user data in Dart applications. +/// +/// For more information, see the WordPress REST API handbook: +/// https://developer.wordpress.org/rest-api/reference/users/ @immutable final class User implements ISelfRespresentive { + /// Creates a new [User] instance. const User({ required this.id, required this.url, @@ -28,6 +36,9 @@ final class User implements ISelfRespresentive { this.nickname, }); + /// Creates a [User] instance from a JSON map. + /// + /// This factory constructor is used to deserialize user data received from the WordPress REST API. factory User.fromJson(Map json) { return User( id: castOrElse(json['id']), @@ -69,26 +80,61 @@ final class User implements ISelfRespresentive { ); } + /// Unique identifier for the user. final int id; + + /// Login name for the user. final String? username; + + /// Display name for the user. final String? name; + + /// First name for the user. final String? firstName; + + /// Last name for the user. final String? lastName; + + /// The nickname for the user. final String? nickname; + + /// The email address for the user. final String? email; + + /// Registration date for the user. final DateTime? registeredDate; + + /// All capabilities assigned to the user. final Map capabilities; + + /// Extra capabilities assigned to the user. final ExtraCapabilities? extraCapabilities; + + /// URL of the user. final String url; + + /// Description of the user. final String? description; + + /// Author URL of the user. final String link; + + /// An alphanumeric identifier for the user. final String slug; + + /// Roles assigned to the user. final List roles; + + /// Avatar URLs for the user. final AvatarUrls? avatarUrls; + /// The raw data received from the API. @override final Map self; + /// Converts the [User] instance to a JSON map. + /// + /// This method is used to serialize the user data for sending to the WordPress REST API. Map toJson() { return { 'id': id, diff --git a/lib/src/responses/wordpress_discovery_response.dart b/lib/src/responses/wordpress_discovery_response.dart index a1cad9b..c93d9c4 100644 --- a/lib/src/responses/wordpress_discovery_response.dart +++ b/lib/src/responses/wordpress_discovery_response.dart @@ -3,8 +3,16 @@ import 'package:meta/meta.dart'; import '../utilities/helpers.dart'; import '../utilities/self_representive_base.dart'; +/// Represents the WordPress site information and API capabilities. +/// +/// This class encapsulates the data returned by the WordPress REST API's root endpoint, +/// which provides information about the site and available API routes. +/// +/// For more information, see: +/// https://developer.wordpress.org/rest-api/reference/ @immutable final class WordpressDiscovery implements ISelfRespresentive { + /// Creates a new [WordpressDiscovery] instance. const WordpressDiscovery({ required this.siteIconUrl, required this.siteIcon, @@ -21,6 +29,10 @@ final class WordpressDiscovery implements ISelfRespresentive { required this.self, }); + /// Creates a [WordpressDiscovery] instance from a JSON map. + /// + /// This factory constructor is used to deserialize site information data + /// received from the WordPress REST API's root endpoint. factory WordpressDiscovery.fromJson(Map map) { return WordpressDiscovery( siteIconUrl: castOrElse(map['site_icon_url']), @@ -42,22 +54,47 @@ final class WordpressDiscovery implements ISelfRespresentive { ); } + /// The URL of the site's icon. final String siteIconUrl; + + /// The ID of the attachment used as the site icon. final int siteIcon; + + /// The ID of the attachment used as the site logo. final int siteLogo; + + /// The name of the site. final String name; + + /// A brief description of the site. final String description; + + /// The URL of the site. final String url; + + /// The homepage URL of the site. final String home; + + /// Offset of the site's timezone from UTC. final double gmtOffset; + + /// String representation of the site's timezone. final String timezoneString; + + /// List of API namespaces supported by the site. final List namespaces; + + /// Information about authentication methods supported by the API. final Map authentication; + + /// Available API routes and their endpoints. final Map routes; + /// The raw JSON response from the API. @override final Map self; + /// Converts the [WordpressDiscovery] instance to a JSON map. Map toJson() { return { 'site_icon_url': siteIconUrl, diff --git a/lib/src/responses/wordpress_raw_response.dart b/lib/src/responses/wordpress_raw_response.dart index 198fc30..f508bd7 100644 --- a/lib/src/responses/wordpress_raw_response.dart +++ b/lib/src/responses/wordpress_raw_response.dart @@ -16,10 +16,27 @@ typedef OnFailure = WordpressFailureResponse Function( WordpressRawResponse response, ); -/// Represents a raw response from the WordPress API. +/// Represents a raw response from the WordPress REST API. +/// +/// This class encapsulates the raw data returned by the WordPress REST API, +/// including the response body, status code, headers, and other metadata. +/// +/// The WordPress REST API returns responses in JSON format. The structure +/// of the response varies depending on the endpoint and the nature of the request. +/// +/// For more information on WordPress REST API responses, see: +/// https://developer.wordpress.org/rest-api/using-the-rest-api/global-parameters/#_envelope @immutable final class WordpressRawResponse { /// Creates a new [WordpressRawResponse] instance. + /// + /// [data]: The entire response body. Can be null. + /// [code]: The HTTP status code. + /// [headers]: The HTTP response headers. + /// [requestHeaders]: The HTTP request headers. + /// [duration]: The request duration. + /// [extra]: Additional metadata associated with the response. + /// [message]: An optional message describing the response. const WordpressRawResponse({ required this.data, required this.code, @@ -32,28 +49,61 @@ final class WordpressRawResponse { /// The entire response body. Can be null. /// - /// This is the raw response from the HTTP client. + /// This is the raw response from the HTTP client, typically in JSON format. + /// The structure of this data varies depending on the endpoint and request. final dynamic data; /// The HTTP status code. + /// + /// WordPress REST API uses standard HTTP status codes to indicate the success + /// or failure of an API request. Common codes include: + /// - 200: OK - The request was successful + /// - 201: Created - A new resource was successfully created + /// - 400: Bad Request - The request was invalid or cannot be served + /// - 401: Unauthorized - Authentication is required and has failed or not been provided + /// - 403: Forbidden - The server understood the request but refuses to authorize it + /// - 404: Not Found - The requested resource could not be found + /// + /// For a full list of status codes, see: + /// https://developer.wordpress.org/rest-api/using-the-rest-api/status-codes/ final int code; /// The HTTP response headers. + /// + /// WordPress REST API includes several custom headers in its responses: + /// - X-WP-Total: The total number of items for the queried resource + /// - X-WP-TotalPages: The total number of pages of data available + /// - Link: Provides links to the next, previous, first, and last pages of data (for paginated responses) + /// + /// For more information on headers, see: + /// https://developer.wordpress.org/rest-api/using-the-rest-api/global-parameters/#_envelope final Map headers; /// The HTTP request headers. + /// + /// These are the headers that were sent with the original request to the API. final Map requestHeaders; - /// The extra content passed with the request. + /// Additional metadata associated with the response. + /// + /// This can include custom data passed along with the response. final Map extra; /// The request duration. + /// + /// This represents the time taken for the API request to complete. final Duration duration; - /// The error message. + /// An optional message describing the response. + /// + /// This can be used to provide additional context about the response, + /// especially useful for error messages. final String? message; /// Returns the [RequestErrorType] for this response. + /// + /// This method interprets the HTTP status code to determine the type of error, + /// if any, that occurred during the API request. RequestErrorType get errorType { if (isSuccessful) { return RequestErrorType.noError; @@ -66,14 +116,30 @@ final class WordpressRawResponse { return RequestErrorType.unknown; } - /// Returns true if the given [code] is in between 200 to 399 range. + /// Returns true if the given [code] is in the 200 to 399 range. + /// + /// This is used to determine if the API request was successful. + /// Status codes in this range indicate various levels of success. bool get isSuccessful => isInRange(code, 200, 399); - /// Returns true if the given [code] is not in between 200 to 399 range. A convenience method for `!isSuccessful`. + /// Returns true if the given [code] is not in the 200 to 399 range. + /// + /// This is a convenience method for `!isSuccessful`. + /// It indicates that the API request encountered an error. bool get isFailure => !isSuccessful; + /// Indicates whether this response is from a middleware. + /// + /// Some WordPress setups may include middleware that intercepts and modifies + /// API responses. This property helps identify such cases. bool get isMiddlewareResponse => headers.containsKey(MIDDLEWARE_HEADER_KEY); + /// Allows accessing response data using array notation. + /// + /// This operator provides a convenient way to access specific fields + /// in the response data, assuming it's a map. + /// + /// Throws exceptions if the data is null or not a map. dynamic operator [](dynamic key) { if (data == null) { throw NullReferenceException('Response is null.'); @@ -86,25 +152,20 @@ final class WordpressRawResponse { return data[key]; } - /// Maps this instance to a [WordpressResponse] instance. + /// Maps this raw response to a [WordpressResponse] instance. /// - /// The [decoder] is used to decode the [data] to the desired type. + /// This method is used to convert the raw API response into a more + /// structured format, either [WordpressSuccessResponse] or [WordpressFailureResponse]. /// - /// ```dart - /// final response = await client.posts.listRaw( - /// PostsListRequest(), - /// ); + /// The [decoder] function is used to parse the response data into the desired type. /// + /// Example usage: + /// ```dart + /// final response = await client.posts.listRaw(PostsListRequest()); /// final posts = response.asResponse>( - /// decoder: (data) => (data as Iterable).map((e) => Post.fromMap(e)).toList(), + /// decoder: (data) => (data as Iterable).map((e) => Post.fromMap(e)).toList(), /// ); /// ``` - /// - /// The above example shows how to decode the response data to a list of [Post] instances. - /// - /// In case if the response is a failure, the [WordpressFailureResponse] instance is returned. - /// - /// This is a convenience method for the `map` method. WordpressResponse asResponse({ required T Function(dynamic data) decoder, }) { @@ -133,19 +194,19 @@ final class WordpressRawResponse { ); } - /// Returns the field value for the given [key]. - /// - /// The [transformer] is used to transform the value to the desired type. + /// Retrieves a specific field from the response data. /// - /// The [orElse] is used to return a default value if the field is not found. + /// This method provides a safe way to access fields in the response data, + /// with options for transformation and default values. /// - /// The [throwIfError] is used to throw an exception if the response is a failure. + /// [key]: The key of the field to retrieve. + /// [throwIfError]: If true, throws an exception for failure responses. + /// [transformer]: A function to transform the retrieved value. + /// [orElse]: A function to provide a default value if the field is not found. /// + /// Example usage: /// ```dart - /// final response = await client.posts.listRaw( - /// PostsListRequest(), - /// ); - /// + /// final response = await client.posts.listRaw(PostsListRequest()); /// final posts = response.getField>( /// 'data', /// transformer: (data) => (data as Iterable).map((e) => Post.fromMap(e)).toList(), @@ -189,13 +250,20 @@ final class WordpressRawResponse { } /// Returns the error and stack trace if the response is a failure. + /// + /// This method is useful for debugging and error handling, providing + /// access to detailed error information when an API request fails. (Object, StackTrace) getError() => (extra['error'], extra['stackTrace']); - /// Maps this instance to a [WordpressResponse] instance. + /// Maps this raw response to a [WordpressResponse] instance. + /// + /// This method allows for custom handling of success and failure responses. /// - /// The [onSuccess] is called if the response is a success. + /// [onSuccess]: A function to handle successful responses. + /// [onFailure]: A function to handle failure responses. /// - /// The [onFailure] is called if the response is a failure. + /// This method is particularly useful when you need different logic + /// for handling successful and failed API responses. WordpressResponse map({ required OnSuccess onSuccess, required OnFailure onFailure, @@ -231,6 +299,7 @@ final class WordpressRawResponse { message.hashCode; } + /// Creates a copy of this [WordpressRawResponse] with the given fields replaced with new values. WordpressRawResponse copyWith({ dynamic data, int? code, diff --git a/lib/src/responses/wordpress_response.dart b/lib/src/responses/wordpress_response.dart index 758c32d..d87f1d2 100644 --- a/lib/src/responses/wordpress_response.dart +++ b/lib/src/responses/wordpress_response.dart @@ -6,8 +6,26 @@ import 'wordpress_error.dart'; @immutable -/// Represents a successful response. +/// Represents a successful response from the WordPress REST API. +/// +/// This class encapsulates the data and metadata returned by a successful API request. +/// It provides access to the response data, headers, and pagination information. +/// +/// The generic type [T] represents the type of the response data. +/// +/// For more information on WordPress REST API responses, see: +/// https://developer.wordpress.org/rest-api/using-the-rest-api/global-parameters/#_envelope final class WordpressSuccessResponse extends WordpressResponse { + /// Creates a new [WordpressSuccessResponse] instance. + /// + /// [headers]: The response headers. + /// [duration]: The time taken for the request to complete. + /// [data]: The parsed response data. + /// [rawData]: The raw response data. + /// [requestHeaders]: The headers sent with the request. + /// [code]: The HTTP status code (defaults to 200). + /// [extra]: Additional metadata associated with the response. + /// [message]: An optional message describing the response. const WordpressSuccessResponse({ required super.headers, required super.duration, @@ -19,8 +37,16 @@ final class WordpressSuccessResponse extends WordpressResponse { super.message, }); + /// The parsed response data. final T data; + /// Returns the total number of pages available for the current query. + /// + /// This value is extracted from the 'X-WP-TotalPages' header. + /// If the header is not present, it returns 0. + /// + /// For more information on pagination headers, see: + /// https://developer.wordpress.org/rest-api/using-the-rest-api/pagination/ int get totalPagesCount { if (headers.isEmpty) { return 0; @@ -41,6 +67,13 @@ final class WordpressSuccessResponse extends WordpressResponse { return 0; } + /// Returns the total number of items available for the current query. + /// + /// This value is extracted from the 'X-WP-Total' header. + /// If the header is not present, it returns 0. + /// + /// For more information on pagination headers, see: + /// https://developer.wordpress.org/rest-api/using-the-rest-api/pagination/ int get totalCount { if (headers.isEmpty) { return 0; @@ -85,8 +118,26 @@ final class WordpressSuccessResponse extends WordpressResponse { @immutable -/// Represents a failure response. +/// Represents a failure response from the WordPress REST API. +/// +/// This class encapsulates the error information returned by a failed API request. +/// It provides access to the error details, status code, and any additional metadata. +/// +/// The generic type [T] represents the expected type of the response data had the request succeeded. +/// +/// For more information on WordPress REST API error responses, see: +/// https://developer.wordpress.org/rest-api/using-the-rest-api/global-parameters/#_envelope final class WordpressFailureResponse extends WordpressResponse { + /// Creates a new [WordpressFailureResponse] instance. + /// + /// [error]: The WordPress error object containing error details. + /// [code]: The HTTP status code of the error response. + /// [rawData]: The raw error response data. + /// [headers]: The response headers (defaults to an empty map). + /// [requestHeaders]: The headers sent with the request (defaults to an empty map). + /// [extra]: Additional metadata associated with the error response. + /// [duration]: The time taken for the request to complete (defaults to zero duration). + /// [message]: An optional message describing the error. const WordpressFailureResponse({ required this.error, required super.code, @@ -98,9 +149,13 @@ final class WordpressFailureResponse extends WordpressResponse { super.message, }); + /// The WordPress error object containing detailed error information. final WordpressError? error; + /// The exception that caused the failure, if available. Object? get exception => extra?['error']; + + /// The stack trace associated with the failure, if available. StackTrace? get stackTrace => extra?['stackTrace']; @override @@ -126,7 +181,26 @@ final class WordpressFailureResponse extends WordpressResponse { } @immutable + +/// Base class for WordPress REST API responses. +/// +/// This abstract interface defines the common properties and methods +/// shared by both successful and failure responses. +/// +/// The generic type [T] represents the type of the response data. +/// +/// For more information on WordPress REST API responses, see: +/// https://developer.wordpress.org/rest-api/using-the-rest-api/global-parameters/#_envelope abstract interface class WordpressResponse { + /// Creates a new [WordpressResponse] instance. + /// + /// [code]: The HTTP status code of the response. + /// [headers]: The response headers. + /// [duration]: The time taken for the request to complete. + /// [requestHeaders]: The headers sent with the request. + /// [rawData]: The raw response data. + /// [extra]: Additional metadata associated with the response. + /// [message]: An optional message describing the response. const WordpressResponse({ required this.code, required this.headers, @@ -137,14 +211,30 @@ abstract interface class WordpressResponse { this.message, }) : _rawData = rawData; + /// The HTTP status code of the response. final int code; + + /// The response headers. final Map headers; + + /// The headers sent with the request. final Map requestHeaders; + + /// Additional metadata associated with the response. final Map? extra; + + /// The time taken for the request to complete. final Duration duration; + + /// An optional message describing the response. final String? message; + + /// The raw response data. final dynamic _rawData; + /// Allows accessing raw response data using index notation. + /// + /// Throws a [NullReferenceException] if the raw data is null. dynamic operator [](dynamic key) { if (_rawData == null) { throw NullReferenceException('Response is null.'); diff --git a/lib/src/utilities/codable_map/codable_map.dart b/lib/src/utilities/codable_map/codable_map.dart index 4d77810..772a2c9 100644 --- a/lib/src/utilities/codable_map/codable_map.dart +++ b/lib/src/utilities/codable_map/codable_map.dart @@ -3,14 +3,30 @@ import '../../exceptions/type_map/map_exist_exception.dart'; import '../typedefs.dart'; import 'type_key.dart'; +/// A utility class for managing encoders and decoders for JSON serialization. +/// +/// This class provides methods to register, retrieve, and manage custom +/// encoders and decoders for different types, allowing for flexible +/// JSON encoding and decoding of complex objects. class CodableMap { static final Map, JsonDecoderCallback> _decoders = {}; static final Map, JsonEncoderCallback> _encoders = {}; + /// Checks if an encoder exists for the given type [T]. + /// + /// Returns `true` if an encoder is registered for type [T], `false` otherwise. bool encoderExists() => _encoders[TypeKey()] != null; + /// Checks if a decoder exists for the given type [T]. + /// + /// Returns `true` if a decoder is registered for type [T], `false` otherwise. bool decoderExists() => _decoders[TypeKey()] != null; + /// Adds a decoder for the type [T]. + /// + /// [decoder] is the function that will be used to decode JSON to type [T]. + /// If [overwrite] is true, it will replace an existing decoder if present. + /// If [throwIfExists] is true, it will throw a [MapAlreadyExistException] if a decoder already exists. void addDecoder( JsonDecoderCallback decoder, { bool overwrite = false, @@ -30,6 +46,9 @@ class CodableMap { _decoders[typeKey] = decoder; } + /// Removes the decoder for the type [T]. + /// + /// Throws a [MapDoesNotExistException] if no decoder exists for type [T]. void removeDecoder() { if (!decoderExists()) { throw MapDoesNotExistException( @@ -40,6 +59,9 @@ class CodableMap { _decoders.remove(TypeKey()); } + /// Retrieves the decoder for the type [T]. + /// + /// Throws a [MapDoesNotExistException] if no decoder exists for type [T]. static JsonDecoderCallback getDecoder() { if (_decoders[TypeKey()] == null) { throw MapDoesNotExistException( @@ -50,6 +72,11 @@ class CodableMap { return _decoders[TypeKey()]! as T Function(dynamic instance); } + /// Adds an encoder for the type [T]. + /// + /// [encoder] is the function that will be used to encode type [T] to JSON. + /// If [overwrite] is true, it will replace an existing encoder if present. + /// If [throwIfExists] is true, it will throw a [MapAlreadyExistException] if an encoder already exists. void addEncoder( JsonEncoderCallback encoder, { bool overwrite = false, @@ -68,11 +95,15 @@ class CodableMap { _encoders[TypeKey()] = encoder; } + /// Clears all registered encoders and decoders. void clear() { _decoders.clear(); _encoders.clear(); } + /// Removes the encoder for the type [T]. + /// + /// Throws a [MapDoesNotExistException] if no encoder exists for type [T]. void removeEncoder() { if (!encoderExists()) { throw MapDoesNotExistException( @@ -83,6 +114,9 @@ class CodableMap { _encoders.remove(TypeKey()); } + /// Retrieves the encoder for the type [T]. + /// + /// Throws a [MapDoesNotExistException] if no encoder exists for type [T]. static JsonEncoderCallback getEncoder() { if (_encoders[TypeKey()] == null) { throw MapDoesNotExistException( @@ -93,6 +127,12 @@ class CodableMap { return _encoders[TypeKey()]!; } + /// Adds both an encoder and a decoder for the type [T] in a single call. + /// + /// [encoder] is the function that will be used to encode type [T] to JSON. + /// [decoder] is the function that will be used to decode JSON to type [T]. + /// If [overwrite] is true, it will replace existing encoder/decoder if present. + /// If [throwIfExists] is true, it will throw a [MapAlreadyExistException] if an encoder or decoder already exists. void addCodablePair({ required JsonEncoderCallback encoder, required JsonDecoderCallback decoder, diff --git a/lib/src/utilities/codable_map/type_key.dart b/lib/src/utilities/codable_map/type_key.dart index eed3df1..ba9a206 100644 --- a/lib/src/utilities/codable_map/type_key.dart +++ b/lib/src/utilities/codable_map/type_key.dart @@ -2,23 +2,41 @@ import 'package:meta/meta.dart'; import '../helpers.dart'; +/// A class that represents a key based on a type [T]. +/// +/// This class is immutable and can be used as a key in collections +/// where you want to differentiate based on types. @immutable class TypeKey { + /// Returns the runtime type of [T]. Type get _type => typeOf(); + /// Compares this [TypeKey] with another object for equality. + /// + /// Two [TypeKey]s are considered equal if they have the same hash code. @override bool operator ==(Object other) => hashCode == other.hashCode; + /// Generates a hash code for this [TypeKey]. + /// + /// The hash code is based on the hash code of the internal type [_type]. @override int get hashCode { return _type.hashCode; } + /// Returns a string representation of this [TypeKey]. + /// + /// The string includes the type parameter [T]. @override String toString() { return 'TypeKey<$_type>'; } + /// Returns a debug string representation of this [TypeKey]. + /// + /// This method currently returns the same string as [toString], + /// but may provide more detailed information in the future. String toDebugString() { return 'TypeKey<$_type>'; } diff --git a/lib/src/utilities/extensions/map_extensions.dart b/lib/src/utilities/extensions/map_extensions.dart index ebfbf05..617dc17 100644 --- a/lib/src/utilities/extensions/map_extensions.dart +++ b/lib/src/utilities/extensions/map_extensions.dart @@ -1,11 +1,25 @@ import 'dart:convert'; extension MapExtensions on Map { + /// Converts the map to a JSON-encoded string. + /// + /// This method uses the `json.encode` function from the `dart:convert` library + /// to convert the map to a JSON string representation. + /// + /// Returns: + /// A JSON-encoded string representation of the map. String toJsonString() { return json.encode(this); } /// Adds the given [value] to the map if the value is not null. + /// + /// This method is useful for conditionally adding key-value pairs to the map + /// only when the value is not null. + /// + /// Parameters: + /// [key]: The key to associate with the value in the map. + /// [value]: The value to add to the map if it's not null. void addIfNotNull(String key, T value) { if (value == null) { return; @@ -14,6 +28,14 @@ extension MapExtensions on Map { this[key] = value; } + /// Adds all key-value pairs from [values] to this map, excluding null values. + /// + /// This method iterates through the provided map and adds each key-value pair + /// to this map, skipping any entries where the value is null. + /// + /// Parameters: + /// [values]: A map containing the key-value pairs to be added. + /// If null, this method does nothing. void addAllIfNotNull(Map? values) { if (values == null) { return; diff --git a/lib/src/utilities/extensions/parallel_result_extensions.dart b/lib/src/utilities/extensions/parallel_result_extensions.dart index 0bb1771..534bc87 100644 --- a/lib/src/utilities/extensions/parallel_result_extensions.dart +++ b/lib/src/utilities/extensions/parallel_result_extensions.dart @@ -1,6 +1,6 @@ import '../../library_exports.dart'; -extension ParallelResultExtensions on Iterable> { +extension ParallelResultExtensions on Iterable> { /// Merges the results of the parallel requests into a single list. Iterable merge() { return fold>( @@ -12,7 +12,7 @@ extension ParallelResultExtensions on Iterable> { } extension FutureParallelResultExtensions - on Future>> { + on Future>> { /// Merges the results of the parallel requests into a single list. Future> merge() async { final results = await this; diff --git a/lib/src/utilities/helpers.dart b/lib/src/utilities/helpers.dart index 9b5ea21..04f3f42 100644 --- a/lib/src/utilities/helpers.dart +++ b/lib/src/utilities/helpers.dart @@ -5,26 +5,27 @@ import 'dart:math'; import '../library_exports.dart'; import 'codable_map/codable_map.dart'; -/// A convenience method to parse a date from a JSON object with error handling. +/// Parses a date from a JSON object with error handling. +/// +/// Returns null if the input is null, not a string, or an empty string. +/// Otherwise, attempts to parse the string as a DateTime. +/// +/// [json] The JSON object to parse, expected to be a string representation of a date. DateTime? parseDateIfNotNull(dynamic json) { - if (json == null) { - return null; - } - - if (json is! String) { + if (json == null || json is! String || json.isEmpty) { return null; } - final dateString = json; - - if (dateString.isEmpty) { - return null; - } - - return DateTime.tryParse(dateString); + return DateTime.tryParse(json); } -/// A convenience method to map a JSON object to a Dart object with error handling. +/// Maps a JSON object to a Dart object with error handling. +/// +/// [mapper] A function that converts a Map to type T. +/// [json] The JSON object to map. +/// +/// Returns null if the input is null or not a Map. +/// If an error occurs during mapping, returns null instead of throwing an exception. T? mapGuarded({ required T Function(Map json) mapper, required dynamic json, @@ -40,7 +41,12 @@ T? mapGuarded({ } } -/// A convenience method to execute an async function and handle any errors via [onError] callback. +/// Executes an asynchronous function and handles any errors. +/// +/// [function] The asynchronous function to execute. +/// [onError] A callback function to handle any errors that occur. +/// +/// Returns the result of [function] if successful, or the result of [onError] if an error occurs. Future guardAsync({ required Future Function() function, required Future Function(Object error, StackTrace stackTrace) onError, @@ -52,7 +58,12 @@ Future guardAsync({ } } -/// A convenience method to execute a function and handle any errors via [onError] callback. +/// Executes a synchronous function and handles any errors. +/// +/// [function] The function to execute. +/// [onError] A callback function to handle any errors that occur. +/// +/// Returns the result of [function] if successful, or the result of [onError] if an error occurs. T guard({ required T Function() function, required T Function(Object error, StackTrace stackTrace) onError, @@ -64,9 +75,15 @@ T guard({ } } -/// Decodes a value from the given map by matching aganist given set of keys. +/// Decodes a value from a map using multiple possible keys. +/// +/// [map] The map to search in. +/// [keys] A list of keys to try. +/// [transformer] An optional function to transform the found value. +/// [orElse] An optional function to provide a default value if no key is found. /// -/// If any of the keys matches to a value in the map, the value is returned. +/// Returns the value associated with the first matching key, optionally transformed. +/// If no key is found and [orElse] is provided, returns the result of [orElse]. T? decodeByMultiKeys( Map map, List keys, { @@ -74,39 +91,38 @@ T? decodeByMultiKeys( T Function()? orElse, }) { for (final key in keys) { - if (!map.containsKey(key)) { - continue; + if (map.containsKey(key)) { + final value = map[key]; + return transformer != null ? transformer(value) : value as T?; } - - final value = map[key]; - - if (transformer != null) { - return transformer(value); - } - - return value; } - return orElse?.call(); } -/// A convenience method to execute a function and dispose the given [IDisposable] object. +/// Executes a function with a disposable object and ensures it's disposed afterwards. +/// +/// [disposable] The disposable object to use. +/// [delegate] The function to execute with the disposable object. +/// +/// Returns the result of [delegate] and ensures [disposable] is disposed, even if an error occurs. FutureOr using( E disposable, FutureOr Function(E instance) delegate, ) async { try { - return delegate(disposable); + return await delegate(disposable); } finally { disposable.dispose(); } } -/// Casts the given dynamic JSON value to the specified type [T]. +/// Casts a dynamic JSON value to a specified type with optional transformation. /// -/// Optionally, you can provide a [transformer] function to transform the value manually if it is not null. +/// [json] The JSON value to cast. +/// [transformer] An optional function to transform the value if it's not null. +/// [orElse] An optional function to provide a default value if casting fails. /// -/// If the value is null, the [orElse] function is called to return a default value. +/// Returns the cast (and optionally transformed) value, or the result of [orElse] if casting fails. T? castOrElse( dynamic json, { T? Function(Object value)? transformer, @@ -121,38 +137,52 @@ T? castOrElse( return transformer(json); } - if (json is! T) { - return orElse?.call(); - } - - return json; + return json is T ? json : orElse?.call(); } catch (_) { return orElse?.call(); } } +/// Checks if a given URI is a valid WordPress REST API URL. +/// +/// [uri] The URI to check. +/// [forceHttps] If true, only considers HTTPS URLs as valid. +/// +/// Returns true if the URI is a valid WordPress REST API URL, false otherwise. bool isValidRestApiUrl(Uri uri, {bool forceHttps = false}) { if (forceHttps && uri.scheme != 'https') { return false; } - if (uri.pathSegments.isEmpty) { - return false; - } - - // It is possible to not have wp-json on some websites but I don't plan on supporting those in this package because it goes outside of the standards defined by WordPress. - return uri.pathSegments.contains('wp-json'); + return uri.pathSegments.isNotEmpty && uri.pathSegments.contains('wp-json'); } +/// Checks if a given integer is a valid port number. +/// +/// [port] The port number to check. +/// +/// Returns true if the port is between 0 and 65535, false otherwise. bool isValidPortNumber(int port) => port >= 0 && port <= 65535; -/// Returns true if the given [value] is null or empty. +/// Checks if a string is null or empty. +/// +/// [value] The string to check. +/// +/// Returns true if the string is null or empty, false otherwise. bool isNullOrEmpty(String? value) => value == null || value.isEmpty; -/// Checks if the given [value] is alpha numeric. +/// Checks if a string contains only alphanumeric characters. +/// +/// [value] The string to check. +/// +/// Returns true if the string contains only alphanumeric characters, false otherwise. bool isAlphaNumeric(String value) => RegExp(r'^[a-zA-Z0-9]*$').hasMatch(value); -/// Encodes the given [text] to Base64. +/// Encodes a string to Base64. +/// +/// [text] The string to encode. +/// +/// Returns the Base64 encoded string, or an empty string if the input is null or empty. String base64Encode(String text) { if (isNullOrEmpty(text)) { return ''; @@ -161,36 +191,65 @@ String base64Encode(String text) { return base64.encode(utf8.encode(text)); } +/// Generates a random string of specified length. +/// +/// [len] The length of the random string to generate. +/// +/// Returns a Base64 URL-encoded random string of the specified length. String getRandString(int len) { final random = Random.secure(); final values = List.generate(len, (i) => random.nextInt(255)); return base64UrlEncode(values); } -/// Checks if the provided integer value is in the range of [min] and [max]. +/// Checks if an integer value is within a specified range. +/// +/// [value] The integer to check. +/// [min] The minimum value of the range (inclusive). +/// [max] The maximum value of the range (inclusive). +/// +/// Returns true if the value is within the range, false otherwise. bool isInRange(int value, int min, int max) => value >= min && value <= max; +/// Removes HTML tags and entities from a string. +/// +/// [htmlString] The HTML string to parse. +/// +/// Returns the string with all HTML tags and entities removed. String parseHtmlString(String htmlString) => htmlString.replaceAll(RegExp('<[^>]*>|&[^;]+;'), ' '); +/// Gets the Type of a generic type parameter. +/// +/// Returns the Type of T. Type typeOf() => T; -/// Deserializes a JSON object by getting its decoder from [CodableMap] +/// Deserializes a JSON object to a specified type using CodableMap. /// -/// You will need to initiate your custom interface first in order to deserialize using this method. +/// [object] The JSON object to deserialize. +/// +/// Returns the deserialized object of type T. T deserialize(dynamic object) { final decoder = CodableMap.getDecoder(); return decoder(object); } -/// Serializes a Dart object by getting its encoder from [CodableMap] +/// Serializes a Dart object to a JSON map using CodableMap. +/// +/// [object] The object to serialize. /// -/// You will need to initiate your custom interface first in order to serialize using this method. +/// Returns the serialized object as a Map. Map serialize(T object) { final encoder = CodableMap.getEncoder(); return encoder(object); } +/// Maps an iterable JSON object to a List of specified type with error checking. +/// +/// [json] The JSON object to map. +/// [decoder] A function to decode each element of the iterable. +/// +/// Returns a List of type T, or an empty list if the input is invalid. List mapIterableWithChecks( dynamic json, T Function(dynamic json) decoder, @@ -202,6 +261,12 @@ List mapIterableWithChecks( return json.map((dynamic e) => decoder(e)).toList(); } +/// Maps a JSON object to a specified type without type safety checks. +/// +/// [json] The JSON object to map. +/// [decoder] A function to decode the JSON object. +/// +/// Returns the decoded object of type T, or null if the input is null. T? mapToTypeNoSafety(dynamic json, T Function(dynamic json) decoder) { if (json == null) { return null; @@ -210,6 +275,12 @@ T? mapToTypeNoSafety(dynamic json, T Function(dynamic json) decoder) { return decoder(json); } +/// Maps a JSON object to a specified type with type safety checks. +/// +/// [json] The JSON object to map. +/// [decoder] A function to decode the JSON object. +/// +/// Returns the decoded object of type T, or null if the input is invalid. T? mapToType( dynamic json, T Function(Map json) decoder, @@ -221,7 +292,11 @@ T? mapToType( return decoder(json); } -/// Returns the MIME type for the given file extension. +/// Returns the MIME type for a given file extension. +/// +/// [extension] The file extension to get the MIME type for. +/// +/// Returns the corresponding MIME type as a string, or 'text/plain' if unknown. String getMIMETypeFromExtension(String extension) { // list from https://codex.wordpress.org/Function_Reference/get_allowed_mime_types switch (extension.toLowerCase()) { diff --git a/lib/src/utilities/request_url.dart b/lib/src/utilities/request_url.dart index 64ecebb..2b5cc59 100644 --- a/lib/src/utilities/request_url.dart +++ b/lib/src/utilities/request_url.dart @@ -5,32 +5,64 @@ import 'package:path/path.dart'; @immutable -/// An helper class which wraps around URI to provide a more flexible way of building request URLs. +/// A helper class that wraps around URI to provide a more flexible way of building request URLs. +/// +/// This class allows you to create and manipulate URLs for API requests, supporting both relative +/// and absolute URLs. It provides methods to create URLs from various inputs and can merge +/// relative URLs with a base URL. class RequestUrl { const RequestUrl._(this._uri, this._endpoint); /// Creates a new [RequestUrl] instance from the given relative endpoint. /// - /// This URL is then merged internally with the [baseUrl] to create the final request URL. + /// Use this when you have a relative path that needs to be combined with a base URL later. + /// + /// Example: + /// ```dart + /// final url = RequestUrl.relative('/api/users'); + /// ``` factory RequestUrl.relative(String endpoint) { return RequestUrl._(null, endpoint); } - /// Creates a new [RequestUrl] instance from the given relative endpoint parts. The parts are joined together using [joinAll]. + /// Creates a new [RequestUrl] instance from the given relative endpoint parts. + /// + /// The parts are joined together using [joinAll] from the `path` package. + /// This is useful when you want to construct a path from multiple segments. /// - /// This URL is then merged internally with the [baseUrl] to create the final request URL. + /// Example: + /// ```dart + /// final url = RequestUrl.relativeParts(['api', 'users', 123]); + /// ``` factory RequestUrl.relativeParts(Iterable parts) { return RequestUrl._(null, joinAll(parts.map((e) => e.toString()))); } /// Creates a new [RequestUrl] instance from the given absolute [Uri]. + /// + /// Use this when you have a complete URL and don't need to combine it with a base URL. + /// + /// Example: + /// ```dart + /// final url = RequestUrl.absolute(Uri.parse('https://api.example.com/users')); + /// ``` factory RequestUrl.absolute(Uri uri) { return RequestUrl._(uri, null); } - /// Creates a new [RequestUrl] instance from the given absolute [Uri] and merges it with the given [base] [Uri]. + /// Creates a new [RequestUrl] instance by merging the given [uri] with an optional [base] [Uri]. /// - /// If the [base] is null, the [uri] is returned as is. + /// If [base] is provided, it combines the path segments of both URIs. + /// If [base] is null, it returns the [uri] as is. + /// + /// This is useful when you want to combine a relative URL with a base URL. + /// + /// Example: + /// ```dart + /// final baseUri = Uri.parse('https://api.example.com'); + /// final relativeUri = Uri.parse('/users/123'); + /// final url = RequestUrl.absoluteMerge(relativeUri, baseUri); + /// ``` factory RequestUrl.absoluteMerge(Uri uri, [Uri? base]) { if (base == null) { return RequestUrl._(uri, null); @@ -47,14 +79,17 @@ class RequestUrl { final Uri? _uri; final String? _endpoint; - /// Gets if this instance holds a relative URL. + /// Returns true if this instance holds a relative URL, false otherwise. bool get isRelative { return _uri == null && _endpoint != null && _endpoint!.isNotEmpty; } - /// Gets if this instance holds an absolute URL. + /// Returns true if this instance holds an absolute URL, false otherwise. bool get isAbsolute => !isRelative; + /// Returns the absolute [Uri] if this instance holds one. + /// + /// Throws a [StateError] if this instance holds a relative URL. Uri get uri { if (isRelative) { throw StateError('This instance holds a relative URL.'); diff --git a/lib/src/utilities/self_representive_base.dart b/lib/src/utilities/self_representive_base.dart index 7381fa8..7ef76b4 100644 --- a/lib/src/utilities/self_representive_base.dart +++ b/lib/src/utilities/self_representive_base.dart @@ -1,14 +1,31 @@ // ignore_for_file: comment_references +/// An abstract class that provides a self-representation of JSON data. +/// +/// This class is designed to handle JSON responses, particularly useful when +/// dealing with dynamic or unknown fields in API responses. abstract class ISelfRespresentive { + /// Creates a new instance of [ISelfRespresentive]. + /// + /// [self] is a required parameter that contains the entire JSON response as a Map. const ISelfRespresentive({ required this.self, }); - /// Represents the entire JSON body as a Map. + /// Represents the entire JSON response as a Map. + /// + /// This field is particularly useful in the following scenarios: + /// 1. When the API response contains extra fields that are not explicitly modeled. + /// 2. When you need to access the raw response data without creating a custom model. /// - /// Usefull if you have extra fields in the response and would like to read them without writing a custom request/response workflow. + /// You can access specific fields in two ways: + /// 1. Using the [getField] method. + /// 2. Using the subscript operator `[]`. For example: `response['fieldName']` /// - /// You can get each field by using the [getField] method. Moreover, you can use `[]` operator to access the values by their key. Eg: `response['key']` + /// Example usage: + /// ```dart + /// final response = MyResponse(self: jsonMap); + /// final specificField = response['fieldName']; + /// ``` final Map self; } diff --git a/lib/src/utilities/wordpress_events.dart b/lib/src/utilities/wordpress_events.dart index 3f6d006..e11bab8 100644 --- a/lib/src/utilities/wordpress_events.dart +++ b/lib/src/utilities/wordpress_events.dart @@ -4,10 +4,21 @@ import 'package:dio/dio.dart'; import '../responses/wordpress_error.dart'; +/// Callback function type for receiving progress updates. +/// +/// [received] is the number of bytes received so far. +/// [total] is the total number of bytes expected to be received. typedef ReceiveProgressCallback = void Function(int received, int total); + +/// Callback function type for sending progress updates. +/// +/// [sent] is the number of bytes sent so far. +/// [total] is the total number of bytes to be sent. typedef SendProgressCallback = void Function(int sent, int total); +/// A class to handle various events related to WordPress API requests. class WordpressEvents { + /// Creates a [WordpressEvents] instance with optional callback functions. const WordpressEvents({ this.onError, this.onResponse, @@ -16,19 +27,27 @@ class WordpressEvents { this.onCancel, }); - /// Invoked from an exception is thrown from internal [Dio] instance or from a failed request. + /// Callback function invoked when an exception is thrown from the internal [Dio] instance or when a request fails. + /// + /// The [error] parameter contains details about the WordPress-specific error that occurred. final void Function(WordpressError error)? onError; - /// Invoked when response is received. + /// Callback function invoked when a response is received from the WordPress API. /// - /// Argument [response] can be of any type, depending on the response received. Mostly, its a [Map] or [List] or [String]. + /// The [response] parameter can be of any type, depending on the response received. + /// It is typically a [Map], [List], or [String]. final void Function(dynamic response)? onResponse; - /// Invoked when response is received. This is directly invoked from [Dio]. + /// Callback function invoked when receiving data from the WordPress API. + /// + /// This callback is directly invoked from [Dio] and provides progress updates during data reception. final ReceiveProgressCallback? onReceive; - /// Invoked when request is sent. This is directly invoked from [Dio]. + /// Callback function invoked when sending data to the WordPress API. + /// + /// This callback is directly invoked from [Dio] and provides progress updates during data transmission. final SendProgressCallback? onSend; + /// Callback function invoked when a request is cancelled. final void Function()? onCancel; } diff --git a/lib/src/wordpress_client_base.dart b/lib/src/wordpress_client_base.dart index 00c7935..e6db05a 100644 --- a/lib/src/wordpress_client_base.dart +++ b/lib/src/wordpress_client_base.dart @@ -26,14 +26,44 @@ import 'utilities/helpers.dart'; part 'internal_requester.dart'; part 'requests/request_interface_base.dart'; -/// The main class for [WordpressClient]. +/// The main class for interacting with the WordPress REST API. +/// +/// This class provides methods to initialize the client, register interfaces, +/// and perform various operations on WordPress resources. +/// +/// Example usage: +/// ```dart +/// final client = WordpressClient( +/// baseUrl: Uri.parse('https://example.com/wp-json/wp/v2'), +/// bootstrapper: (builder) => builder +/// .withDefaultAuthorization( +/// WordpressAuth.applicationPassword('username', 'app_password') +/// ) +/// .build(), +/// ); +/// +/// // Get posts +/// final posts = await client.posts.list(); +/// ``` final class WordpressClient implements IDisposable { - /// Default Constructor. + /// Creates a new [WordpressClient] instance. /// - /// [baseUrl] is the base url of the wordpress site. - /// [bootstrapper] is a builder method for initializing the client. + /// [baseUrl] is the base URL of the WordPress site's REST API. + /// [bootstrapper] is an optional function to configure the client. /// - /// You can change [baseUrl] per request basis as well. You will have to assign it in `build()` method of request class which inherits from [IRequest]. + /// Throws an [ArgumentError] if the provided URL is invalid. + /// + /// Example: + /// ```dart + /// final client = WordpressClient( + /// baseUrl: Uri.parse('https://example.com/wp-json/wp/v2'), + /// bootstrapper: (builder) => builder + /// .withDefaultAuthorization( + /// AppPasswordAuth('username', 'app_password') + /// ) + /// .build(), + /// ); + /// ``` factory WordpressClient({ required Uri baseUrl, BootstrapConfiguration Function(BootstrapBuilder builder)? bootstrapper, @@ -82,11 +112,27 @@ final class WordpressClient implements IDisposable { ); } - /// Special constructor allows you to initialize the client without a base URL. + /// Creates a [WordpressClient] instance without a base URL. /// - /// Note that, for any operations which require access to the Base URL, it will throw an exception in this case. + /// This constructor is useful when you don't know the base URL at initialization time. + /// Note that operations requiring the base URL will throw exceptions until [reconfigure] is called with a valid URL. /// - /// To update the client with a Base URL, you will have to call `reconfigure` method. + /// [instance] is an optional Dio instance to use for HTTP requests. + /// [bootstrapper] is an optional function to configure the client. + /// + /// Example: + /// ```dart + /// final client = WordpressClient.generic( + /// bootstrapper: (builder) => builder + /// .withDefaultAuthorization( + /// AppPasswordAuth('username', 'app_password') + /// ) + /// .build(), + /// ); + /// + /// // Later, when you know the base URL: + /// client.reconfigure(baseUri: Uri.parse('https://example.com/wp-json/wp/v2')); + /// ``` factory WordpressClient.generic({ Dio? instance, BootstrapConfiguration Function(BootstrapBuilder builder)? bootstrapper, @@ -123,6 +169,27 @@ final class WordpressClient implements IDisposable { _initialize(); } + /// Creates a [WordpressClient] instance with a custom Dio instance. + /// + /// This constructor is useful when you need to customize the Dio instance used for HTTP requests. + /// + /// [baseUrl] is the base URL of the WordPress site's REST API. + /// [instance] is the custom Dio instance to use. + /// [bootstrapper] is an optional function to configure the client. + /// + /// Example: + /// ```dart + /// final dio = Dio()..interceptors.add(CustomInterceptor()); + /// final client = WordpressClient.fromDioInstance( + /// baseUrl: Uri.parse('https://example.com/wp-json/wp/v2'), + /// instance: dio, + /// bootstrapper: (builder) => builder + /// .withDefaultAuthorization( + /// AppPasswordAuth('username', 'app_password') + /// ) + /// .build(), + /// ); + /// ``` factory WordpressClient.fromDioInstance({ required Uri baseUrl, required Dio instance, @@ -171,10 +238,7 @@ final class WordpressClient implements IDisposable { ); } - /// Default Constructor but with initialization. - /// - /// [baseUrl] is the base url of the wordpress site. - /// [bootstrapper] is a builder method for initializing the client. + /// Deprecated constructor. Use [WordpressClient] instead. @Deprecated( 'Use WordpressClient() constructor instead. This is no longer required.', ) @@ -196,21 +260,19 @@ final class WordpressClient implements IDisposable { WordpressDiscovery? _discovery; bool _hasInitialized = false; - /// Base url of this instance. + /// The base URL of this WordPress REST API instance. Uri get baseUrl => _requester.baseUrl; - /// Base url path. + /// The path component of the base URL. String get path => baseUrl.path; - /// Indicates if this instance of [WordpressClient] has been disposed. + /// Indicates whether this [WordpressClient] instance has been disposed. bool get disposed => _isDisposed; - /// Returns true if this instance of [WordpressClient] is running in debug mode. - /// - /// i.e., [LogInterceptor] of [Dio] is attached to [Dio] instance which prints every request & response to console. + /// Returns true if this instance is running in debug mode (i.e., with Dio's LogInterceptor attached). bool get isDebugMode => _requester._isDebugMode; - /// Returns true if we have valid default authorization which is to be used for all requests. + /// Returns true if a valid default authorization is set for all requests. bool get hasValidDefaultAuthorization { if (_requester._defaultAuthorization == null) { return false; @@ -219,139 +281,188 @@ final class WordpressClient implements IDisposable { return _requester._defaultAuthorization!.isValidAuth; } - /// Returns true if the discovery process has been completed. + /// Returns true if the WordPress site discovery process has been completed. bool get discoveryCompleted => _discovery != null; - /// The current user interface. + /// Interface for operations on the current authorized user. /// - /// Provides functionality to manipulate current authorized user. - /// - /// Available Operations: - /// - Retrive (Requires Authorization) + /// Available operations: + /// - Retrieve (Requires Authorization) /// - Update (Requires Authorization) /// - Delete (Requires Authorization) /// + /// Example: + /// ```dart + /// final currentUser = await client.me.retrieve(RetrieveMeRequest()); + /// print(currentUser.name); + /// ``` MeInterface get me => get('me'); - /// The posts interface. + /// Interface for operations on posts. /// - /// Provides functionality to manipulate posts. - /// - /// Available Operations: + /// Available operations: /// - List - /// - Retrive + /// - Retrieve /// - Create (Requires Authorization) /// - Update (Requires Authorization) /// - Delete (Requires Authorization) /// + /// Example: + /// ```dart + /// final posts = await client.posts.list(ListPostsRequest()); + /// for (final post in posts) { + /// print(post.title); + /// } + /// ``` PostsInterface get posts => get('posts'); - /// The pages interface. + /// Interface for operations on pages. /// - /// Provides functionality to manipulate pages. - /// - /// Available Operations: + /// Available operations: /// - List - /// - Retrive + /// - Retrieve /// - Create (Requires Authorization) /// - Update (Requires Authorization) /// - Delete (Requires Authorization) /// + /// Example: + /// ```dart + /// final pages = await client.pages.list(ListPagesRequest()); + /// for (final page in pages) { + /// print(page.title); + /// } + /// ``` PagesInterface get pages => get('pages'); - /// The categories interface. + /// Interface for operations on categories. /// - /// Provides functionality to manipulate categories. - /// - /// Available Operations: + /// Available operations: /// - List - /// - Retrive + /// - Retrieve /// - Create (Requires Authorization) /// - Update (Requires Authorization) /// - Delete (Requires Authorization) /// + /// Example: + /// ```dart + /// final categories = await client.categories.list(ListCategoriesRequest()); + /// for (final category in categories) { + /// print(category.name); + /// } + /// ``` CategoryInterface get categories => get('categories'); - /// The comments interface. - /// - /// Provides functionality to manipulate comments. + /// Interface for operations on comments. /// - /// Available Operations: + /// Available operations: /// - List - /// - Retrive + /// - Retrieve /// - Create (Requires Authorization) /// - Update (Requires Authorization) /// - Delete (Requires Authorization) /// + /// Example: + /// ```dart + /// final comments = await client.comments.list(ListCommentsRequest()); + /// for (final comment in comments) { + /// print(comment.content); + /// } + /// ``` CommentInterface get comments => get('comments'); - /// The media interface. - /// - /// Provides functionality to manipulate media. + /// Interface for operations on media. /// - /// Available Operations: + /// Available operations: /// - List - /// - Retrive + /// - Retrieve /// - Create (Requires Authorization) /// - Update (Requires Authorization) /// - Delete (Requires Authorization) /// + /// Example: + /// ```dart + /// final mediaItems = await client.media.list(ListMediaRequest()); + /// for (final item in mediaItems) { + /// print(item.sourceUrl); + /// } + /// ``` MediaInterface get media => get('media'); - /// The tags interface. - /// - /// Provides functionality to manipulate tags. + /// Interface for operations on tags. /// - /// Available Operations: + /// Available operations: /// - List - /// - Retrive + /// - Retrieve /// - Create (Requires Authorization) /// - Update (Requires Authorization) /// - Delete (Requires Authorization) /// + /// Example: + /// ```dart + /// final tags = await client.tags.list(ListTagsRequest()); + /// for (final tag in tags) { + /// print(tag.name); + /// } + /// ``` TagInterface get tags => get('tags'); - /// The users interface. - /// - /// Provides functionality to manipulate users. + /// Interface for operations on users. /// - /// Available Operations: + /// Available operations: /// - List - /// - Retrive + /// - Retrieve /// - Create (Requires Authorization) /// - Update (Requires Authorization) /// - Delete (Requires Authorization) /// + /// Example: + /// ```dart + /// final users = await client.users.list(ListUsersRequest()); + /// for (final user in users) { + /// print(user.name); + /// } + /// ``` UsersInterface get users => get('users'); - /// The search interface. - /// - /// Provides functionality to search posts, terms, post-formats. + /// Interface for search operations. /// - /// Available Operations: + /// Available operations: /// - List /// + /// Example: + /// ```dart + /// final searchResults = await client.search.list(ListSearchRequest(search: 'WordPress')); + /// for (final result in searchResults) { + /// print(result.title); + /// } + /// ``` SearchInterface get search => get('search'); - /// The application password interface. - /// - /// Provides functionality to list, create and delete application passwords. + /// Interface for operations on application passwords. /// - /// Available Operations: + /// Available operations: /// - List (Requires Authorization) /// - Create (Requires Authorization) /// - Update (Requires Authorization) - /// - Retrive (Requires Authorization) + /// - Retrieve (Requires Authorization) /// - Delete (Requires Authorization) /// + /// Example: + /// ```dart + /// final appPasswords = await client.applicationPasswords.list(ListApplicationPasswordsRequest()); + /// for (final password in appPasswords) { + /// print(password.name); + /// } + /// ``` ApplicationPasswordsInterface get applicationPasswords => get('application-passwords'); - /// Status on if the client has been initialized successfully. - /// - /// This will be true if [_initialize] method has been called and completed. + /// Indicates whether the client has been initialized successfully. bool get isReady => _hasInitialized && _requester.hasBaseURL; + /// Returns the WordPress site discovery information. + /// + /// Throws a [DiscoveryPendingException] if discovery hasn't been performed yet. WordpressDiscovery get discovery { if (_discovery == null) { throw DiscoveryPendingException(); @@ -360,6 +471,9 @@ final class WordpressClient implements IDisposable { return _discovery!; } + /// Checks if the provided URL is valid. + /// + /// Returns true if the URL is absolute and has a valid port number. static bool isValidUrl(String url) { final uri = Uri.tryParse(url); @@ -378,9 +492,7 @@ final class WordpressClient implements IDisposable { return true; } - /// Initializes all the built in interfaces and other services - /// - /// This method should be called before any other method. + /// Initializes all the built-in interfaces and other services. void _initialize() { if (_hasInitialized) { return; diff --git a/test/wordpress_client_test.dart b/test/wordpress_client_test.dart index 150b3d0..9b56287 100644 --- a/test/wordpress_client_test.dart +++ b/test/wordpress_client_test.dart @@ -22,7 +22,7 @@ Future main() async { final client = WordpressClient( baseUrl: baseUrl, bootstrapper: (builder) => builder - .withDefaultMaxRedirects(5) + .withMaxRedirects(5) .withFollowRedirects(true) .withRequestTimeout(const Duration(seconds: 60)) .withDebugMode(false) From 9cba2642d4464468352568c035ad2b8a42f08742 Mon Sep 17 00:00:00 2001 From: Arun Prakash Date: Sun, 8 Sep 2024 14:59:31 +0530 Subject: [PATCH 2/6] Refactor withResponsePreprocessor method signature --- lib/src/bootstrap_builder.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/bootstrap_builder.dart b/lib/src/bootstrap_builder.dart index 927b93d..d55247b 100644 --- a/lib/src/bootstrap_builder.dart +++ b/lib/src/bootstrap_builder.dart @@ -88,7 +88,8 @@ class BootstrapBuilder { /// Sets a response preprocessor function. BootstrapBuilder withResponsePreprocessor( - bool Function(dynamic) preprocessor) { + bool Function(dynamic) preprocessor, + ) { _responsePreprocessorDelegate = preprocessor; return this; } From 2793eb007ef0b5c9815a520c0c0ac87628789ae4 Mon Sep 17 00:00:00 2001 From: Arun Prakash Date: Sun, 8 Sep 2024 21:56:35 +0530 Subject: [PATCH 3/6] Updated docs | Support for adding media from bytes --- CHANGELOG.md | 5 + lib/src/cache/cache_exports.dart | 6 + lib/src/library_exports.dart | 1 + lib/src/requests/create/create_media.dart | 231 ++++++++++++++++++---- pubspec.yaml | 2 +- 5 files changed, 206 insertions(+), 39 deletions(-) create mode 100644 lib/src/cache/cache_exports.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index d03b55e..452fdb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 8.5.4 + +- Added support for creating media from bytes +- Updated documentation + ## 8.5.3 - Fix invalid base url on web diff --git a/lib/src/cache/cache_exports.dart b/lib/src/cache/cache_exports.dart new file mode 100644 index 0000000..361749a --- /dev/null +++ b/lib/src/cache/cache_exports.dart @@ -0,0 +1,6 @@ +export 'cache_entry.dart'; +export 'cache_manager_base.dart'; +export 'exceptions/cache_exception_base.dart'; +export 'exceptions/cache_expired_exception.dart'; +export 'exceptions/cache_not_exists_exception.dart'; +export 'stores/memory_cache_store.dart'; diff --git a/lib/src/library_exports.dart b/lib/src/library_exports.dart index 398c5ee..5638ddf 100644 --- a/lib/src/library_exports.dart +++ b/lib/src/library_exports.dart @@ -1,4 +1,5 @@ export 'authorization/authorization_export.dart'; +export 'cache/cache_exports.dart'; export 'client_configuration.dart'; export 'enums.dart'; export 'exceptions/exceptions_export.dart'; diff --git a/lib/src/requests/create/create_media.dart b/lib/src/requests/create/create_media.dart index 1dd06ad..6dd5682 100644 --- a/lib/src/requests/create/create_media.dart +++ b/lib/src/requests/create/create_media.dart @@ -1,22 +1,25 @@ // ignore_for_file: avoid_slow_async_io import 'dart:io'; +import 'dart:typed_data'; import 'package:dio/dio.dart'; -import 'package:http_parser/http_parser.dart' show MediaType; import 'package:path/path.dart'; -import '../../enums.dart' show Status, HttpMethod; -import '../../exceptions/file_not_exist_exception.dart'; -import '../../utilities/extensions/map_extensions.dart'; -import '../../utilities/helpers.dart'; -import '../../utilities/request_url.dart'; -import '../request_interface.dart'; -import '../wordpress_request.dart'; +import '../../../wordpress_client.dart'; +import '../../constants.dart'; +/// A request class for creating media in WordPress. +/// +/// This class provides functionality to create media items in WordPress, +/// supporting both file-based and byte-based media uploads. final class CreateMediaRequest extends IRequest { - CreateMediaRequest({ - required this.mediaFilePath, + /// Private constructor for CreateMediaRequest. + /// + /// This constructor is used internally by the factory methods. + CreateMediaRequest._({ + required this.mediaFile, + required this.fileName, this.altText, this.caption, this.description, @@ -38,39 +41,191 @@ final class CreateMediaRequest extends IRequest { super.queryParameters, }); - String mediaFilePath; - String? altText; - String? caption; - String? description; - String? mediaStatus; - int? post; - String? title; - int? authorId; - Status? commentStatus; - Status? pingStatus; + /// Creates a CreateMediaRequest instance from a File. + /// + /// Use this factory method when you have a file on the device that you want to upload. + /// + /// [file] is the File object representing the media to be uploaded. + /// Other parameters are optional and correspond to various media attributes and request options. + factory CreateMediaRequest.fromFile({ + required File file, + String? altText, + String? caption, + String? description, + String? mediaStatus, + int? post, + String? title, + int? authorId, + Status? commentStatus, + Status? pingStatus, + CancelToken? cancelToken, + IAuthorization? authorization, + WordpressEvents? events, + Duration? receiveTimeout, + bool? requireAuth, + Duration? sendTimeout, + ValidatorCallback? validator, + Map? extra, + Map? headers, + Map? queryParameters, + }) { + return CreateMediaRequest._( + mediaFile: file, + fileName: basename(file.path), + altText: altText, + caption: caption, + description: description, + mediaStatus: mediaStatus, + post: post, + title: title, + authorId: authorId, + commentStatus: commentStatus, + pingStatus: pingStatus, + cancelToken: cancelToken, + authorization: authorization, + events: events, + receiveTimeout: receiveTimeout ?? DEFAULT_REQUEST_TIMEOUT, + requireAuth: requireAuth ?? true, + sendTimeout: sendTimeout ?? DEFAULT_REQUEST_TIMEOUT, + validator: validator, + extra: extra, + headers: headers, + queryParameters: queryParameters, + ); + } + + /// Creates a CreateMediaRequest instance from bytes. + /// + /// Use this factory method when you have the media content as a byte array. + /// + /// [bytes] is the Uint8List containing the media data. + /// [fileName] is the name to be given to the file when uploaded. + /// Other parameters are optional and correspond to various media attributes and request options. + factory CreateMediaRequest.fromBytes({ + required Uint8List bytes, + required String fileName, + String? altText, + String? caption, + String? description, + String? mediaStatus, + int? post, + String? title, + int? authorId, + Status? commentStatus, + Status? pingStatus, + CancelToken? cancelToken, + IAuthorization? authorization, + WordpressEvents? events, + Duration? receiveTimeout, + bool? requireAuth, + Duration? sendTimeout, + ValidatorCallback? validator, + Map? extra, + Map? headers, + Map? queryParameters, + }) { + return CreateMediaRequest._( + mediaFile: bytes, + fileName: fileName, + altText: altText, + caption: caption, + description: description, + mediaStatus: mediaStatus, + post: post, + title: title, + authorId: authorId, + commentStatus: commentStatus, + pingStatus: pingStatus, + cancelToken: cancelToken, + authorization: authorization, + events: events, + receiveTimeout: receiveTimeout ?? DEFAULT_REQUEST_TIMEOUT, + requireAuth: requireAuth ?? true, + sendTimeout: sendTimeout ?? DEFAULT_REQUEST_TIMEOUT, + validator: validator, + extra: extra, + headers: headers, + queryParameters: queryParameters, + ); + } + /// The media file to be uploaded, either as a File or Uint8List. + final dynamic mediaFile; + + /// The name of the file to be uploaded. + final String fileName; + + /// Alternative text for the media. + final String? altText; + + /// Caption for the media. + final String? caption; + + /// Description of the media. + final String? description; + + /// Status of the media (e.g., 'publish', 'draft'). + final String? mediaStatus; + + /// ID of the post to which this media is attached. + final int? post; + + /// Title of the media. + final String? title; + + /// ID of the author of the media. + final int? authorId; + + /// Comment status for the media. + final Status? commentStatus; + + /// Ping status for the media. + final Status? pingStatus; + + /// Builds the WordPress request for creating media. + /// + /// This method prepares the request by creating a MultipartFile from the media, + /// setting up the necessary headers and body, and returning a WordpressRequest object. @override Future build(Uri baseUrl) async { - final file = File(mediaFilePath); - - if (!await file.exists()) { - throw FileDoesntExistException( - 'The file at path "$mediaFilePath" doesn\'t exist.', - ); - } + late MultipartFile multipartFile; + late String mimeType; - final fileName = basename(file.path); - final mediaType = getMIMETypeFromExtension( - extension(fileName).replaceFirst('.', ''), - ); + switch (mediaFile) { + case final File file: + if (!await file.exists()) { + throw FileDoesntExistException( + 'The file at path "${file.path}" doesn\'t exist.', + ); + } - final multipartFile = MultipartFile.fromBytes( - await file.readAsBytes(), - filename: fileName, - contentType: MediaType.parse(mediaType), - ); + final mediaType = getMIMETypeFromExtension( + extension(fileName).replaceFirst('.', ''), + ); - final mimeType = multipartFile.contentType!.mimeType; + multipartFile = await MultipartFile.fromFile( + file.path, + filename: fileName, + contentType: DioMediaType.parse(mediaType), + ); + mimeType = mediaType; + break; + case final Uint8List bytes: + final mediaType = getMIMETypeFromExtension( + extension(fileName).replaceFirst('.', ''), + ); + multipartFile = MultipartFile.fromBytes( + bytes, + filename: fileName, + contentType: DioMediaType.parse(mediaType), + ); + mimeType = mediaType; + break; + default: + throw ArgumentError( + 'Invalid mediaFile type. Expected File or Uint8List.', + ); + } final body = {} ..addIfNotNull('alt_text', altText) @@ -88,7 +243,7 @@ final class CreateMediaRequest extends IRequest { final headers = {} ..addIfNotNull( 'Content-Disposition', - 'attachment; filename="${multipartFile.filename}"', + 'attachment; filename="$fileName"', ) ..addIfNotNull('Content-Type', mimeType) ..addAllIfNotNull(this.headers); diff --git a/pubspec.yaml b/pubspec.yaml index 03803df..b8ff545 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: wordpress_client description: A library to interact with the Wordpress REST API. Supports most of the common endpoints and all of the CRUD operations on the endpoints. -version: 8.5.3 +version: 8.5.4 topics: - wordpress - parallel From 76ed8c69ec2ff9f063a9d94db9a075380add22e8 Mon Sep 17 00:00:00 2001 From: Arun Prakash Date: Sun, 8 Sep 2024 22:03:03 +0530 Subject: [PATCH 4/6] refactors --- lib/src/requests/create/create_media.dart | 72 ++++++++++++----------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/lib/src/requests/create/create_media.dart b/lib/src/requests/create/create_media.dart index 6dd5682..b43649f 100644 --- a/lib/src/requests/create/create_media.dart +++ b/lib/src/requests/create/create_media.dart @@ -188,43 +188,49 @@ final class CreateMediaRequest extends IRequest { /// setting up the necessary headers and body, and returning a WordpressRequest object. @override Future build(Uri baseUrl) async { - late MultipartFile multipartFile; - late String mimeType; + MultipartFile? multipartFile; + String? mimeType; - switch (mediaFile) { - case final File file: - if (!await file.exists()) { - throw FileDoesntExistException( - 'The file at path "${file.path}" doesn\'t exist.', - ); - } - - final mediaType = getMIMETypeFromExtension( - extension(fileName).replaceFirst('.', ''), + if (mediaFile is File) { + final file = mediaFile as File; + if (!await file.exists()) { + throw FileDoesntExistException( + 'The file at path "${file.path}" doesn\'t exist.', ); + } - multipartFile = await MultipartFile.fromFile( - file.path, - filename: fileName, - contentType: DioMediaType.parse(mediaType), - ); - mimeType = mediaType; - break; - case final Uint8List bytes: - final mediaType = getMIMETypeFromExtension( - extension(fileName).replaceFirst('.', ''), - ); - multipartFile = MultipartFile.fromBytes( - bytes, - filename: fileName, - contentType: DioMediaType.parse(mediaType), - ); - mimeType = mediaType; - break; - default: + final mediaType = getMIMETypeFromExtension( + extension(fileName).replaceFirst('.', ''), + ); + + multipartFile = await MultipartFile.fromFile( + file.path, + filename: fileName, + contentType: DioMediaType.parse(mediaType), + ); + mimeType = mediaType; + } else if (mediaFile is Uint8List) { + final bytes = mediaFile as Uint8List; + + if (!fileName.contains('.')) { throw ArgumentError( - 'Invalid mediaFile type. Expected File or Uint8List.', + 'The file name must contain a file extension.', ); + } + + final mediaType = getMIMETypeFromExtension( + extension(fileName).replaceFirst('.', ''), + ); + multipartFile = MultipartFile.fromBytes( + bytes, + filename: fileName, + contentType: DioMediaType.parse(mediaType), + ); + mimeType = mediaType; + } else { + throw ArgumentError( + 'Invalid content type for the file. Please use the `fromBytes` factory method to upload files with custom content types.', + ); } final body = {} @@ -243,7 +249,7 @@ final class CreateMediaRequest extends IRequest { final headers = {} ..addIfNotNull( 'Content-Disposition', - 'attachment; filename="$fileName"', + 'attachment; filename="${multipartFile.filename}"', ) ..addIfNotNull('Content-Type', mimeType) ..addAllIfNotNull(this.headers); From e27f9434d8439935ce197becb4f4912542dece64 Mon Sep 17 00:00:00 2001 From: Arun Prakash Date: Sun, 8 Sep 2024 22:41:17 +0530 Subject: [PATCH 5/6] misc --- analysis_options.yaml | 3 + lib/src/requests/query_builder_base.dart | 74 ++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 lib/src/requests/query_builder_base.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index aefa97f..a5228d6 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,3 +1,6 @@ +analyzer: + errors: + avoid_positional_boolean_parameters: ignore include: package:personal_flutter_lints/analysis_options.yaml linter: diff --git a/lib/src/requests/query_builder_base.dart b/lib/src/requests/query_builder_base.dart new file mode 100644 index 0000000..ea93aa0 --- /dev/null +++ b/lib/src/requests/query_builder_base.dart @@ -0,0 +1,74 @@ +import 'package:dio/dio.dart'; + +import '../../wordpress_client.dart'; + +abstract base class IQueryBuilder> extends IRequest { + final Map _queryParameters = {}; + final Map _body = {}; + final Map _headers = {}; + final Map _extra = {}; + bool _requireAuth = false; + CancelToken? _cancelToken; + IAuthorization? _authorization; + WordpressEvents? _events; + Duration _receiveTimeout = const Duration(seconds: 30); + Duration _sendTimeout = const Duration(seconds: 30); + ValidatorCallback? _validator; + + IQueryBuilder withQueryParameter(String key, dynamic value) { + _queryParameters[key] = value; + return this; + } + + IQueryBuilder withBodyParameter(String key, dynamic value) { + _body[key] = value; + return this; + } + + IQueryBuilder withHeader(String key, String value) { + _headers[key] = value; + return this; + } + + IQueryBuilder withExtra(String key, dynamic value) { + _extra[key] = value; + return this; + } + + IQueryBuilder withAuthRequired(bool value) { + _requireAuth = value; + return this; + } + + IQueryBuilder withCancelToken(CancelToken token) { + _cancelToken = token; + return this; + } + + IQueryBuilder withAuthorization(IAuthorization auth) { + _authorization = auth; + return this; + } + + IQueryBuilder withEvents(WordpressEvents events) { + _events = events; + return this; + } + + IQueryBuilder withReceiveTimeout(Duration timeout) { + _receiveTimeout = timeout; + return this; + } + + IQueryBuilder withSendTimeout(Duration timeout) { + _sendTimeout = timeout; + return this; + } + + IQueryBuilder withValidator(ValidatorCallback validator) { + _validator = validator; + return this; + } + + WordpressRequest build(Uri baseUrl); +} From 015f98e7182efd9e057e54c345cabcafe4afa0e9 Mon Sep 17 00:00:00 2001 From: Arun Prakash Date: Sun, 8 Sep 2024 22:42:38 +0530 Subject: [PATCH 6/6] doc update --- lib/src/interface/application_passwords.dart | 2 +- lib/src/interface/category.dart | 2 +- lib/src/interface/comments.dart | 2 +- lib/src/interface/media.dart | 2 +- lib/src/interface/page.dart | 2 +- lib/src/interface/posts.dart | 2 +- lib/src/interface/search.dart | 2 +- lib/src/interface/tags.dart | 2 +- lib/src/interface/users.dart | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/src/interface/application_passwords.dart b/lib/src/interface/application_passwords.dart index 9dc1305..ac37035 100644 --- a/lib/src/interface/application_passwords.dart +++ b/lib/src/interface/application_passwords.dart @@ -11,7 +11,7 @@ import '../../wordpress_client.dart'; /// /// Example usage: /// ```dart -/// final wpClient = WordPressClient('https://your-wordpress-site.com'); +/// final wpClient = WordpressClient(baseUrl: 'https://your-wordpress-site.com'); /// final appPasswords = wpClient.applicationPasswords; /// /// // Create a new application password diff --git a/lib/src/interface/category.dart b/lib/src/interface/category.dart index df8cf15..1bc57f9 100644 --- a/lib/src/interface/category.dart +++ b/lib/src/interface/category.dart @@ -8,7 +8,7 @@ import '../../wordpress_client.dart'; /// /// Example usage: /// ```dart -/// final wordpress = WordPressClient('https://your-wordpress-site.com/wp-json'); +/// final wordpress = WordpressClient(baseUrl: 'https://your-wordpress-site.com/wp-json'); /// final categoryInterface = wordpress.categories; /// /// // Create a new category diff --git a/lib/src/interface/comments.dart b/lib/src/interface/comments.dart index d0933b8..d00c74e 100644 --- a/lib/src/interface/comments.dart +++ b/lib/src/interface/comments.dart @@ -8,7 +8,7 @@ import '../../wordpress_client.dart'; /// Example usage: /// /// ```dart -/// final wp = WordPressClient('https://your-wordpress-site.com/wp-json'); +/// final wp = WordpressClient(baseUrl: 'https://your-wordpress-site.com/wp-json'); /// final commentInterface = wp.comments; /// /// // Create a new comment diff --git a/lib/src/interface/media.dart b/lib/src/interface/media.dart index 1929a95..ac7203b 100644 --- a/lib/src/interface/media.dart +++ b/lib/src/interface/media.dart @@ -7,7 +7,7 @@ import '../../wordpress_client.dart'; /// /// Example usage: /// ```dart -/// final wp = WordPressClient('https://example.com/wp-json'); +/// final wp = WordpressClient(baseUrl: 'https://example.com/wp-json'); /// final mediaInterface = wp.media; /// /// // Create a new media item diff --git a/lib/src/interface/page.dart b/lib/src/interface/page.dart index daf579b..740590c 100644 --- a/lib/src/interface/page.dart +++ b/lib/src/interface/page.dart @@ -8,7 +8,7 @@ import '../../wordpress_client.dart'; /// Example usage: /// /// ```dart -/// final wordpress = WordPressClient('https://your-site.com/wp-json'); +/// final wordpress = WordpressClient(baseUrl: 'https://your-site.com/wp-json'); /// final pagesInterface = wordpress.pages; /// /// // Create a new page diff --git a/lib/src/interface/posts.dart b/lib/src/interface/posts.dart index 9bd3a10..1d640f1 100644 --- a/lib/src/interface/posts.dart +++ b/lib/src/interface/posts.dart @@ -8,7 +8,7 @@ import '../../wordpress_client.dart'; /// Example usage: /// /// ```dart -/// final wordpress = WordPressClient('https://your-wordpress-site.com'); +/// final wordpress = WordpressClient(baseUrl: 'https://your-wordpress-site.com'); /// final postsInterface = wordpress.posts; /// /// // Create a new post diff --git a/lib/src/interface/search.dart b/lib/src/interface/search.dart index d7924ae..9ea9bf1 100644 --- a/lib/src/interface/search.dart +++ b/lib/src/interface/search.dart @@ -7,7 +7,7 @@ import '../library_exports.dart'; /// Example usage: /// /// ```dart -/// final wordpress = WordPressClient('https://your-wordpress-site.com/wp-json'); +/// final wordpress = WordpressClient(baseUrl: 'https://your-wordpress-site.com/wp-json'); /// final searchInterface = wordpress.search; /// /// // Perform a search diff --git a/lib/src/interface/tags.dart b/lib/src/interface/tags.dart index f02ac1f..bac5697 100644 --- a/lib/src/interface/tags.dart +++ b/lib/src/interface/tags.dart @@ -7,7 +7,7 @@ import '../../wordpress_client.dart'; /// /// Example usage: /// ```dart -/// final wordpress = WordPressClient('https://your-wordpress-site.com/wp-json'); +/// final wordpress = WordpressClient(baseUrl: 'https://your-wordpress-site.com/wp-json'); /// final tagInterface = wordpress.tags; /// /// // Create a new tag diff --git a/lib/src/interface/users.dart b/lib/src/interface/users.dart index 6ea3911..ee512eb 100644 --- a/lib/src/interface/users.dart +++ b/lib/src/interface/users.dart @@ -8,7 +8,7 @@ import '../../wordpress_client.dart'; /// Example usage: /// /// ```dart -/// final wordpress = WordPressClient('https://your-site.com'); +/// final wordpress = WordpressClient(baseUrl: 'https://your-site.com'); /// final usersInterface = wordpress.users; /// /// // Create a new user