diff --git a/Cargo.lock b/Cargo.lock index 3874b64f5..6ea0e99cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1356,7 +1356,6 @@ checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", - "serde", ] [[package]] @@ -1679,15 +1678,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "ordered-float" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e" -dependencies = [ - "num-traits", -] - [[package]] name = "os_str_bytes" version = "6.6.1" @@ -2224,7 +2214,6 @@ version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ - "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -2834,19 +2823,6 @@ dependencies = [ "webrtc-util", ] -[[package]] -name = "webrtc-constraints" -version = "0.1.0" -dependencies = [ - "env_logger", - "indexmap 2.2.6", - "lazy_static", - "ordered-float", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "webrtc-data" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index ae6926cef..f38c32a89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,5 @@ [workspace] members = [ - "constraints", "data", "dtls", "examples", @@ -20,8 +19,5 @@ members = [ ] resolver = "2" -[workspace.lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } - [profile.dev] opt-level = 0 diff --git a/constraints/.gitignore b/constraints/.gitignore deleted file mode 100644 index 926fe98a1..000000000 --- a/constraints/.gitignore +++ /dev/null @@ -1,88 +0,0 @@ - -# Created by https://www.toptal.com/developers/gitignore/api/rust -# Edit at https://www.toptal.com/developers/gitignore?templates=rust - -### Rust ### -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb - -# End of https://www.toptal.com/developers/gitignore/api/rust - -# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode -# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode - -### VisualStudioCode ### -.vscode/* -# !.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -!.vscode/*.code-snippets - -# Local History for Visual Studio Code -.history/ - -# Built Visual Studio Code Extensions -*.vsix - -### VisualStudioCode Patch ### -# Ignore all local history of files -.history -.ionide - -# Support for Project snippet scope -.vscode/*.code-snippets - -# Ignore code-workspaces -*.code-workspace - -# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode - -# Created by https://www.toptal.com/developers/gitignore/api/macos -# Edit at https://www.toptal.com/developers/gitignore?templates=macos - -### macOS ### -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### macOS Patch ### -# iCloud generated files -*.icloud - -# End of https://www.toptal.com/developers/gitignore/api/macos diff --git a/constraints/CHANGELOG.md b/constraints/CHANGELOG.md deleted file mode 100644 index c2cce53a4..000000000 --- a/constraints/CHANGELOG.md +++ /dev/null @@ -1,7 +0,0 @@ -# webrtc-constraints changelog - -## Unreleased - -## v0.1.0 - -Initial release. diff --git a/constraints/Cargo.toml b/constraints/Cargo.toml deleted file mode 100644 index 9f2fa755a..000000000 --- a/constraints/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "webrtc-constraints" -version = "0.1.0" -authors = ["Vincent Esche "] -edition = "2021" -description = "A pure Rust implementation of WebRTC Media Constraints API" -license = "MIT OR Apache-2.0" -documentation = "https://docs.rs/webrtc-constraints" -homepage = "https://webrtc.rs" -repository = "https://github.com/webrtc-rs/webrtc/tree/master/constraints" - -[dependencies] -indexmap = "2" -serde = { version = "1", features = ["derive"], optional = true } -ordered-float = { version = "4", default-features = false } -thiserror = "1" - -[dev-dependencies] -env_logger = "0.11.3" -lazy_static = "1" -serde_json = { version = "1", features = ["preserve_order"] } - -[lints] -workspace = true - -[features] -default = ["serde"] -serde = ["dep:serde", "indexmap/serde"] - -[[example]] -name = "json" -path = "examples/json.rs" -required-features = ["serde"] diff --git a/constraints/LICENSE-APACHE b/constraints/LICENSE-APACHE deleted file mode 100644 index b2e847a43..000000000 --- a/constraints/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/constraints/LICENSE-MIT b/constraints/LICENSE-MIT deleted file mode 100644 index 5a980079b..000000000 --- a/constraints/LICENSE-MIT +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Vincent Esche - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/constraints/README.md b/constraints/README.md deleted file mode 100644 index 249f66e9f..000000000 --- a/constraints/README.md +++ /dev/null @@ -1,32 +0,0 @@ -

- WebRTC.rs -
-

-

- - - - - - - - - - - - - - - - - License: MIT/Apache 2.0 - - - Discord - -

-

- A pure Rust implementation of the SelectSettings algorithm from the WebRTC/W3C "Media Capture and Streams" spec. - - (Last synced with the spec against commit 8cea879 from 2023/01/09.) -

diff --git a/constraints/examples/json.rs b/constraints/examples/json.rs deleted file mode 100644 index 878cbb142..000000000 --- a/constraints/examples/json.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::iter::FromIterator; - -use webrtc_constraints::algorithms::{ - select_settings_candidates, ClosestToIdealPolicy, DeviceInformationExposureMode, - TieBreakingPolicy, -}; -use webrtc_constraints::property::all::name::*; -use webrtc_constraints::{ - MediaTrackConstraints, MediaTrackSettings, MediaTrackSupportedConstraints, -}; - -fn main() { - let supported_constraints = - MediaTrackSupportedConstraints::from_iter(vec![&DEVICE_ID, &HEIGHT, &WIDTH, &RESIZE_MODE]); - - // Deserialize possible settings from JSON: - let possible_settings: Vec = { - let json = serde_json::json!([ - { "deviceId": "480p", "width": 720, "height": 480, "resizeMode": "crop-and-scale" }, - { "deviceId": "720p", "width": 1280, "height": 720, "resizeMode": "crop-and-scale" }, - { "deviceId": "1080p", "width": 1920, "height": 1080, "resizeMode": "none" }, - { "deviceId": "1440p", "width": 2560, "height": 1440, "resizeMode": "none" }, - { "deviceId": "2160p", "width": 3840, "height": 2160, "resizeMode": "none" }, - ]); - serde_json::from_value(json).unwrap() - }; - - // Deserialize constraints from JSON: - let constraints: MediaTrackConstraints = { - let json = serde_json::json!({ - "width": { - "max": 2560, - }, - "height": { - "max": 1440, - }, - // Unsupported constraint, which should thus get ignored: - "frameRate": { - "exact": 30.0 - }, - // Ideal resize-mode: - "resizeMode": "none", - "advanced": [ - // The first advanced constraint set of "exact 800p" does not match - // any candidate and should thus get ignored by the algorithm: - { "height": 800 }, - // The second advanced constraint set of "no resizing" does match - // candidates and should thus be applied by the algorithm: - { "resizeMode": "none" }, - ] - }); - serde_json::from_value(json).unwrap() - }; - - // Resolve bare values to proper constraints: - let resolved_constraints = constraints.into_resolved(); - - // Sanitize constraints, removing empty and unsupported constraints: - let sanitized_constraints = resolved_constraints.into_sanitized(&supported_constraints); - - let candidates = select_settings_candidates( - &possible_settings, - &sanitized_constraints, - DeviceInformationExposureMode::Protected, - ) - .unwrap(); - - // Specify a tie-breaking policy - // - // A couple of basic policies are provided batteries-included, - // but for more sophisticated needs you can implement your own `TieBreakingPolicy`: - let tie_breaking_policy = - ClosestToIdealPolicy::new(possible_settings[2].clone(), &supported_constraints); - - let actual = tie_breaking_policy.select_candidate(candidates); - - let expected = &possible_settings[2]; - - assert_eq!(actual, expected); -} diff --git a/constraints/examples/macros.rs b/constraints/examples/macros.rs deleted file mode 100644 index 0cf9def33..000000000 --- a/constraints/examples/macros.rs +++ /dev/null @@ -1,104 +0,0 @@ -use std::iter::FromIterator; - -use webrtc_constraints::algorithms::{ - select_settings_candidates, ClosestToIdealPolicy, DeviceInformationExposureMode, - TieBreakingPolicy, -}; -use webrtc_constraints::macros::*; -use webrtc_constraints::property::all::name::*; -use webrtc_constraints::{settings, MediaTrackSupportedConstraints, ResizeMode}; - -fn main() { - let supported_constraints = - MediaTrackSupportedConstraints::from_iter(vec![&DEVICE_ID, &HEIGHT, &WIDTH, &RESIZE_MODE]); - - let possible_settings = vec![ - settings![ - &DEVICE_ID => "480p", - &HEIGHT => 480, - &WIDTH => 720, - &RESIZE_MODE => ResizeMode::crop_and_scale(), - ], - settings![ - &DEVICE_ID => "720p", - &HEIGHT => 720, - &WIDTH => 1280, - &RESIZE_MODE => ResizeMode::crop_and_scale(), - ], - settings![ - &DEVICE_ID => "1080p", - &HEIGHT => 1080, - &WIDTH => 1920, - &RESIZE_MODE => ResizeMode::none(), - ], - settings![ - &DEVICE_ID => "1440p", - &HEIGHT => 1440, - &WIDTH => 2560, - &RESIZE_MODE => ResizeMode::none(), - ], - settings![ - &DEVICE_ID => "2160p", - &HEIGHT => 2160, - &WIDTH => 3840, - &RESIZE_MODE => ResizeMode::none(), - ], - ]; - - let constraints = constraints! { - mandatory: { - &WIDTH => value_range_constraint!{ - max: 2560 - }, - &HEIGHT => value_range_constraint!{ - max: 1440 - }, - // Unsupported constraint, which should thus get ignored: - &FRAME_RATE => value_range_constraint!{ - exact: 30.0 - }, - }, - advanced: [ - // The first advanced constraint set of "exact 800p" does not match - // any candidate and should thus get ignored by the algorithm: - { - &HEIGHT => value_range_constraint!{ - exact: 800 - } - }, - // The second advanced constraint set of "no resizing" does match - // candidates and should thus be applied by the algorithm: - { - &RESIZE_MODE => value_constraint!{ - exact: ResizeMode::none() - } - }, - ] - }; - - // Resolve bare values to proper constraints: - let resolved_constraints = constraints.into_resolved(); - - // Sanitize constraints, removing empty and unsupported constraints: - let sanitized_constraints = resolved_constraints.to_sanitized(&supported_constraints); - - let candidates = select_settings_candidates( - &possible_settings, - &sanitized_constraints, - DeviceInformationExposureMode::Exposed, - ) - .unwrap(); - - // Specify a tie-breaking policy - // - // A couple of basic policies are provided batteries-included, - // but for more sophisticated needs you can implement your own `TieBreakingPolicy`: - let tie_breaking_policy = - ClosestToIdealPolicy::new(possible_settings[2].clone(), &supported_constraints); - - let actual = tie_breaking_policy.select_candidate(candidates); - - let expected = &possible_settings[2]; - - assert_eq!(actual, expected); -} diff --git a/constraints/examples/native.rs b/constraints/examples/native.rs deleted file mode 100644 index 6324ade18..000000000 --- a/constraints/examples/native.rs +++ /dev/null @@ -1,119 +0,0 @@ -use std::iter::FromIterator; - -use webrtc_constraints::algorithms::{ - select_settings_candidates, ClosestToIdealPolicy, DeviceInformationExposureMode, - TieBreakingPolicy, -}; -use webrtc_constraints::property::all::name::*; -use webrtc_constraints::{ - AdvancedMediaTrackConstraints, MandatoryMediaTrackConstraints, MediaTrackConstraintSet, - MediaTrackConstraints, MediaTrackSettings, MediaTrackSupportedConstraints, ResizeMode, - ResolvedValueConstraint, ResolvedValueRangeConstraint, ValueConstraint, ValueRangeConstraint, -}; - -fn main() { - let supported_constraints = - MediaTrackSupportedConstraints::from_iter(vec![&DEVICE_ID, &HEIGHT, &WIDTH, &RESIZE_MODE]); - - let possible_settings = vec![ - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "480p".into()), - (&HEIGHT, 480.into()), - (&WIDTH, 720.into()), - (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "720p".into()), - (&HEIGHT, 720.into()), - (&WIDTH, 1280.into()), - (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "1080p".into()), - (&HEIGHT, 1080.into()), - (&WIDTH, 1920.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "1440p".into()), - (&HEIGHT, 1440.into()), - (&WIDTH, 2560.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "2160p".into()), - (&HEIGHT, 2160.into()), - (&WIDTH, 3840.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]), - ]; - - let constraints = MediaTrackConstraints { - mandatory: MandatoryMediaTrackConstraints::from_iter([ - ( - &WIDTH, - ValueRangeConstraint::Constraint(ResolvedValueRangeConstraint::default().max(2560)) - .into(), - ), - ( - &HEIGHT, - ValueRangeConstraint::Constraint(ResolvedValueRangeConstraint::default().max(1440)) - .into(), - ), - // Unsupported constraint, which should thus get ignored: - ( - &FRAME_RATE, - ValueRangeConstraint::Constraint( - ResolvedValueRangeConstraint::default().exact(30.0), - ) - .into(), - ), - ]), - advanced: AdvancedMediaTrackConstraints::from_iter([ - // The first advanced constraint set of "exact 800p" does not match - // any candidate and should thus get ignored by the algorithm: - MediaTrackConstraintSet::from_iter([( - &HEIGHT, - ValueRangeConstraint::Constraint( - ResolvedValueRangeConstraint::default().exact(800), - ) - .into(), - )]), - // The second advanced constraint set of "no resizing" does match - // candidates and should thus be applied by the algorithm: - MediaTrackConstraintSet::from_iter([( - &RESIZE_MODE, - ValueConstraint::Constraint( - ResolvedValueConstraint::default().exact(ResizeMode::none()), - ) - .into(), - )]), - ]), - }; - - // Resolve bare values to proper constraints: - let resolved_constraints = constraints.into_resolved(); - - // Sanitize constraints, removing empty and unsupported constraints: - let sanitized_constraints = resolved_constraints.to_sanitized(&supported_constraints); - - let candidates = select_settings_candidates( - &possible_settings, - &sanitized_constraints, - DeviceInformationExposureMode::Exposed, - ) - .unwrap(); - - // Specify a tie-breaking policy - // - // A couple of basic policies are provided batteries-included, - // but for more sophisticated needs you can implement your own `TieBreakingPolicy`: - let tie_breaking_policy = - ClosestToIdealPolicy::new(possible_settings[2].clone(), &supported_constraints); - - let actual = tie_breaking_policy.select_candidate(candidates); - - let expected = &possible_settings[2]; - - assert_eq!(actual, expected); -} diff --git a/constraints/src/algorithms.rs b/constraints/src/algorithms.rs deleted file mode 100644 index 46d1f4043..000000000 --- a/constraints/src/algorithms.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Algorithms as defined in the ["Media Capture and Streams"][mediacapture_streams] spec. -//! -//! [mediacapture_streams]: https://www.w3.org/TR/mediacapture-streams/ - -mod fitness_distance; -mod select_settings; - -pub use self::fitness_distance::*; -pub use self::select_settings::*; diff --git a/constraints/src/algorithms/fitness_distance.rs b/constraints/src/algorithms/fitness_distance.rs deleted file mode 100644 index 973fb2e8a..000000000 --- a/constraints/src/algorithms/fitness_distance.rs +++ /dev/null @@ -1,132 +0,0 @@ -/// The function used to compute the "fitness distance" of a [setting][media_track_settings] value of a [`MediaStreamTrack`][media_stream_track] object. -/// -/// # W3C Spec Compliance -/// -/// The trait corresponds to the ["fitness distance"][fitness_distance] function in the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -/// [media_track_settings]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatracksettings -/// [fitness_distance]: https://www.w3.org/TR/mediacapture-streams/#dfn-fitness-distance -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams -pub trait FitnessDistance { - /// The type returned in the event of a computation error. - type Error; - - /// Computes the fitness distance of the given `subject` in the range of `0.0..=1.0`. - /// - /// A distance of `0.0` denotes it maximally fit, one of `1.0` as maximally unfit. - fn fitness_distance(&self, subject: Subject) -> Result; -} - -mod empty_constraint; -mod setting; -mod settings; -mod value_constraint; -mod value_range_constraint; -mod value_sequence_constraint; - -use std::cmp::Ordering; - -pub use self::setting::{SettingFitnessDistanceError, SettingFitnessDistanceErrorKind}; -pub use self::settings::SettingsFitnessDistanceError; - -fn nearly_cmp(lhs: f64, rhs: f64) -> Ordering { - // Based on: https://stackoverflow.com/a/32334103/227536 - - let epsilon: f64 = 128.0 * f64::EPSILON; - let abs_th: f64 = f64::MIN; - - debug_assert!(epsilon < 1.0); - - if lhs == rhs { - return Ordering::Equal; - } - - let diff = (lhs - rhs).abs(); - let norm = (lhs.abs() + rhs.abs()).min(f64::MAX); - - if diff < (epsilon * norm).max(abs_th) { - Ordering::Equal - } else if lhs < rhs { - Ordering::Less - } else { - Ordering::Greater - } -} - -fn is_nearly_greater_than_or_equal_to(actual: f64, min: f64) -> bool { - nearly_cmp(actual, min) != Ordering::Less -} - -fn is_nearly_less_than_or_equal_to(actual: f64, max: f64) -> bool { - nearly_cmp(actual, max) != Ordering::Greater -} - -fn is_nearly_equal_to(actual: f64, exact: f64) -> bool { - nearly_cmp(actual, exact) == Ordering::Equal -} - -fn relative_fitness_distance(actual: f64, ideal: f64) -> f64 { - // As specified in step 7 of the `fitness distance` algorithm: - // - // - // > For all positive numeric constraints […], - // > the fitness distance is the result of the formula - // > - // > ``` - // > (actual == ideal) ? 0 : |actual - ideal| / max(|actual|, |ideal|) - // > ``` - if (actual - ideal).abs() < f64::EPSILON { - 0.0 - } else { - let numerator = (actual - ideal).abs(); - let denominator = actual.abs().max(ideal.abs()); - if denominator.abs() < f64::EPSILON { - // Avoid division by zero crashes: - 0.0 - } else { - numerator / denominator - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - mod relative_fitness_distance { - #[test] - fn zero_distance() { - // Make sure we're not dividing by zero: - assert_eq!(super::relative_fitness_distance(0.0, 0.0), 0.0); - - assert_eq!(super::relative_fitness_distance(0.5, 0.5), 0.0); - assert_eq!(super::relative_fitness_distance(1.0, 1.0), 0.0); - assert_eq!(super::relative_fitness_distance(2.0, 2.0), 0.0); - } - - #[test] - fn fract_distance() { - assert_eq!(super::relative_fitness_distance(1.0, 2.0), 0.5); - assert_eq!(super::relative_fitness_distance(2.0, 1.0), 0.5); - - assert_eq!(super::relative_fitness_distance(0.5, 1.0), 0.5); - assert_eq!(super::relative_fitness_distance(1.0, 0.5), 0.5); - - assert_eq!(super::relative_fitness_distance(0.25, 0.5), 0.5); - assert_eq!(super::relative_fitness_distance(0.5, 0.25), 0.5); - } - - #[test] - fn one_distance() { - assert_eq!(super::relative_fitness_distance(0.0, 0.5), 1.0); - assert_eq!(super::relative_fitness_distance(0.5, 0.0), 1.0); - - assert_eq!(super::relative_fitness_distance(0.0, 1.0), 1.0); - assert_eq!(super::relative_fitness_distance(1.0, 0.0), 1.0); - - assert_eq!(super::relative_fitness_distance(0.0, 2.0), 1.0); - assert_eq!(super::relative_fitness_distance(2.0, 0.0), 1.0); - } - } -} diff --git a/constraints/src/algorithms/fitness_distance/empty_constraint.rs b/constraints/src/algorithms/fitness_distance/empty_constraint.rs deleted file mode 100644 index f27329a9e..000000000 --- a/constraints/src/algorithms/fitness_distance/empty_constraint.rs +++ /dev/null @@ -1,71 +0,0 @@ -use super::setting::SettingFitnessDistanceError; -use super::FitnessDistance; -use crate::constraint::EmptyConstraint; - -impl<'a, T> FitnessDistance> for EmptyConstraint { - type Error = SettingFitnessDistanceError; - - fn fitness_distance(&self, _setting: Option<&'a T>) -> Result { - // As specified in step 1 of the `SelectSettings` algorithm: - // - // - // > If an empty list has been given as the value for a constraint, - // > it MUST be interpreted as if the constraint were not specified - // > (in other words, an empty constraint == no constraint). - Ok(0.0) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - type Constraint = EmptyConstraint; - - macro_rules! test_empty_constraint { - ( - settings: $t:ty => $s:expr, - expected: $e:expr $(,)? - ) => { - let settings: &[Option<$t>] = $s; - let constraint = &Constraint {}; - for setting in settings { - let actual = constraint.fitness_distance(setting.as_ref()); - - assert_eq!(actual, $e); - } - }; - } - - #[test] - fn bool() { - test_empty_constraint!( - settings: bool => &[None, Some(false)], - expected: Ok(0.0) - ); - } - - #[test] - fn string() { - test_empty_constraint!( - settings: String => &[None, Some("foo".to_owned())], - expected: Ok(0.0) - ); - } - - #[test] - fn i64() { - test_empty_constraint!( - settings: i64 => &[None, Some(42)], - expected: Ok(0.0) - ); - } - - #[test] - fn f64() { - test_empty_constraint!( - settings: f64 => &[None, Some(42.0)], - expected: Ok(0.0) - ); - } -} diff --git a/constraints/src/algorithms/fitness_distance/setting.rs b/constraints/src/algorithms/fitness_distance/setting.rs deleted file mode 100644 index 440a65b1c..000000000 --- a/constraints/src/algorithms/fitness_distance/setting.rs +++ /dev/null @@ -1,532 +0,0 @@ -use super::FitnessDistance; -use crate::{MediaTrackSetting, ResolvedMediaTrackConstraint}; - -/// An error indicating a rejected fitness distance computation, -/// likely caused by a mismatched yet required constraint. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct SettingFitnessDistanceError { - /// The kind of the error (e.g. missing value, mismatching value, …). - pub kind: SettingFitnessDistanceErrorKind, - /// The required constraint value. - pub constraint: String, - /// The offending setting value. - pub setting: Option, -} - -/// The kind of the error (e.g. missing value, mismatching value, …). -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum SettingFitnessDistanceErrorKind { - /// Settings value is missing. - Missing, - /// Settings value is a mismatch. - Mismatch, - /// Settings value is too small. - TooSmall, - /// Settings value is too large. - TooLarge, -} - -impl<'a> FitnessDistance> for ResolvedMediaTrackConstraint { - type Error = SettingFitnessDistanceError; - - fn fitness_distance(&self, setting: Option<&'a MediaTrackSetting>) -> Result { - type Setting = MediaTrackSetting; - type Constraint = ResolvedMediaTrackConstraint; - - let setting = match setting { - Some(setting) => setting, - None => { - return if self.is_required() { - Err(Self::Error { - kind: SettingFitnessDistanceErrorKind::Missing, - constraint: format!("{}", self.to_required_only()), - setting: None, - }) - } else { - Ok(1.0) - } - } - }; - - let result = match (self, setting) { - // Empty constraint: - (ResolvedMediaTrackConstraint::Empty(constraint), setting) => { - constraint.fitness_distance(Some(setting)) - } - - // Boolean constraint: - (Constraint::Bool(constraint), Setting::Bool(setting)) => { - constraint.fitness_distance(Some(setting)) - } - (Constraint::Bool(constraint), Setting::Integer(setting)) => { - constraint.fitness_distance(Some(setting)) - } - (Constraint::Bool(constraint), Setting::Float(setting)) => { - constraint.fitness_distance(Some(setting)) - } - (Constraint::Bool(constraint), Setting::String(setting)) => { - constraint.fitness_distance(Some(setting)) - } - - // Integer constraint: - (Constraint::IntegerRange(_constraint), Setting::Bool(_setting)) => Ok(0.0), - (Constraint::IntegerRange(constraint), Setting::Integer(setting)) => { - constraint.fitness_distance(Some(setting)) - } - (Constraint::IntegerRange(constraint), Setting::Float(setting)) => { - constraint.fitness_distance(Some(setting)) - } - (Constraint::IntegerRange(_constraint), Setting::String(_setting)) => Ok(0.0), - - // Float constraint: - (Constraint::FloatRange(_constraint), Setting::Bool(_setting)) => Ok(0.0), - (Constraint::FloatRange(constraint), Setting::Integer(setting)) => { - constraint.fitness_distance(Some(setting)) - } - (Constraint::FloatRange(constraint), Setting::Float(setting)) => { - constraint.fitness_distance(Some(setting)) - } - (Constraint::FloatRange(_constraint), Setting::String(_setting)) => Ok(0.0), - - // String constraint: - (Constraint::String(_constraint), Setting::Bool(_setting)) => Ok(0.0), - (Constraint::String(_constraint), Setting::Integer(_setting)) => Ok(0.0), - (Constraint::String(_constraint), Setting::Float(_setting)) => Ok(0.0), - (Constraint::String(constraint), Setting::String(setting)) => { - constraint.fitness_distance(Some(setting)) - } - - // String sequence constraint: - (Constraint::StringSequence(_constraint), Setting::Bool(_setting)) => Ok(0.0), - (Constraint::StringSequence(_constraint), Setting::Integer(_setting)) => Ok(0.0), - (Constraint::StringSequence(_constraint), Setting::Float(_setting)) => Ok(0.0), - (Constraint::StringSequence(constraint), Setting::String(setting)) => { - constraint.fitness_distance(Some(setting)) - } - }; - - #[cfg(debug_assertions)] - if let Ok(fitness_distance) = result { - debug_assert!({ fitness_distance.is_finite() }); - } - - result - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::constraint::EmptyConstraint; - use crate::{MediaTrackSetting, ResolvedMediaTrackConstraint}; - - #[test] - fn empty_constraint() { - // As per step 1 of the `SelectSettings` algorithm from the W3C spec: - // - // - // > Each constraint specifies one or more values (or a range of values) for its property. - // > A property MAY appear more than once in the list of 'advanced' ConstraintSets. - // > If an empty list has been given as the value for a constraint, - // > it MUST be interpreted as if the constraint were not specified - // > (in other words, an empty constraint == no constraint). - let constraint = ResolvedMediaTrackConstraint::Empty(EmptyConstraint {}); - - let settings = [ - MediaTrackSetting::Bool(true), - MediaTrackSetting::Integer(42), - MediaTrackSetting::Float(4.2), - MediaTrackSetting::String("string".to_owned()), - ]; - - let expected = 0.0; - - for setting in settings { - let actual = constraint.fitness_distance(Some(&setting)).unwrap(); - - assert_eq!(actual, expected); - } - } - - mod bool_constraint { - use super::*; - use crate::ResolvedValueConstraint; - - #[test] - fn bool_setting() { - // As per step 8 of the `fitness distance` function from the W3C spec: - // - // - // > For all string, enum and boolean constraints - // > (e.g. deviceId, groupId, facingMode, resizeMode, echoCancellation), - // > the fitness distance is the result of the formula: - // > - // > ``` - // > (actual == ideal) ? 0 : 1 - // > ``` - - let scenarios = [(false, false), (false, true), (true, false), (true, true)]; - - for (constraint_value, setting_value) in scenarios { - let constraint = ResolvedMediaTrackConstraint::Bool(ResolvedValueConstraint { - exact: None, - ideal: Some(constraint_value), - }); - - let setting = MediaTrackSetting::Bool(setting_value); - - let actual = constraint.fitness_distance(Some(&setting)).unwrap(); - - let expected = if constraint_value == setting_value { - 0.0 - } else { - 1.0 - }; - - assert_eq!(actual, expected); - } - } - - #[test] - fn non_bool_settings() { - // As per step 4 of the `fitness distance` function from the W3C spec: - // - // - // > If constraintValue is a boolean, but the constrainable property is not, - // > then the fitness distance is based on whether the settings dictionary's - // > constraintName member exists or not, from the formula: - // > - // > ``` - // > (constraintValue == exists) ? 0 : 1 - // > ``` - - let settings = [ - MediaTrackSetting::Integer(42), - MediaTrackSetting::Float(4.2), - MediaTrackSetting::String("string".to_owned()), - ]; - - let scenarios = [(false, false), (false, true), (true, false), (true, true)]; - - for (constraint_value, setting_value) in scenarios { - let constraint = ResolvedMediaTrackConstraint::Bool(ResolvedValueConstraint { - exact: None, - ideal: Some(constraint_value), - }); - - for setting in settings.iter() { - // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)` - // once MSRV has passed 1.62.0: - let setting = if setting_value { Some(setting) } else { None }; - let actual = constraint.fitness_distance(setting).unwrap(); - - let expected = if setting_value { 0.0 } else { 1.0 }; - - assert_eq!(actual, expected); - } - } - } - } - - mod numeric_constraint { - use super::*; - use crate::ResolvedValueRangeConstraint; - - #[test] - fn missing_settings() { - // As per step 5 of the `fitness distance` function from the W3C spec: - // - // - // > If the settings dictionary's constraintName member does not exist, - // > the fitness distance is 1. - - let constraints = [ - ResolvedMediaTrackConstraint::IntegerRange(ResolvedValueRangeConstraint { - exact: None, - ideal: Some(42), - min: None, - max: None, - }), - ResolvedMediaTrackConstraint::FloatRange(ResolvedValueRangeConstraint { - exact: None, - ideal: Some(42.0), - min: None, - max: None, - }), - ]; - - for constraint in constraints { - let actual = constraint.fitness_distance(None).unwrap(); - - let expected = 1.0; - - assert_eq!(actual, expected); - } - } - - #[test] - fn compatible_settings() { - // As per step 7 of the `fitness distance` function from the W3C spec: - // - // - // > For all positive numeric constraints - // > (such as height, width, frameRate, aspectRatio, sampleRate and sampleSize), - // > the fitness distance is the result of the formula - // > - // > ``` - // > (actual == ideal) ? 0 : |actual - ideal| / max(|actual|, |ideal|) - // > ``` - - let settings = [ - MediaTrackSetting::Integer(21), - MediaTrackSetting::Float(21.0), - ]; - - let constraints = [ - ResolvedMediaTrackConstraint::IntegerRange(ResolvedValueRangeConstraint { - exact: None, - ideal: Some(42), - min: None, - max: None, - }), - ResolvedMediaTrackConstraint::FloatRange(ResolvedValueRangeConstraint { - exact: None, - ideal: Some(42.0), - min: None, - max: None, - }), - ]; - - for constraint in constraints { - for setting in settings.iter() { - let actual = constraint.fitness_distance(Some(setting)).unwrap(); - - let expected = 0.5; - - assert_eq!(actual, expected); - } - } - } - - #[test] - fn incompatible_settings() { - // As per step 3 of the `fitness distance` function from the W3C spec: - // - // - // > If the constraint does not apply for this type of object, the fitness distance is 0 - // > (that is, the constraint does not influence the fitness distance). - - let settings = [ - MediaTrackSetting::Bool(true), - MediaTrackSetting::String("string".to_owned()), - ]; - - let constraints = [ - ResolvedMediaTrackConstraint::IntegerRange(ResolvedValueRangeConstraint { - exact: None, - ideal: Some(42), - min: None, - max: None, - }), - ResolvedMediaTrackConstraint::FloatRange(ResolvedValueRangeConstraint { - exact: None, - ideal: Some(42.0), - min: None, - max: None, - }), - ]; - - for constraint in constraints { - for setting in settings.iter() { - let actual = constraint.fitness_distance(Some(setting)).unwrap(); - - let expected = 0.0; - - println!("constraint: {constraint:?}"); - println!("setting: {setting:?}"); - println!("actual: {actual:?}"); - println!("expected: {expected:?}"); - - assert_eq!(actual, expected); - } - } - } - } - - mod string_constraint { - use super::*; - use crate::ResolvedValueConstraint; - - #[test] - fn missing_settings() { - // As per step 5 of the `fitness distance` function from the W3C spec: - // - // - // > If the settings dictionary's constraintName member does not exist, - // > the fitness distance is 1. - - let constraint = ResolvedMediaTrackConstraint::String(ResolvedValueConstraint { - exact: None, - ideal: Some("constraint".to_owned()), - }); - - let actual = constraint.fitness_distance(None).unwrap(); - - let expected = 1.0; - - assert_eq!(actual, expected); - } - - #[test] - fn compatible_settings() { - // As per step 8 of the `fitness distance` function from the W3C spec: - // - // - // > For all string, enum and boolean constraints - // > (e.g. deviceId, groupId, facingMode, resizeMode, echoCancellation), - // > the fitness distance is the result of the formula: - // > - // > ``` - // > (actual == ideal) ? 0 : 1 - // > ``` - - let constraint = ResolvedMediaTrackConstraint::String(ResolvedValueConstraint { - exact: None, - ideal: Some("constraint".to_owned()), - }); - - let settings = [MediaTrackSetting::String("setting".to_owned())]; - - for setting in settings { - let actual = constraint.fitness_distance(Some(&setting)).unwrap(); - - let expected = 1.0; - - assert_eq!(actual, expected); - } - } - - #[test] - fn incompatible_settings() { - // As per step 3 of the `fitness distance` function from the W3C spec: - // - // - // > If the constraint does not apply for this type of object, the fitness distance is 0 - // > (that is, the constraint does not influence the fitness distance). - - let constraint = ResolvedMediaTrackConstraint::String(ResolvedValueConstraint { - exact: None, - ideal: Some("string".to_owned()), - }); - - let settings = [ - MediaTrackSetting::Bool(true), - MediaTrackSetting::Integer(42), - MediaTrackSetting::Float(4.2), - ]; - - for setting in settings { - let actual = constraint.fitness_distance(Some(&setting)).unwrap(); - - let expected = 0.0; - - println!("constraint: {constraint:?}"); - println!("setting: {setting:?}"); - println!("actual: {actual:?}"); - println!("expected: {expected:?}"); - - assert_eq!(actual, expected); - } - } - } - - mod string_sequence_constraint { - use super::*; - use crate::ResolvedValueSequenceConstraint; - - #[test] - fn missing_settings() { - // As per step 5 of the `fitness distance` function from the W3C spec: - // - // - // > If the settings dictionary's constraintName member does not exist, - // > the fitness distance is 1. - - let constraint = - ResolvedMediaTrackConstraint::StringSequence(ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec!["constraint".to_owned()]), - }); - - let actual = constraint.fitness_distance(None).unwrap(); - - let expected = 1.0; - - assert_eq!(actual, expected); - } - - #[test] - fn compatible_settings() { - // As per step 8 of the `fitness distance` function from the W3C spec: - // - // - // > For all string, enum and boolean constraints - // > (e.g. deviceId, groupId, facingMode, resizeMode, echoCancellation), - // > the fitness distance is the result of the formula: - // > - // > ``` - // > (actual == ideal) ? 0 : 1 - // > ``` - // - // As well as the preliminary definition: - // - // > For string valued constraints, we define "==" below to be true if one of the - // > values in the sequence is exactly the same as the value being compared against. - - let constraint = - ResolvedMediaTrackConstraint::StringSequence(ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec!["constraint".to_owned()]), - }); - - let settings = [MediaTrackSetting::String("setting".to_owned())]; - - for setting in settings { - let actual = constraint.fitness_distance(Some(&setting)).unwrap(); - - let expected = 1.0; - - assert_eq!(actual, expected); - } - } - - #[test] - fn incompatible_settings() { - // As per step 3 of the `fitness distance` function from the W3C spec: - // - // - // > If the constraint does not apply for this type of object, the fitness distance is 0 - // > (that is, the constraint does not influence the fitness distance). - - let constraint = - ResolvedMediaTrackConstraint::StringSequence(ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec!["constraint".to_owned()]), - }); - - let settings = [ - MediaTrackSetting::Bool(true), - MediaTrackSetting::Integer(42), - MediaTrackSetting::Float(4.2), - ]; - - for setting in settings { - let actual = constraint.fitness_distance(Some(&setting)).unwrap(); - - let expected = 0.0; - - assert_eq!(actual, expected); - } - } - } -} diff --git a/constraints/src/algorithms/fitness_distance/settings.rs b/constraints/src/algorithms/fitness_distance/settings.rs deleted file mode 100644 index d37f7c3ec..000000000 --- a/constraints/src/algorithms/fitness_distance/settings.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::collections::HashMap; - -use super::setting::SettingFitnessDistanceError; -use super::FitnessDistance; -use crate::{MediaTrackProperty, MediaTrackSettings, SanitizedMediaTrackConstraintSet}; - -/// A list of media track properties and their corresponding fitness distance errors. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct SettingsFitnessDistanceError { - /// Setting errors per media track property. - pub setting_errors: HashMap, -} - -impl<'a> FitnessDistance<&'a MediaTrackSettings> for SanitizedMediaTrackConstraintSet { - type Error = SettingsFitnessDistanceError; - - fn fitness_distance(&self, settings: &'a MediaTrackSettings) -> Result { - let results: HashMap = self - .iter() - .map(|(property, constraint)| { - let setting = settings.get(property); - let result = constraint.fitness_distance(setting); - (property.clone(), result) - }) - .collect(); - - let mut total_fitness_distance = 0.0; - - let mut setting_errors: HashMap = - Default::default(); - - for (property, result) in results.into_iter() { - match result { - Ok(fitness_distance) => total_fitness_distance += fitness_distance, - Err(error) => { - setting_errors.insert(property, error); - } - } - } - - if setting_errors.is_empty() { - Ok(total_fitness_distance) - } else { - Err(SettingsFitnessDistanceError { setting_errors }) - } - } -} diff --git a/constraints/src/algorithms/fitness_distance/value_constraint.rs b/constraints/src/algorithms/fitness_distance/value_constraint.rs deleted file mode 100644 index 3ed6755d9..000000000 --- a/constraints/src/algorithms/fitness_distance/value_constraint.rs +++ /dev/null @@ -1,218 +0,0 @@ -use super::setting::SettingFitnessDistanceError; -use super::{FitnessDistance, SettingFitnessDistanceErrorKind}; -use crate::constraint::ResolvedValueConstraint; - -// Standard implementation for value constraints of arbitrary `Setting` and `Constraint` -// types where `Setting: PartialEq`: -macro_rules! impl_non_numeric_value_constraint { - (setting: $s:ty, constraint: $c:ty) => { - impl<'a> FitnessDistance> for ResolvedValueConstraint<$c> - where - $s: PartialEq<$c>, - { - type Error = SettingFitnessDistanceError; - - fn fitness_distance(&self, setting: Option<&'a $s>) -> Result { - if let Some(exact) = self.exact.as_ref() { - // As specified in step 2 of the `fitness distance` algorithm: - // - // - // > If the constraint is required (constraintValue either contains - // > one or more members named […] 'exact' […]), and the settings - // > dictionary's constraintName member's value does not satisfy the - // > constraint or doesn't exist, the fitness distance is positive infinity. - match setting { - Some(actual) if actual == exact => {} - Some(setting) => { - return Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: format!("{}", self.to_required_only()), - setting: Some(format!("{:?}", setting)), - }) - } - None => { - return Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Missing, - constraint: format!("{}", self.to_required_only()), - setting: None, - }) - } - }; - } - - if let Some(ideal) = self.ideal.as_ref() { - match setting { - Some(actual) if actual == ideal => { - // As specified in step 8 of the `fitness distance` algorithm: - // - // - // > For all string, enum and boolean constraints […], - // > the fitness distance is the result of the formula: - // > - // > ``` - // > (actual == ideal) ? 0 : 1 - // > ``` - Ok(0.0) - } - _ => { - // As specified in step 5 of the `fitness distance` algorithm: - // - // - // > If the settings dictionary's `constraintName` member - // > does not exist, the fitness distance is 1. - Ok(1.0) - } - } - } else { - // As specified in step 6 of the `fitness distance` algorithm: - // - // - // > If no ideal value is specified (constraintValue either - // > contains no member named 'ideal', or, if bare values are to be - // > treated as 'ideal', isn't a bare value), the fitness distance is 0. - Ok(0.0) - } - } - } - }; -} - -impl_non_numeric_value_constraint!(setting: bool, constraint: bool); -impl_non_numeric_value_constraint!(setting: String, constraint: String); - -// Specialized implementations for floating-point value constraints (and settings): - -macro_rules! impl_numeric_value_constraint { - (setting: $s:ty, constraint: $c:ty) => { - impl<'a> FitnessDistance> for ResolvedValueConstraint<$c> { - type Error = SettingFitnessDistanceError; - - fn fitness_distance(&self, setting: Option<&'a $s>) -> Result { - if let Some(exact) = self.exact { - // As specified in step 2 of the `fitness distance` algorithm: - // - // - // > If the constraint is required (constraintValue either contains - // > one or more members named […] 'exact' […]), and the settings - // > dictionary's constraintName member's value does not satisfy the - // > constraint or doesn't exist, the fitness distance is positive infinity. - match setting { - Some(&actual) if super::is_nearly_equal_to(actual as f64, exact as f64) => { - } - Some(setting) => { - return Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: format!("{}", self.to_required_only()), - setting: Some(format!("{:?}", setting)), - }) - } - None => { - return Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Missing, - constraint: format!("{}", self.to_required_only()), - setting: None, - }) - } - }; - } - - if let Some(ideal) = self.ideal { - match setting { - Some(&actual) => { - let actual: f64 = actual as f64; - let ideal: f64 = ideal as f64; - // As specified in step 7 of the `fitness distance` algorithm: - // - // - // > For all positive numeric constraints […], - // > the fitness distance is the result of the formula - // > - // > ``` - // > (actual == ideal) ? 0 : |actual - ideal| / max(|actual|, |ideal|) - // > ``` - Ok(super::relative_fitness_distance(actual, ideal)) - } - None => { - // As specified in step 5 of the `fitness distance` algorithm: - // - // - // > If the settings dictionary's `constraintName` member - // > does not exist, the fitness distance is 1. - Ok(1.0) - } - } - } else { - // As specified in step 6 of the `fitness distance` algorithm: - // - // - // > If no ideal value is specified (constraintValue either - // > contains no member named 'ideal', or, if bare values are to be - // > treated as 'ideal', isn't a bare value), the fitness distance is 0. - Ok(0.0) - } - } - } - }; -} - -impl_numeric_value_constraint!(setting: f64, constraint: f64); -impl_numeric_value_constraint!(setting: i64, constraint: u64); -impl_numeric_value_constraint!(setting: i64, constraint: f64); -impl_numeric_value_constraint!(setting: f64, constraint: u64); - -// Specialized implementations for boolean value constraints of mismatching -// and thus either "existence"-checked or ignored setting types: -macro_rules! impl_exists_value_constraint { - (settings: [$($s:ty),+], constraint: bool) => { - $(impl_exists_value_constraint!(setting: $s, constraint: bool);)+ - }; - (setting: $s:ty, constraint: bool) => { - impl<'a> FitnessDistance> for ResolvedValueConstraint { - type Error = SettingFitnessDistanceError; - - fn fitness_distance(&self, setting: Option<&'a $s>) -> Result { - // A bare boolean value (as described in step 4 of the - // `fitness distance` algorithm) gets parsed as: - // ``` - // ResolvedValueConstraint:: { - // exact: Some(bare), - // ideal: None, - // } - // ``` - // - // For all other configurations we just interpret it as an incompatible constraint. - match self.exact { - // As specified in step 4 of the `fitness distance` algorithm: - // - // - // > If constraintValue is a boolean, but the constrainable property is not, - // > then the fitness distance is based on whether the settings dictionary's - // > `constraintName` member exists or not, from the formula: - // > - // > ``` - // > (constraintValue == exists) ? 0 : 1 - // > ``` - Some(expected) => { - if setting.is_some() == expected { - Ok(0.0) - } else { - Ok(1.0) - } - } - // As specified in step 3 of the `fitness distance` algorithm: - // - // - // > If the constraint does not apply for this type of object, - // > the fitness distance is 0 (that is, the constraint does not - // > influence the fitness distance). - None => Ok(0.0), - } - } - } - }; -} - -impl_exists_value_constraint!(settings: [String, i64, f64], constraint: bool); - -#[cfg(test)] -mod tests; diff --git a/constraints/src/algorithms/fitness_distance/value_constraint/tests.rs b/constraints/src/algorithms/fitness_distance/value_constraint/tests.rs deleted file mode 100644 index 7090c6626..000000000 --- a/constraints/src/algorithms/fitness_distance/value_constraint/tests.rs +++ /dev/null @@ -1,144 +0,0 @@ -use super::*; - -macro_rules! generate_value_constraint_tests { - ( - tests: [ - $({ - name: $ti:ident, - settings: $st:ty => $se:expr $(,)? - }),+ $(,)? - ], - constraints: $ct:ty => $ce:expr, - expected: $e:expr $(,)? - ) => { - generate_value_constraint_tests!( - tests: [ - $({ - name: $ti, - settings: $st => $se, - constraints: $ct => $ce, - }),+ - ], - expected: $e - ); - }; - ( - tests: [ - $({ - name: $ti:ident, - settings: $st:ty => $se:expr, - constraints: $ct:ty => $ce:expr $(,)? - }),+ $(,)? - ], - expected: $e:expr $(,)? - ) => { - generate_value_constraint_tests!( - tests: [ - $({ - name: $ti, - settings: $st => $se, - constraints: $ct => $ce, - }),+ - ], - validate: |result| { - assert_eq!(result, $e); - } - ); - }; - ( - tests: [ - $({ - name: $ti:ident, - settings: $st:ty => $se:expr, - constraints: $ct:ty => $ce:expr $(,)? - }),+ $(,)? - ], - validate: |$a:ident| $b:block - ) => { - $( - #[test] - fn $ti() { - test_value_constraint!( - settings: $st => $se, - constraints: $ct => $ce, - validate: |$a| $b - ); - } - )+ - }; -} - -macro_rules! test_value_constraint { - ( - settings: $st:ty => $se:expr, - constraints: $ct:ty => $ce:expr, - expected: $e:expr $(,)? - ) => { - test_value_constraint!( - settings: $st => $se, - constraints: $ct => $ce, - validate: |result| { - assert_eq!(result, $e); - } - ); - }; - ( - settings: $st:ty => $se:expr, - constraints: $ct:ty => $ce:expr, - validate: |$a:ident| $b:block - ) => {{ - let settings: &[Option<$st>] = $se; - let constraints: &[ResolvedValueConstraint<$ct>] = $ce; - - for constraint in constraints { - for setting in settings { - let closure = |$a| $b; - let actual = constraint.fitness_distance(setting.as_ref()); - closure(actual); - } - } - }}; - ( - checks: [ - $({ - setting: $st:ty => $se:expr, - constraint: $ct:ty => $ce:expr, - expected: $ee:expr $(,)? - }),+ $(,)? - ] - ) => { - test_value_constraint!( - checks: [ - $({ - setting: $st => $se, - constraint: $ct => $ce, - expected: $ee, - }),+ - ], - validate: |actual, expected| { - assert_eq!(actual, expected); - } - ); - }; - ( - checks: [ - $({ - setting: $st:ty => $se:expr, - constraint: $ct:ty => $ce:expr, - expected: $ee:expr $(,)? - }),+ $(,)? - ], - validate: |$ai:ident, $ei:ident| $b:block - ) => {{ - $({ - let closure = |$ai, $ei| $b; - let actual = $ce.fitness_distance($se.as_ref()); - closure(actual, $ee); - })+ - }}; -} - -mod bool; -mod f64; -mod string; -mod u64; diff --git a/constraints/src/algorithms/fitness_distance/value_constraint/tests/bool.rs b/constraints/src/algorithms/fitness_distance/value_constraint/tests/bool.rs deleted file mode 100644 index aec57d984..000000000 --- a/constraints/src/algorithms/fitness_distance/value_constraint/tests/bool.rs +++ /dev/null @@ -1,283 +0,0 @@ -use super::*; - -mod basic { - use super::*; - - mod zero_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: bool_setting, - settings: bool => &[Some(true)], - }, - ], - constraints: bool => &[ - ResolvedValueConstraint { - exact: None, - ideal: None, - }, - ResolvedValueConstraint { - exact: None, - ideal: Some(true), - }, - ], - expected: Ok(0.0) - ); - - generate_value_constraint_tests!( - tests: [ - { - name: string_setting, - settings: String => &[Some("foo".to_owned())], - }, - { - name: i64_setting, - settings: i64 => &[Some(42)], - }, - { - name: f64_setting, - settings: f64 => &[Some(42.0)], - }, - ], - constraints: bool => &[ - ResolvedValueConstraint { - exact: None, - ideal: Some(false), - }, - ], - expected: Ok(0.0) - ); - } - - mod one_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: bool_setting, - settings: bool => &[None, Some(false)], - }, - ], - constraints: bool => &[ResolvedValueConstraint { - exact: None, - ideal: Some(true), - }], - expected: Ok(1.0) - ); - } -} - -mod required { - use super::*; - - mod zero_distance { - use super::*; - // A constraint that does apply for a type of setting, - // is expected to return a fitness distance of `0`, - // iff the setting matches the constraint: - generate_value_constraint_tests!( - tests: [ - { - name: bool_setting, - settings: bool => &[Some(true)], - }, - ], - constraints: bool => &[ResolvedValueConstraint { - exact: Some(true), - ideal: None, - }], - expected: Ok(0.0) - ); - } - - mod inf_distance { - use super::*; - - mod missing { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: bool_setting, - settings: bool => &[None], - }, - ], - constraints: bool => &[ - ResolvedValueConstraint { - exact: Some(true), - ideal: None, - }, - ResolvedValueConstraint { - exact: Some(true), - ideal: Some(true), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Missing, - constraint: "(x == true)".to_owned(), - setting: None, - }) - ); - } - - mod mismatch { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: bool_setting, - settings: bool => &[Some(false)], - }, - ], - constraints: bool => &[ - ResolvedValueConstraint { - exact: Some(true), - ideal: None, - }, - ResolvedValueConstraint { - exact: Some(true), - ideal: Some(true), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: "(x == true)".to_owned(), - setting: Some("false".to_owned()), - }) - ); - } - } - - // Required boolean constraints have specialized logic as per - // rule 4 of the fitness distance algorithm specification: - // - - mod specialization { - use super::*; - - mod expected { - use super::*; - - mod existing { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: string_setting, - settings: String => &[Some("foo".to_owned())], - }, - { - name: i64_setting, - settings: i64 => &[Some(42)], - }, - { - name: f64_setting, - settings: f64 => &[Some(42.0)], - }, - ], - constraints: bool => &[ - ResolvedValueConstraint { - exact: Some(true), - ideal: None, - }, - ], - expected: Ok(0.0) - ); - } - - mod missing { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: string_setting, - settings: String => &[None], - }, - { - name: i64_setting, - settings: i64 => &[None], - }, - { - name: f64_setting, - settings: f64 => &[None], - }, - ], - constraints: bool => &[ - ResolvedValueConstraint { - exact: Some(true), - ideal: None, - }, - ], - expected: Ok(1.0) - ); - } - } - - mod unexpected { - use super::*; - - mod existing { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: string_setting, - settings: String => &[Some("foo".to_owned())], - }, - { - name: i64_setting, - settings: i64 => &[Some(42)], - }, - { - name: f64_setting, - settings: f64 => &[Some(42.0)], - }, - ], - constraints: bool => &[ - ResolvedValueConstraint { - exact: Some(false), - ideal: None, - }, - ], - expected: Ok(1.0) - ); - } - - mod missing { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: string_setting, - settings: String => &[None], - }, - { - name: i64_setting, - settings: i64 => &[None], - }, - { - name: f64_setting, - settings: f64 => &[None], - }, - ], - constraints: bool => &[ - ResolvedValueConstraint { - exact: Some(false), - ideal: None, - }, - ], - expected: Ok(0.0) - ); - } - } - } -} diff --git a/constraints/src/algorithms/fitness_distance/value_constraint/tests/f64.rs b/constraints/src/algorithms/fitness_distance/value_constraint/tests/f64.rs deleted file mode 100644 index 0ad486021..000000000 --- a/constraints/src/algorithms/fitness_distance/value_constraint/tests/f64.rs +++ /dev/null @@ -1,243 +0,0 @@ -use super::*; - -mod basic { - use super::*; - - mod zero_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(42)], - }, - { - name: f64_setting, - settings: f64 => &[Some(42.0)], - }, - ], - constraints: f64 => &[ - ResolvedValueConstraint { - exact: None, - ideal: None, - }, - ResolvedValueConstraint { - exact: None, - ideal: Some(42.0), - }, - ], - expected: Ok(0.0) - ); - } - - mod fract_distance { - use super::*; - - #[test] - fn i64_setting() { - test_value_constraint!( - checks: [ - { - setting: i64 => Some(1), - constraint: f64 => ResolvedValueConstraint { - exact: None, - ideal: Some(4.0), - }, - expected: Ok(0.75), - }, - { - setting: i64 => Some(2), - constraint: f64 => ResolvedValueConstraint { - exact: None, - ideal: Some(4.0), - }, - expected: Ok(0.5), - }, - { - setting: i64 => Some(3), - constraint: f64 => ResolvedValueConstraint { - exact: None, - ideal: Some(4.0), - }, - expected: Ok(0.25), - }, - ], - validate: |actual, expected| { - assert_eq!(actual, expected); - } - ); - } - - #[test] - fn f64_setting() { - test_value_constraint!( - checks: [ - { - setting: f64 => Some(1.0), - constraint: f64 => ResolvedValueConstraint { - exact: None, - ideal: Some(4.0), - }, - expected: Ok(0.75), - }, - { - setting: f64 => Some(2.0), - constraint: f64 => ResolvedValueConstraint { - exact: None, - ideal: Some(4.0), - }, - expected: Ok(0.5), - }, - { - setting: f64 => Some(3.0), - constraint: f64 => ResolvedValueConstraint { - exact: None, - ideal: Some(4.0), - }, - expected: Ok(0.25), - }, - ], - validate: |actual, expected| { - assert_eq!(actual, expected); - } - ); - } - } - - mod one_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(0)], - }, - { - name: f64_setting, - settings: f64 => &[Some(0.0)], - }, - ], - constraints: f64 => &[ResolvedValueConstraint { - exact: None, - ideal: Some(42.0), - }], - expected: Ok(1.0) - ); - } -} - -mod required { - use super::*; - - mod zero_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(42)], - }, - { - name: f64_setting, - settings: f64 => &[Some(42.0)], - }, - ], - constraints: f64 => &[ResolvedValueConstraint { - exact: Some(42.0), - ideal: None, - }], - expected: Ok(0.0) - ); - } - - mod inf_distance { - use super::*; - - mod missing { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[None], - }, - { - name: f64_setting, - settings: f64 => &[None], - }, - ], - constraints: f64 => &[ - ResolvedValueConstraint { - exact: Some(42.0), - ideal: None, - }, - ResolvedValueConstraint { - exact: Some(42.0), - ideal: Some(42.0), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Missing, - constraint: "(x == 42.0)".to_owned(), - setting: None, - }) - ); - } - - mod mismatch { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(0)], - }, - ], - constraints: f64 => &[ - ResolvedValueConstraint { - exact: Some(42.0), - ideal: None, - }, - ResolvedValueConstraint { - exact: Some(42.0), - ideal: Some(42.0), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: "(x == 42.0)".to_owned(), - setting: Some("0".to_owned()), - }) - ); - - generate_value_constraint_tests!( - tests: [ - { - name: f64_setting, - settings: f64 => &[Some(0.0)], - }, - ], - constraints: f64 => &[ - ResolvedValueConstraint { - exact: Some(42.0), - ideal: None, - }, - ResolvedValueConstraint { - exact: Some(42.0), - ideal: Some(42.0), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: "(x == 42.0)".to_owned(), - setting: Some("0.0".to_owned()), - }) - ); - } - } -} diff --git a/constraints/src/algorithms/fitness_distance/value_constraint/tests/string.rs b/constraints/src/algorithms/fitness_distance/value_constraint/tests/string.rs deleted file mode 100644 index ca663a446..000000000 --- a/constraints/src/algorithms/fitness_distance/value_constraint/tests/string.rs +++ /dev/null @@ -1,132 +0,0 @@ -use super::*; - -mod basic { - use super::*; - - mod zero_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: string_setting, - settings: String => &[Some("foo".to_owned())], - }, - ], - constraints: String => &[ - ResolvedValueConstraint { - exact: None, - ideal: None, - }, - ResolvedValueConstraint { - exact: None, - ideal: Some("foo".to_owned()), - }, - ], - expected: Ok(0.0) - ); - } - - mod one_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: string_setting, - settings: String => &[None, Some("bar".to_owned())], - }, - ], - constraints: String => &[ResolvedValueConstraint { - exact: None, - ideal: Some("foo".to_owned()), - }], - expected: Ok(1.0) - ); - } -} - -mod required { - use super::*; - - mod zero_distance { - use super::*; - - // A constraint that does apply for a type of setting, - // is expected to return a fitness distance of `0`, - // iff the setting matches the constraint: - generate_value_constraint_tests!( - tests: [ - { - name: string_setting, - settings: String => &[Some("foo".to_owned())], - }, - ], - constraints: String => &[ResolvedValueConstraint { - exact: Some("foo".to_owned()), - ideal: None, - }], - expected: Ok(0.0) - ); - } - - mod inf_distance { - use super::*; - - mod missing { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: string_setting, - settings: String => &[None], - }, - ], - constraints: String => &[ - ResolvedValueConstraint { - exact: Some("foo".to_owned()), - ideal: None, - }, - ResolvedValueConstraint { - exact: Some("foo".to_owned()), - ideal: Some("foo".to_owned()), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Missing, - constraint: "(x == \"foo\")".to_owned(), - setting: None, - }) - ); - } - - mod mismatch { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: string_setting, - settings: String => &[Some("bar".to_owned())], - }, - ], - constraints: String => &[ - ResolvedValueConstraint { - exact: Some("foo".to_owned()), - ideal: None, - }, - ResolvedValueConstraint { - exact: Some("foo".to_owned()), - ideal: Some("foo".to_owned()), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: "(x == \"foo\")".to_owned(), - setting: Some("\"bar\"".to_owned()), - }) - ); - } - } -} diff --git a/constraints/src/algorithms/fitness_distance/value_constraint/tests/u64.rs b/constraints/src/algorithms/fitness_distance/value_constraint/tests/u64.rs deleted file mode 100644 index 81be604ee..000000000 --- a/constraints/src/algorithms/fitness_distance/value_constraint/tests/u64.rs +++ /dev/null @@ -1,243 +0,0 @@ -use super::*; - -mod basic { - use super::*; - - mod zero_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(42)], - }, - { - name: f64_setting, - settings: f64 => &[Some(42.0)], - }, - ], - constraints: u64 => &[ - ResolvedValueConstraint { - exact: None, - ideal: None, - }, - ResolvedValueConstraint { - exact: None, - ideal: Some(42), - }, - ], - expected: Ok(0.0) - ); - } - - mod fract_distance { - use super::*; - - #[test] - fn i64_setting() { - test_value_constraint!( - checks: [ - { - setting: i64 => Some(1), - constraint: i64 => ResolvedValueConstraint { - exact: None, - ideal: Some(4), - }, - expected: Ok(0.75), - }, - { - setting: i64 => Some(2), - constraint: i64 => ResolvedValueConstraint { - exact: None, - ideal: Some(4), - }, - expected: Ok(0.5), - }, - { - setting: i64 => Some(3), - constraint: i64 => ResolvedValueConstraint { - exact: None, - ideal: Some(4), - }, - expected: Ok(0.25), - }, - ], - validate: |actual, expected| { - assert_eq!(actual, expected); - } - ); - } - - #[test] - fn f64_setting() { - test_value_constraint!( - checks: [ - { - setting: f64 => Some(1.0), - constraint: u64 => ResolvedValueConstraint { - exact: None, - ideal: Some(4), - }, - expected: Ok(0.75), - }, - { - setting: f64 => Some(2.0), - constraint: u64 => ResolvedValueConstraint { - exact: None, - ideal: Some(4), - }, - expected: Ok(0.5), - }, - { - setting: f64 => Some(3.0), - constraint: u64 => ResolvedValueConstraint { - exact: None, - ideal: Some(4), - }, - expected: Ok(0.25), - }, - ], - validate: |actual, expected| { - assert_eq!(actual, expected); - } - ); - } - } - - mod one_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[None, Some(0)], - }, - { - name: f64_setting, - settings: f64 => &[None, Some(0.0)], - }, - ], - constraints: u64 => &[ResolvedValueConstraint { - exact: None, - ideal: Some(42), - }], - expected: Ok(1.0) - ); - } -} - -mod required { - use super::*; - - mod zero_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(42)], - }, - { - name: f64_setting, - settings: f64 => &[Some(42.0)], - }, - ], - constraints: u64 => &[ResolvedValueConstraint { - exact: Some(42), - ideal: None, - }], - expected: Ok(0.0) - ); - } - - mod inf_distance { - use super::*; - - mod missing { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[None], - }, - { - name: f64_setting, - settings: f64 => &[None], - }, - ], - constraints: u64 => &[ - ResolvedValueConstraint { - exact: Some(42), - ideal: None, - }, - ResolvedValueConstraint { - exact: Some(42), - ideal: Some(42), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Missing, - constraint: "(x == 42)".to_owned(), - setting: None, - }) - ); - } - - mod mismatch { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(0)], - }, - ], - constraints: u64 => &[ - ResolvedValueConstraint { - exact: Some(42), - ideal: None, - }, - ResolvedValueConstraint { - exact: Some(42), - ideal: Some(42), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: "(x == 42)".to_owned(), - setting: Some("0".to_owned()), - }) - ); - - generate_value_constraint_tests!( - tests: [ - { - name: f64_setting, - settings: f64 => &[Some(0.0)], - }, - ], - constraints: u64 => &[ - ResolvedValueConstraint { - exact: Some(42), - ideal: None, - }, - ResolvedValueConstraint { - exact: Some(42), - ideal: Some(42), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: "(x == 42)".to_owned(), - setting: Some("0.0".to_owned()), - }) - ); - } - } -} diff --git a/constraints/src/algorithms/fitness_distance/value_range_constraint.rs b/constraints/src/algorithms/fitness_distance/value_range_constraint.rs deleted file mode 100644 index 68147c768..000000000 --- a/constraints/src/algorithms/fitness_distance/value_range_constraint.rs +++ /dev/null @@ -1,172 +0,0 @@ -use super::setting::SettingFitnessDistanceError; -use super::{FitnessDistance, SettingFitnessDistanceErrorKind}; -use crate::ResolvedValueRangeConstraint; - -macro_rules! impl_value_range_constraint { - (setting: $s:ty, constraint: $c:ty) => { - impl<'a> FitnessDistance> for ResolvedValueRangeConstraint<$c> { - type Error = SettingFitnessDistanceError; - - fn fitness_distance(&self, setting: Option<&'a $s>) -> Result { - if let Some(exact) = self.exact { - // As specified in step 2 of the `fitness distance` algorithm: - // - // - // > If the constraint is required (constraintValue either contains - // > one or more members named […] 'exact' […]), and the settings - // > dictionary's constraintName member's value does not satisfy the - // > constraint or doesn't exist, the fitness distance is positive infinity. - match setting { - Some(&actual) if super::is_nearly_equal_to(actual as f64, exact as f64) => { - } - Some(setting) => { - return Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: format!("{}", self.to_required_only()), - setting: Some(format!("{:?}", setting)), - }) - } - None => { - return Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Missing, - constraint: format!("{}", self.to_required_only()), - setting: None, - }) - } - }; - } - - if let Some(min) = self.min { - // As specified in step 2 of the `fitness distance` algorithm: - // - // - // > If the constraint is required (constraintValue either contains - // > one or more members named […] 'min' […]), and the settings - // > dictionary's constraintName member's value does not satisfy the - // > constraint or doesn't exist, the fitness distance is positive infinity. - match setting { - Some(&actual) - if super::is_nearly_greater_than_or_equal_to( - actual as f64, - min as f64, - ) => {} - Some(setting) => { - return Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::TooSmall, - constraint: format!("{}", self.to_required_only()), - setting: Some(format!("{:?}", setting)), - }) - } - None => { - return Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Missing, - constraint: format!("{}", self.to_required_only()), - setting: None, - }) - } - }; - } - - if let Some(max) = self.max { - // As specified in step 2 of the `fitness distance` algorithm: - // - // - // > If the constraint is required (constraintValue either contains - // > one or more members named […] 'max' […]), and the settings - // > dictionary's constraintName member's value does not satisfy the - // > constraint or doesn't exist, the fitness distance is positive infinity. - match setting { - Some(&actual) - if super::is_nearly_less_than_or_equal_to( - actual as f64, - max as f64, - ) => {} - Some(setting) => { - return Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::TooLarge, - constraint: format!("{}", self.to_required_only()), - setting: Some(format!("{:?}", setting)), - }) - } - None => { - return Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Missing, - constraint: format!("{}", self.to_required_only()), - setting: None, - }) - } - }; - } - - if let Some(ideal) = self.ideal { - match setting { - Some(&actual) => { - let actual: f64 = actual as f64; - let ideal: f64 = ideal as f64; - // As specified in step 7 of the `fitness distance` algorithm: - // - // - // > For all positive numeric constraints […], - // > the fitness distance is the result of the formula - // > - // > ``` - // > (actual == ideal) ? 0 : |actual - ideal| / max(|actual|, |ideal|) - // > ``` - Ok(super::relative_fitness_distance(actual, ideal)) - } - None => { - // As specified in step 5 of the `fitness distance` algorithm: - // - // - // > If the settings dictionary's `constraintName` member - // > does not exist, the fitness distance is 1. - Ok(1.0) - } - } - } else { - // As specified in step 6 of the `fitness distance` algorithm: - // - // - // > If no ideal value is specified (constraintValue either - // > contains no member named 'ideal', or, if bare values are to be - // > treated as 'ideal', isn't a bare value), the fitness distance is 0. - Ok(0.0) - } - } - } - }; -} - -impl_value_range_constraint!(setting: f64, constraint: f64); -impl_value_range_constraint!(setting: i64, constraint: u64); -impl_value_range_constraint!(setting: i64, constraint: f64); -impl_value_range_constraint!(setting: f64, constraint: u64); - -// Specialized implementations for non-boolean value constraints of mismatching, -// and thus ignored setting types: -macro_rules! impl_ignored_value_range_constraint { - (settings: [$($s:ty),+], constraint: $c:ty) => { - $(impl_ignored_value_range_constraint!(setting: $s, constraint: $c);)+ - }; - (setting: $s:ty, constraint: $c:ty) => { - impl<'a> FitnessDistance> for ResolvedValueRangeConstraint<$c> { - type Error = SettingFitnessDistanceError; - - fn fitness_distance(&self, _setting: Option<&'a $s>) -> Result { - // As specified in step 3 of the `fitness distance` algorithm: - // - // - // > If the constraint does not apply for this type of object, - // > the fitness distance is 0 (that is, the constraint does not - // > influence the fitness distance). - Ok(0.0) - } - } - }; -} - -impl_ignored_value_range_constraint!(settings: [bool, String], constraint: u64); -impl_ignored_value_range_constraint!(settings: [bool, String], constraint: f64); - -#[cfg(test)] -mod tests; diff --git a/constraints/src/algorithms/fitness_distance/value_range_constraint/tests.rs b/constraints/src/algorithms/fitness_distance/value_range_constraint/tests.rs deleted file mode 100644 index 97e495aee..000000000 --- a/constraints/src/algorithms/fitness_distance/value_range_constraint/tests.rs +++ /dev/null @@ -1,143 +0,0 @@ -use super::*; - -macro_rules! generate_value_range_constraint_tests { - ( - tests: [ - $({ - name: $ti:ident, - settings: $st:ty => $se:expr $(,)? - }),+ $(,)? - ], - constraints: $ct:ty => $ce:expr, - expected: $e:expr $(,)? - ) => { - generate_value_range_constraint_tests!( - tests: [ - $({ - name: $ti, - settings: $st => $se, - constraints: $ct => $ce, - }),+ - ], - expected: $e - ); - }; - ( - tests: [ - $({ - name: $ti:ident, - settings: $st:ty => $se:expr, - constraints: $ct:ty => $ce:expr $(,)? - }),+ $(,)? - ], - expected: $e:expr $(,)? - ) => { - generate_value_range_constraint_tests!( - tests: [ - $({ - name: $ti, - settings: $st => $se, - constraints: $ct => $ce, - }),+ - ], - validate: |result| { - assert_eq!(result, $e); - } - ); - }; - ( - tests: [ - $({ - name: $ti:ident, - settings: $st:ty => $se:expr, - constraints: $ct:ty => $ce:expr $(,)? - }),+ $(,)? - ], - validate: |$a:ident| $b:block - ) => { - $( - #[test] - fn $ti() { - test_value_range_constraint!( - settings: $st => $se, - constraints: $ct => $ce, - validate: |$a| $b - ); - } - )+ - }; -} - -macro_rules! test_value_range_constraint { - ( - settings: $st:ty => $se:expr, - constraints: $ct:ty => $ce:expr, - expected: $e:expr $(,)? - ) => { - test_value_range_constraint!( - settings: $st => $se, - constraints: $ct => $ce, - validate: |result| { - assert_eq!(result, $e); - } - ); - }; - ( - settings: $st:ty => $se:expr, - constraints: $ct:ty => $ce:expr, - validate: |$a:ident| $b:block - ) => {{ - let settings: &[Option<$st>] = $se; - let constraints: &[ResolvedValueRangeConstraint<$ct>] = $ce; - - for constraint in constraints { - for setting in settings { - let closure = |$a| $b; - let actual = constraint.fitness_distance(setting.as_ref()); - closure(actual); - } - } - }}; - ( - checks: [ - $({ - setting: $st:ty => $se:expr, - constraint: $ct:ty => $ce:expr, - expected: $ee:expr $(,)? - }),+ $(,)? - ] - ) => { - test_value_range_constraint!( - checks: [ - $({ - setting: $st => $se, - constraint: $ct => $ce, - expected: $ee, - }),+ - ], - validate: |actual, expected| { - assert_eq!(actual, expected); - } - ); - }; - ( - checks: [ - $({ - setting: $st:ty => $se:expr, - constraint: $ct:ty => $ce:expr, - expected: $ee:expr $(,)? - }),+ $(,)? - ], - validate: |$ai:ident, $ei:ident| $b:block - ) => {{ - $({ - let closure = |$ai, $ei| $b; - let actual = $ce.fitness_distance($se.as_ref()); - closure(actual, $ee); - })+ - }}; -} - -mod empty; -mod f64; -mod u64; diff --git a/constraints/src/algorithms/fitness_distance/value_range_constraint/tests/empty.rs b/constraints/src/algorithms/fitness_distance/value_range_constraint/tests/empty.rs deleted file mode 100644 index c2fdce3fc..000000000 --- a/constraints/src/algorithms/fitness_distance/value_range_constraint/tests/empty.rs +++ /dev/null @@ -1,75 +0,0 @@ -use super::*; - -macro_rules! generate_empty_value_range_constraint_tests { - ( - tests: [ - $({ - name: $ti:ident, - settings: $st:ty => $se:expr $(,)? - }),+ $(,)? - ], - constraint: $ct:ty $(,)? - ) => { - generate_value_range_constraint_tests!( - tests: [ - $({ - name: $ti, - settings: $st => $se, - }),+ - ], - constraints: $ct => &[ - ResolvedValueRangeConstraint::<$ct> { - min: None, - max: None, - exact: None, - ideal: None, - } - ], - expected: Ok(0.0) - ); - }; -} - -mod u64_constraint { - use super::*; - - generate_empty_value_range_constraint_tests!( - tests: [ - { - name: bool_setting, - settings: bool => &[Some(false)], - }, - { - name: string_setting, - settings: String => &[Some("foo".to_owned())], - }, - { - name: f64_setting, - settings: f64 => &[Some(42.0)], - }, - ], - constraint: u64, - ); -} - -mod f64_constraint { - use super::*; - - generate_empty_value_range_constraint_tests!( - tests: [ - { - name: bool_setting, - settings: bool => &[Some(false)], - }, - { - name: string_setting, - settings: String => &[Some("foo".to_owned())], - }, - { - name: i64_setting, - settings: i64 => &[Some(42)], - }, - ], - constraint: f64, - ); -} diff --git a/constraints/src/algorithms/fitness_distance/value_range_constraint/tests/f64.rs b/constraints/src/algorithms/fitness_distance/value_range_constraint/tests/f64.rs deleted file mode 100644 index 79a4afc45..000000000 --- a/constraints/src/algorithms/fitness_distance/value_range_constraint/tests/f64.rs +++ /dev/null @@ -1,312 +0,0 @@ -use super::*; -use crate::algorithms::SettingFitnessDistanceErrorKind; - -mod basic { - use super::*; - - mod zero_distance { - use super::*; - - generate_value_range_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(42)], - }, - { - name: f64_setting, - settings: f64 => &[Some(42.0)], - }, - ], - constraints: f64 => &[ResolvedValueRangeConstraint { - min: None, - max: None, - exact: None, - ideal: Some(42.0), - }], - expected: Ok(0.0) - ); - - generate_value_range_constraint_tests!( - tests: [ - { - name: bool_setting, - settings: bool => &[Some(true)], - }, - { - name: string_setting, - settings: String => &[Some("foo".to_owned())], - }, - ], - constraints: f64 => &[ - ResolvedValueRangeConstraint { - min: None, - max: None, - exact: None, - ideal: Some(42.0), - } - ], - expected: Ok(0.0) - ); - } - - mod fract_distance { - use super::*; - - #[test] - fn i64_setting() { - test_value_range_constraint!( - checks: [ - { - setting: i64 => Some(1), - constraint: f64 => ResolvedValueRangeConstraint { - min: None, - max: None, - exact: None, - ideal: Some(4.0), - }, - expected: Ok(0.75), - }, - { - setting: i64 => Some(2), - constraint: f64 => ResolvedValueRangeConstraint { - min: None, - max: None, - exact: None, - ideal: Some(4.0), - }, - expected: Ok(0.5), - }, - { - setting: i64 => Some(3), - constraint: f64 => ResolvedValueRangeConstraint { - min: None, - max: None, - exact: None, - ideal: Some(4.0), - }, - expected: Ok(0.25), - }, - ], - validate: |actual, expected| { - assert_eq!(actual, expected); - } - ); - } - - #[test] - fn f64_setting() { - test_value_range_constraint!( - checks: [ - { - setting: f64 => Some(1.0), - constraint: f64 => ResolvedValueRangeConstraint { - min: None, - max: None, - exact: None, - ideal: Some(4.0), - }, - expected: Ok(0.75), - }, - { - setting: f64 => Some(2.0), - constraint: f64 => ResolvedValueRangeConstraint { - min: None, - max: None, - exact: None, - ideal: Some(4.0), - }, - expected: Ok(0.5), - }, - { - setting: f64 => Some(3.0), - constraint: f64 => ResolvedValueRangeConstraint { - min: None, - max: None, - exact: None, - ideal: Some(4.0), - }, - expected: Ok(0.25), - }, - ], - validate: |actual, expected| { - assert_eq!(actual, expected); - } - ); - } - } - - mod one_distance { - use super::*; - - generate_value_range_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(0)], - }, - { - name: f64_setting, - settings: f64 => &[Some(0.0)], - }, - ], - constraints: f64 => &[ResolvedValueRangeConstraint { - min: None, - max: None, - exact: None, - ideal: Some(42.0), - }], - expected: Ok(1.0) - ); - } -} - -mod required { - use super::*; - - mod zero_distance { - use super::*; - - generate_value_range_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(42)], - }, - { - name: f64_setting, - settings: f64 => &[Some(42.0)], - }, - ], - constraints: f64 => &[ResolvedValueRangeConstraint { - min: None, - max: None, - exact: Some(42.0), - ideal: None, - }], - expected: Ok(0.0) - ); - - generate_value_range_constraint_tests!( - tests: [ - { - name: bool_setting, - settings: bool => &[Some(true)], - }, - { - name: string_setting, - settings: String => &[Some("foo".to_owned())], - }, - ], - constraints: f64 => &[ - ResolvedValueRangeConstraint { - min: None, - max: None, - exact: Some(42.0), - ideal: None, - } - ], - expected: Ok(0.0) - ); - } - - mod inf_distance { - use super::*; - - mod missing { - use super::*; - - generate_value_range_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[None], - }, - { - name: f64_setting, - settings: f64 => &[None], - }, - ], - constraints: f64 => &[ - ResolvedValueRangeConstraint { - min: None, - max: None, - exact: Some(42.0), - ideal: None, - }, - ResolvedValueRangeConstraint { - min: None, - max: None, - exact: Some(42.0), - ideal: Some(42.0), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Missing, - constraint: "(x == 42.0)".to_owned(), - setting: None, - }) - ); - } - - mod mismatch { - use super::*; - - generate_value_range_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(0)], - }, - ], - constraints: f64 => &[ - ResolvedValueRangeConstraint { - min: None, - max: None, - exact: Some(42.0), - ideal: None, - }, - ResolvedValueRangeConstraint { - min: None, - max: None, - exact: Some(42.0), - ideal: Some(42.0), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: "(x == 42.0)".to_owned(), - setting: Some("0".to_owned()), - }) - ); - - generate_value_range_constraint_tests!( - tests: [ - { - name: f64_setting, - settings: f64 => &[Some(0.0)], - }, - ], - constraints: f64 => &[ - ResolvedValueRangeConstraint { - min: None, - max: None, - exact: Some(42.0), - ideal: None, - }, - ResolvedValueRangeConstraint { - min: None, - max: None, - exact: Some(42.0), - ideal: Some(42.0), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: "(x == 42.0)".to_owned(), - setting: Some("0.0".to_owned()), - }) - ); - } - } -} diff --git a/constraints/src/algorithms/fitness_distance/value_range_constraint/tests/u64.rs b/constraints/src/algorithms/fitness_distance/value_range_constraint/tests/u64.rs deleted file mode 100644 index 72ec1845e..000000000 --- a/constraints/src/algorithms/fitness_distance/value_range_constraint/tests/u64.rs +++ /dev/null @@ -1,312 +0,0 @@ -use super::*; -use crate::algorithms::SettingFitnessDistanceErrorKind; - -mod basic { - use super::*; - - mod zero_distance { - use super::*; - - generate_value_range_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(42)], - }, - { - name: f64_setting, - settings: f64 => &[Some(42.0)], - }, - ], - constraints: u64 => &[ResolvedValueRangeConstraint { - min: None, - max: None, - exact: None, - ideal: Some(42), - }], - expected: Ok(0.0) - ); - - generate_value_range_constraint_tests!( - tests: [ - { - name: bool_setting, - settings: bool => &[Some(true)], - }, - { - name: string_setting, - settings: String => &[Some("foo".to_owned())], - }, - ], - constraints: u64 => &[ - ResolvedValueRangeConstraint { - min: None, - max: None, - exact: None, - ideal: Some(42), - } - ], - expected: Ok(0.0) - ); - } - - mod fract_distance { - use super::*; - - #[test] - fn i64_setting() { - test_value_range_constraint!( - checks: [ - { - setting: i64 => Some(1), - constraint: u64 => ResolvedValueRangeConstraint { - min: None, - max: None, - exact: None, - ideal: Some(4), - }, - expected: Ok(0.75), - }, - { - setting: i64 => Some(2), - constraint: u64 => ResolvedValueRangeConstraint { - min: None, - max: None, - exact: None, - ideal: Some(4), - }, - expected: Ok(0.5), - }, - { - setting: i64 => Some(3), - constraint: u64 => ResolvedValueRangeConstraint { - min: None, - max: None, - exact: None, - ideal: Some(4), - }, - expected: Ok(0.25), - }, - ], - validate: |actual, expected| { - assert_eq!(actual, expected); - } - ); - } - - #[test] - fn f64_setting() { - test_value_range_constraint!( - checks: [ - { - setting: f64 => Some(1.0), - constraint: u64 => ResolvedValueRangeConstraint { - min: None, - max: None, - exact: None, - ideal: Some(4), - }, - expected: Ok(0.75), - }, - { - setting: f64 => Some(2.0), - constraint: u64 => ResolvedValueRangeConstraint { - min: None, - max: None, - exact: None, - ideal: Some(4), - }, - expected: Ok(0.5), - }, - { - setting: f64 => Some(3.0), - constraint: u64 => ResolvedValueRangeConstraint { - min: None, - max: None, - exact: None, - ideal: Some(4), - }, - expected: Ok(0.25), - }, - ], - validate: |actual, expected| { - assert_eq!(actual, expected); - } - ); - } - } - - mod one_distance { - use super::*; - - generate_value_range_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[None, Some(0)], - }, - { - name: f64_setting, - settings: f64 => &[None, Some(0.0)], - }, - ], - constraints: u64 => &[ResolvedValueRangeConstraint { - min: None, - max: None, - exact: None, - ideal: Some(42), - }], - expected: Ok(1.0) - ); - } -} - -mod required { - use super::*; - - mod zero_distance { - use super::*; - - generate_value_range_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(42)], - }, - { - name: f64_setting, - settings: f64 => &[Some(42.0)], - }, - ], - constraints: u64 => &[ResolvedValueRangeConstraint { - min: None, - max: None, - exact: Some(42), - ideal: None, - }], - expected: Ok(0.0) - ); - - generate_value_range_constraint_tests!( - tests: [ - { - name: bool_setting, - settings: bool => &[Some(true)], - }, - { - name: string_setting, - settings: String => &[Some("foo".to_owned())], - }, - ], - constraints: u64 => &[ - ResolvedValueRangeConstraint { - min: None, - max: None, - exact: Some(42), - ideal: None, - } - ], - expected: Ok(0.0) - ); - } - - mod inf_distance { - use super::*; - - mod missing { - use super::*; - - generate_value_range_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[None], - }, - { - name: f64_setting, - settings: f64 => &[None], - }, - ], - constraints: u64 => &[ - ResolvedValueRangeConstraint { - min: None, - max: None, - exact: Some(42), - ideal: None, - }, - ResolvedValueRangeConstraint { - min: None, - max: None, - exact: Some(42), - ideal: Some(42), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Missing, - constraint: "(x == 42)".to_owned(), - setting: None, - }) - ); - } - - mod mismatch { - use super::*; - - generate_value_range_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(0)], - }, - ], - constraints: u64 => &[ - ResolvedValueRangeConstraint { - min: None, - max: None, - exact: Some(42), - ideal: None, - }, - ResolvedValueRangeConstraint { - min: None, - max: None, - exact: Some(42), - ideal: Some(42), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: "(x == 42)".to_owned(), - setting: Some("0".to_owned()), - }) - ); - - generate_value_range_constraint_tests!( - tests: [ - { - name: f64_setting, - settings: f64 => &[Some(0.0)], - }, - ], - constraints: u64 => &[ - ResolvedValueRangeConstraint { - min: None, - max: None, - exact: Some(42), - ideal: None, - }, - ResolvedValueRangeConstraint { - min: None, - max: None, - exact: Some(42), - ideal: Some(42), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: "(x == 42)".to_owned(), - setting: Some("0.0".to_owned()), - }) - ); - } - } -} diff --git a/constraints/src/algorithms/fitness_distance/value_sequence_constraint.rs b/constraints/src/algorithms/fitness_distance/value_sequence_constraint.rs deleted file mode 100644 index e8f68c28c..000000000 --- a/constraints/src/algorithms/fitness_distance/value_sequence_constraint.rs +++ /dev/null @@ -1,173 +0,0 @@ -use super::setting::SettingFitnessDistanceError; -use super::{FitnessDistance, SettingFitnessDistanceErrorKind}; -use crate::ResolvedValueSequenceConstraint; - -macro_rules! impl_non_numeric_value_sequence_constraint { - (setting: $s:ty, constraint: $c:ty) => { - impl<'a> FitnessDistance> for ResolvedValueSequenceConstraint<$c> - where - $s: PartialEq<$c>, - { - type Error = SettingFitnessDistanceError; - - fn fitness_distance(&self, setting: Option<&'a $s>) -> Result { - if let Some(exact) = self.exact.as_ref() { - // As specified in step 2 of the `fitness distance` algorithm: - // - // - // > If the constraint is required (constraintValue either contains - // > one or more members named […] 'exact' […]), and the settings - // > dictionary's constraintName member's value does not satisfy the - // > constraint or doesn't exist, the fitness distance is positive infinity. - match setting { - Some(actual) if exact.contains(actual) => {} - Some(setting) => { - return Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: format!("{}", self.to_required_only()), - setting: Some(format!("{:?}", setting)), - }) - } - None => { - return Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Missing, - constraint: format!("{}", self.to_required_only()), - setting: None, - }) - } - }; - } - - if let Some(ideal) = self.ideal.as_ref() { - // As specified in step 8 of the `fitness distance` algorithm: - // - // - // > For all string, enum and boolean constraints […], - // > the fitness distance is the result of the formula: - // > - // > ``` - // > (actual == ideal) ? 0 : 1 - // > ``` - // - // As well as step 5 of the `fitness distance` algorithm: - // - // - // > If the settings dictionary's `constraintName` member - // > does not exist, the fitness distance is 1. - match setting { - Some(actual) if ideal.contains(actual) => Ok(0.0), - Some(_) => Ok(1.0), - None => Ok(1.0), - } - } else { - // As specified in step 6 of the `fitness distance` algorithm: - // - // - // > If no ideal value is specified (constraintValue either - // > contains no member named 'ideal', or, if bare values are to be - // > treated as 'ideal', isn't a bare value), the fitness distance is 0. - Ok(0.0) - } - } - } - }; -} - -impl_non_numeric_value_sequence_constraint!(setting: bool, constraint: bool); -impl_non_numeric_value_sequence_constraint!(setting: String, constraint: String); - -macro_rules! impl_numeric_value_sequence_constraint { - (setting: $s:ty, constraint: $c:ty) => { - impl<'a> FitnessDistance> for ResolvedValueSequenceConstraint<$c> { - type Error = SettingFitnessDistanceError; - - fn fitness_distance(&self, setting: Option<&'a $s>) -> Result { - if let Some(exact) = &self.exact { - // As specified in step 2 of the `fitness distance` algorithm: - // - // - // > If the constraint is required (constraintValue either contains - // > one or more members named […] 'exact' […]), and the settings - // > dictionary's constraintName member's value does not satisfy the - // > constraint or doesn't exist, the fitness distance is positive infinity. - match setting { - Some(&actual) if exact.contains(&(actual as $c)) => {} - Some(setting) => { - return Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: format!("{}", self.to_required_only()), - setting: Some(format!("{:?}", setting)), - }) - } - None => { - return Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Missing, - constraint: format!("{}", self.to_required_only()), - setting: None, - }) - } - }; - } - - if let Some(ideal) = &self.ideal { - // As specified in step 8 of the `fitness distance` algorithm: - // - // - // > For all string, enum and boolean constraints […], - // > the fitness distance is the result of the formula: - // > - // > ``` - // > (actual == ideal) ? 0 : 1 - // > ``` - // - // As well as step 5 of the `fitness distance` algorithm: - // - // - // > If the settings dictionary's `constraintName` member - // > does not exist, the fitness distance is 1. - match setting { - Some(&actual) => { - let actual: f64 = actual as f64; - let mut min_fitness_distance = 1.0; - for ideal in ideal.into_iter() { - let ideal: f64 = (*ideal) as f64; - // As specified in step 7 of the `fitness distance` algorithm: - // - // - // > For all positive numeric constraints […], - // > the fitness distance is the result of the formula - // > - // > ``` - // > (actual == ideal) ? 0 : |actual - ideal| / max(|actual|, |ideal|) - // > ``` - let fitness_distance = - super::relative_fitness_distance(actual, ideal); - if fitness_distance < min_fitness_distance { - min_fitness_distance = fitness_distance; - } - } - Ok(min_fitness_distance) - } - None => Ok(1.0), - } - } else { - // As specified in step 6 of the `fitness distance` algorithm: - // - // - // > If no ideal value is specified (constraintValue either - // > contains no member named 'ideal', or, if bare values are to be - // > treated as 'ideal', isn't a bare value), the fitness distance is 0. - Ok(0.0) - } - } - } - }; -} - -impl_numeric_value_sequence_constraint!(setting: f64, constraint: f64); -impl_numeric_value_sequence_constraint!(setting: i64, constraint: u64); -impl_numeric_value_sequence_constraint!(setting: i64, constraint: f64); -impl_numeric_value_sequence_constraint!(setting: f64, constraint: u64); - -#[cfg(test)] -mod tests; diff --git a/constraints/src/algorithms/fitness_distance/value_sequence_constraint/tests.rs b/constraints/src/algorithms/fitness_distance/value_sequence_constraint/tests.rs deleted file mode 100644 index 5750ae0a8..000000000 --- a/constraints/src/algorithms/fitness_distance/value_sequence_constraint/tests.rs +++ /dev/null @@ -1,144 +0,0 @@ -use super::*; - -macro_rules! generate_value_constraint_tests { - ( - tests: [ - $({ - name: $ti:ident, - settings: $st:ty => $se:expr $(,)? - }),+ $(,)? - ], - constraints: $ct:ty => $ce:expr, - expected: $e:expr $(,)? - ) => { - generate_value_constraint_tests!( - tests: [ - $({ - name: $ti, - settings: $st => $se, - constraints: $ct => $ce, - }),+ - ], - expected: $e - ); - }; - ( - tests: [ - $({ - name: $ti:ident, - settings: $st:ty => $se:expr, - constraints: $ct:ty => $ce:expr $(,)? - }),+ $(,)? - ], - expected: $e:expr $(,)? - ) => { - generate_value_constraint_tests!( - tests: [ - $({ - name: $ti, - settings: $st => $se, - constraints: $ct => $ce, - }),+ - ], - validate: |result| { - assert_eq!(result, $e); - } - ); - }; - ( - tests: [ - $({ - name: $ti:ident, - settings: $st:ty => $se:expr, - constraints: $ct:ty => $ce:expr $(,)? - }),+ $(,)? - ], - validate: |$a:ident| $b:block - ) => { - $( - #[test] - fn $ti() { - test_value_constraint!( - settings: $st => $se, - constraints: $ct => $ce, - validate: |$a| $b - ); - } - )+ - }; -} - -macro_rules! test_value_constraint { - ( - settings: $st:ty => $se:expr, - constraints: $ct:ty => $ce:expr, - expected: $e:expr $(,)? - ) => { - test_value_constraint!( - settings: $st => $se, - constraints: $ct => $ce, - validate: |result| { - assert_eq!(result, $e); - } - ); - }; - ( - settings: $st:ty => $se:expr, - constraints: $ct:ty => $ce:expr, - validate: |$a:ident| $b:block - ) => {{ - let settings: &[Option<$st>] = $se; - let constraints: &[ResolvedValueSequenceConstraint<$ct>] = $ce; - - for constraint in constraints { - for setting in settings { - let closure = |$a| $b; - let actual = constraint.fitness_distance(setting.as_ref()); - closure(actual); - } - } - }}; - ( - checks: [ - $({ - setting: $st:ty => $se:expr, - constraint: $ct:ty => $ce:expr, - expected: $ee:expr $(,)? - }),+ $(,)? - ] - ) => { - test_value_constraint!( - checks: [ - $({ - setting: $st => $se, - constraint: $ct => $ce, - expected: $ee, - }),+ - ], - validate: |actual, expected| { - assert_eq!(actual, expected); - } - ); - }; - ( - checks: [ - $({ - setting: $st:ty => $se:expr, - constraint: $ct:ty => $ce:expr, - expected: $ee:expr $(,)? - }),+ $(,)? - ], - validate: |$ai:ident, $ei:ident| $b:block - ) => {{ - $({ - let closure = |$ai, $ei| $b; - let actual = $ce.fitness_distance($se.as_ref()); - closure(actual, $ee); - })+ - }}; -} - -mod bool; -mod f64; -mod string; -mod u64; diff --git a/constraints/src/algorithms/fitness_distance/value_sequence_constraint/tests/bool.rs b/constraints/src/algorithms/fitness_distance/value_sequence_constraint/tests/bool.rs deleted file mode 100644 index b6d4bc245..000000000 --- a/constraints/src/algorithms/fitness_distance/value_sequence_constraint/tests/bool.rs +++ /dev/null @@ -1,132 +0,0 @@ -use super::*; -use crate::algorithms::SettingFitnessDistanceErrorKind; - -mod basic { - use super::*; - - mod zero_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: bool_setting, - settings: bool => &[Some(true)], - }, - ], - constraints: bool => &[ - ResolvedValueSequenceConstraint { - exact: None, - ideal: None, - }, - ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec![true]), - }, - ], - expected: Ok(0.0) - ); - } - - mod one_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: bool_setting, - settings: bool => &[None, Some(false)], - }, - ], - constraints: bool => &[ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec![true]), - }], - expected: Ok(1.0) - ); - } -} - -mod required { - use super::*; - - mod zero_distance { - use super::*; - // A constraint that does apply for a type of setting, - // is expected to return a fitness distance of `0`, - // iff the setting matches the constraint: - generate_value_constraint_tests!( - tests: [ - { - name: bool_setting, - settings: bool => &[Some(true)], - }, - ], - constraints: bool => &[ResolvedValueSequenceConstraint { - exact: Some(vec![true]), - ideal: None, - }], - expected: Ok(0.0) - ); - } - - mod inf_distance { - use super::*; - - mod missing { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: bool_setting, - settings: bool => &[None], - }, - ], - constraints: bool => &[ - ResolvedValueSequenceConstraint { - exact: Some(vec![true]), - ideal: None, - }, - ResolvedValueSequenceConstraint { - exact: Some(vec![true]), - ideal: Some(vec![true]), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Missing, - constraint: "(x == [true])".to_owned(), - setting: None, - }) - ); - } - - mod mismatch { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: bool_setting, - settings: bool => &[Some(false)], - }, - ], - constraints: bool => &[ - ResolvedValueSequenceConstraint { - exact: Some(vec![true]), - ideal: None, - }, - ResolvedValueSequenceConstraint { - exact: Some(vec![true]), - ideal: Some(vec![true]), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: "(x == [true])".to_owned(), - setting: Some("false".to_owned()), - }) - ); - } - } -} diff --git a/constraints/src/algorithms/fitness_distance/value_sequence_constraint/tests/f64.rs b/constraints/src/algorithms/fitness_distance/value_sequence_constraint/tests/f64.rs deleted file mode 100644 index aa3803323..000000000 --- a/constraints/src/algorithms/fitness_distance/value_sequence_constraint/tests/f64.rs +++ /dev/null @@ -1,245 +0,0 @@ -use super::*; -use crate::algorithms::SettingFitnessDistanceErrorKind; - -mod basic { - use super::*; - - mod zero_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(42)], - }, - { - name: f64_setting, - settings: f64 => &[Some(42.0)], - }, - ], - constraints: f64 => &[ - ResolvedValueSequenceConstraint { - exact: None, - ideal: None, - }, - ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec![42.0]), - }, - ], - expected: Ok(0.0) - ); - } - - mod fract_distance { - use super::*; - - #[test] - fn i64_setting() { - test_value_constraint!( - checks: [ - { - setting: i64 => Some(1), - constraint: f64 => ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec![4.0]), - }, - expected: Ok(0.75), - }, - { - setting: i64 => Some(2), - constraint: f64 => ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec![4.0]), - }, - expected: Ok(0.5), - }, - { - setting: i64 => Some(3), - constraint: f64 => ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec![4.0]), - }, - expected: Ok(0.25), - }, - ], - validate: |actual, expected| { - assert_eq!(actual, expected); - } - ); - } - - #[test] - fn f64_setting() { - test_value_constraint!( - checks: [ - { - setting: f64 => Some(1.0), - constraint: f64 => ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec![4.0]), - }, - expected: Ok(0.75), - }, - { - setting: f64 => Some(2.0), - constraint: f64 => ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec![4.0]), - }, - expected: Ok(0.5), - }, - { - setting: f64 => Some(3.0), - constraint: f64 => ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec![4.0]), - }, - expected: Ok(0.25), - }, - ], - validate: |actual, expected| { - assert_eq!(actual, expected); - } - ); - } - } - - mod one_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(0)], - }, - { - name: f64_setting, - settings: f64 => &[Some(0.0)], - }, - ], - constraints: f64 => &[ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec![42.0]), - }], - expected: Ok(1.0) - ); - } -} - -mod required { - use super::*; - - mod zero_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(42)], - }, - { - name: f64_setting, - settings: f64 => &[Some(42.0)], - }, - ], - constraints: f64 => &[ResolvedValueSequenceConstraint { - exact: Some(vec![42.0]), - ideal: None, - }], - expected: Ok(0.0) - ); - } - - mod inf_distance { - use super::*; - - mod missing { - - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[None], - }, - { - name: f64_setting, - settings: f64 => &[None], - }, - ], - constraints: f64 => &[ - ResolvedValueSequenceConstraint { - exact: Some(vec![1.0, 1.5, 2.0]), - ideal: None, - }, - ResolvedValueSequenceConstraint { - exact: Some(vec![1.0, 1.5, 2.0]), - ideal: Some(vec![1.5]), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Missing, - constraint: "(x == [1.0, 1.5, 2.0])".to_owned(), - setting: None, - }) - ); - } - - mod mismatch { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(0)], - }, - ], - constraints: f64 => &[ - ResolvedValueSequenceConstraint { - exact: Some(vec![1.0, 1.5, 2.0]), - ideal: None, - }, - ResolvedValueSequenceConstraint { - exact: Some(vec![1.0, 1.5, 2.0]), - ideal: Some(vec![1.5]), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: "(x == [1.0, 1.5, 2.0])".to_owned(), - setting: Some("0".to_owned()), - }) - ); - - generate_value_constraint_tests!( - tests: [ - { - name: f64_setting, - settings: f64 => &[Some(0.0)], - }, - ], - constraints: f64 => &[ - ResolvedValueSequenceConstraint { - exact: Some(vec![1.0, 1.5, 2.0]), - ideal: None, - }, - ResolvedValueSequenceConstraint { - exact: Some(vec![1.0, 1.5, 2.0]), - ideal: Some(vec![1.5]), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: "(x == [1.0, 1.5, 2.0])".to_owned(), - setting: Some("0.0".to_owned()), - }) - ); - } - } -} diff --git a/constraints/src/algorithms/fitness_distance/value_sequence_constraint/tests/string.rs b/constraints/src/algorithms/fitness_distance/value_sequence_constraint/tests/string.rs deleted file mode 100644 index 5b2b11ece..000000000 --- a/constraints/src/algorithms/fitness_distance/value_sequence_constraint/tests/string.rs +++ /dev/null @@ -1,133 +0,0 @@ -use super::*; -use crate::algorithms::SettingFitnessDistanceErrorKind; - -mod basic { - use super::*; - - mod zero_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: string_setting, - settings: String => &[Some("foo".to_owned())], - }, - ], - constraints: String => &[ - ResolvedValueSequenceConstraint { - exact: None, - ideal: None, - }, - ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec!["foo".to_owned()]), - }, - ], - expected: Ok(0.0) - ); - } - - mod one_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: string_setting, - settings: String => &[None, Some("bar".to_owned())], - }, - ], - constraints: String => &[ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec!["foo".to_owned()]), - }], - expected: Ok(1.0) - ); - } -} - -mod required { - use super::*; - - mod zero_distance { - use super::*; - - // A constraint that does apply for a type of setting, - // is expected to return a fitness distance of `0`, - // iff the setting matches the constraint: - generate_value_constraint_tests!( - tests: [ - { - name: string_setting, - settings: String => &[Some("foo".to_owned())], - }, - ], - constraints: String => &[ResolvedValueSequenceConstraint { - exact: Some(vec!["foo".to_owned()]), - ideal: None, - }], - expected: Ok(0.0) - ); - } - - mod inf_distance { - use super::*; - - mod missing { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: string_setting, - settings: String => &[None], - }, - ], - constraints: String => &[ - ResolvedValueSequenceConstraint { - exact: Some(vec!["foo".to_owned(), "bar".to_owned(), "baz".to_owned()]), - ideal: None, - }, - ResolvedValueSequenceConstraint { - exact: Some(vec!["foo".to_owned(), "bar".to_owned(), "baz".to_owned()]), - ideal: Some(vec!["foo".to_owned()]), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Missing, - constraint: "(x == [\"foo\", \"bar\", \"baz\"])".to_owned(), - setting: None, - }) - ); - } - - mod mismatch { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: string_setting, - settings: String => &[Some("blee".to_owned())], - }, - ], - constraints: String => &[ - ResolvedValueSequenceConstraint { - exact: Some(vec!["foo".to_owned(), "bar".to_owned(), "baz".to_owned()]), - ideal: None, - }, - ResolvedValueSequenceConstraint { - exact: Some(vec!["foo".to_owned(), "bar".to_owned(), "baz".to_owned()]), - ideal: Some(vec!["foo".to_owned()]), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: "(x == [\"foo\", \"bar\", \"baz\"])".to_owned(), - setting: Some("\"blee\"".to_owned()), - }) - ); - } - } -} diff --git a/constraints/src/algorithms/fitness_distance/value_sequence_constraint/tests/u64.rs b/constraints/src/algorithms/fitness_distance/value_sequence_constraint/tests/u64.rs deleted file mode 100644 index 4693fa143..000000000 --- a/constraints/src/algorithms/fitness_distance/value_sequence_constraint/tests/u64.rs +++ /dev/null @@ -1,244 +0,0 @@ -use super::*; -use crate::algorithms::SettingFitnessDistanceErrorKind; - -mod basic { - use super::*; - - mod zero_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(42)], - }, - { - name: f64_setting, - settings: f64 => &[Some(42.0)], - }, - ], - constraints: u64 => &[ - ResolvedValueSequenceConstraint { - exact: None, - ideal: None, - }, - ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec![42]), - }, - ], - expected: Ok(0.0) - ); - } - - mod fract_distance { - use super::*; - - #[test] - fn i64_setting() { - test_value_constraint!( - checks: [ - { - setting: i64 => Some(1), - constraint: i64 => ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec![4]), - }, - expected: Ok(0.75), - }, - { - setting: i64 => Some(2), - constraint: i64 => ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec![4]), - }, - expected: Ok(0.5), - }, - { - setting: i64 => Some(3), - constraint: i64 => ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec![4]), - }, - expected: Ok(0.25), - }, - ], - validate: |actual, expected| { - assert_eq!(actual, expected); - } - ); - } - - #[test] - fn f64_setting() { - test_value_constraint!( - checks: [ - { - setting: f64 => Some(1.0), - constraint: u64 => ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec![4]), - }, - expected: Ok(0.75), - }, - { - setting: f64 => Some(2.0), - constraint: u64 => ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec![4]), - }, - expected: Ok(0.5), - }, - { - setting: f64 => Some(3.0), - constraint: u64 => ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec![4]), - }, - expected: Ok(0.25), - }, - ], - validate: |actual, expected| { - assert_eq!(actual, expected); - } - ); - } - } - - mod one_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[None, Some(0)], - }, - { - name: f64_setting, - settings: f64 => &[None, Some(0.0)], - }, - ], - constraints: u64 => &[ResolvedValueSequenceConstraint { - exact: None, - ideal: Some(vec![42]), - }], - expected: Ok(1.0) - ); - } -} - -mod required { - use super::*; - - mod zero_distance { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(42)], - }, - { - name: f64_setting, - settings: f64 => &[Some(42.0)], - }, - ], - constraints: u64 => &[ResolvedValueSequenceConstraint { - exact: Some(vec![42]), - ideal: None, - }], - expected: Ok(0.0) - ); - } - - mod inf_distance { - use super::*; - - mod missing { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[None], - }, - { - name: f64_setting, - settings: f64 => &[None], - }, - ], - constraints: u64 => &[ - ResolvedValueSequenceConstraint { - exact: Some(vec![42]), - ideal: None, - }, - ResolvedValueSequenceConstraint { - exact: Some(vec![42]), - ideal: Some(vec![42]), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Missing, - constraint: "(x == [42])".to_owned(), - setting: None, - }) - ); - } - - mod mismatch { - use super::*; - - generate_value_constraint_tests!( - tests: [ - { - name: i64_setting, - settings: i64 => &[Some(0)], - }, - ], - constraints: u64 => &[ - ResolvedValueSequenceConstraint { - exact: Some(vec![42]), - ideal: None, - }, - ResolvedValueSequenceConstraint { - exact: Some(vec![42]), - ideal: Some(vec![42]), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: "(x == [42])".to_owned(), - setting: Some("0".to_owned()), - }) - ); - - generate_value_constraint_tests!( - tests: [ - { - name: f64_setting, - settings: f64 => &[Some(0.0)], - }, - ], - constraints: u64 => &[ - ResolvedValueSequenceConstraint { - exact: Some(vec![42]), - ideal: None, - }, - ResolvedValueSequenceConstraint { - exact: Some(vec![42]), - ideal: Some(vec![42]), - }, - ], - expected: Err(SettingFitnessDistanceError { - kind: SettingFitnessDistanceErrorKind::Mismatch, - constraint: "(x == [42])".to_owned(), - setting: Some("0.0".to_owned()), - }) - ); - } - } -} diff --git a/constraints/src/algorithms/select_settings.rs b/constraints/src/algorithms/select_settings.rs deleted file mode 100644 index bd0798b3e..000000000 --- a/constraints/src/algorithms/select_settings.rs +++ /dev/null @@ -1,121 +0,0 @@ -use std::collections::HashSet; - -use thiserror::Error; - -use crate::algorithms::fitness_distance::SettingFitnessDistanceError; -use crate::errors::OverconstrainedError; -use crate::{MediaTrackSettings, SanitizedMediaTrackConstraints}; - -mod apply_advanced; -mod apply_mandatory; -mod select_optimal; -mod tie_breaking; - -use self::apply_advanced::*; -use self::apply_mandatory::*; -use self::select_optimal::*; -pub use self::tie_breaking::*; - -/// A mode indicating whether device information may be exposed. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum DeviceInformationExposureMode { - /// Device information may be exposed. - Exposed, - /// Device information may NOT be exposed. - Protected, -} - -/// An error type indicating a failure of the `SelectSettings` algorithm. -#[derive(Error, Clone, Eq, PartialEq, Debug)] -pub enum SelectSettingsError { - /// An error caused by one or more over-constrained settings. - #[error(transparent)] - Overconstrained(#[from] OverconstrainedError), -} - -/// This function implements steps 1-5 of the `SelectSettings` algorithm -/// as defined by the W3C spec: -/// -/// -/// Step 6 (tie-breaking) is omitted by this implementation and expected to be performed -/// manually on the returned candidates. -/// For this several implementation of `TieBreakingPolicy` are provided by this crate. -pub fn select_settings_candidates<'a, I>( - possible_settings: I, - constraints: &SanitizedMediaTrackConstraints, - exposure_mode: DeviceInformationExposureMode, -) -> Result, SelectSettingsError> -where - I: IntoIterator, -{ - let possible_settings = possible_settings.into_iter(); - - // As specified in step 1 of the `SelectSettings` algorithm: - // - // - // > Each constraint specifies one or more values (or a range of values) for its property. - // > A property MAY appear more than once in the list of 'advanced' ConstraintSets. - // > If an empty list has been given as the value for a constraint, - // > it MUST be interpreted as if the constraint were not specified - // > (in other words, an empty constraint == no constraint). - // > - // > Note that unknown properties are discarded by WebIDL, - // > which means that unknown/unsupported required constraints will silently disappear. - // > To avoid this being a surprise, application authors are expected to first use - // > the `getSupportedConstraints()` method […]. - - // We expect "sanitized" constraints to not contain empty constraints: - debug_assert!(constraints - .mandatory - .iter() - .all(|(_, constraint)| !constraint.is_empty())); - - // Obtain candidates by filtering possible settings, dropping those with infinite fitness distances: - // - // This function call corresponds to steps 3 & 4 of the `SelectSettings` algorithm: - // - - let candidates_and_fitness_distances = - apply_mandatory_constraints(possible_settings, &constraints.mandatory, exposure_mode)?; - - // As specified in step 5 of the `SelectSettings` algorithm: - // - // - // > Iterate over the 'advanced' ConstraintSets in newConstraints in the order in which they were specified. - // > - // > For each ConstraintSet: - // > - // > 1. compute the fitness distance between it and each settings dictionary in candidates, - // > treating bare values of properties as exact. - // > - // > 2. If the fitness distance is finite for one or more settings dictionaries in candidates, - // > keep those settings dictionaries in candidates, discarding others. - // > - // > If the fitness distance is infinite for all settings dictionaries in candidates, - // > ignore this ConstraintSet. - let candidates = - apply_advanced_constraints(candidates_and_fitness_distances, &constraints.advanced); - - // As specified in step 6 of the `SelectSettings` algorithm: - // - // - // > Select one settings dictionary from candidates, and return it as the result of the `SelectSettings` algorithm. - // > The User Agent MUST use one with the smallest fitness distance, as calculated in step 3. - // > If more than one settings dictionary have the smallest fitness distance, - // > the User Agent chooses one of them based on system default property values and User Agent default property values. - // - // # Important - // Instead of return just ONE settings instance "with the smallest fitness distance, as calculated in step 3" - // we instead return ALL settings instances "with the smallest fitness distance, as calculated in step 3" - // and leave tie-breaking to the User Agent in a separate step: - Ok(select_optimal_candidates(candidates)) -} - -#[derive(Default)] -pub(crate) struct ConstraintFailureInfo { - pub(crate) failures: usize, - pub(crate) errors: HashSet, -} - -#[cfg(test)] -mod tests; diff --git a/constraints/src/algorithms/select_settings/apply_advanced.rs b/constraints/src/algorithms/select_settings/apply_advanced.rs deleted file mode 100644 index b7adfbe51..000000000 --- a/constraints/src/algorithms/select_settings/apply_advanced.rs +++ /dev/null @@ -1,188 +0,0 @@ -use crate::algorithms::FitnessDistance; -use crate::constraints::SanitizedAdvancedMediaTrackConstraints; -use crate::MediaTrackSettings; - -/// Returns the set of settings for which all non-overconstraining advanced constraints' -/// fitness distance is finite. -/// -/// Implements step 5 of the `SelectSettings` algorithm: -/// -/// -/// # Note: -/// This may change the order of items in `feasible_candidates`. -/// In practice however this is not a problem as we have to sort -/// it by fitness-distance eventually anyway. -pub(super) fn apply_advanced_constraints<'a>( - mut candidates: Vec<(&'a MediaTrackSettings, f64)>, - advanced_constraints: &SanitizedAdvancedMediaTrackConstraints, -) -> Vec<(&'a MediaTrackSettings, f64)> { - // As specified in step 5 of the `SelectSettings` algorithm: - // - // - // > Iterate over the 'advanced' ConstraintSets in newConstraints in the order in which they were specified. - // > - // > For each ConstraintSet: - // > - // > 1. compute the fitness distance between it and each settings dictionary in candidates, - // > treating bare values of properties as exact. - // > - // > 2. If the fitness distance is finite for one or more settings dictionaries in candidates, - // > keep those settings dictionaries in candidates, discarding others. - // > - // > If the fitness distance is infinite for all settings dictionaries in candidates, - // > ignore this ConstraintSet. - - let mut selected_candidates = Vec::with_capacity(candidates.len()); - - // Double-buffered sieving to avoid excessive vec allocations: - for advanced_constraint_set in advanced_constraints.iter() { - for (candidate, fitness_distance) in candidates.iter() { - if advanced_constraint_set.fitness_distance(candidate).is_ok() { - selected_candidates.push((*candidate, *fitness_distance)); - } - } - - if !selected_candidates.is_empty() { - candidates.clear(); - std::mem::swap(&mut candidates, &mut selected_candidates); - } - } - - candidates -} - -#[cfg(test)] -mod tests { - use std::iter::FromIterator; - - use super::*; - use crate::property::all::name::*; - use crate::{ - MediaTrackSupportedConstraints, ResizeMode, ResolvedAdvancedMediaTrackConstraints, - ResolvedMediaTrackConstraintSet, ResolvedValueConstraint, ResolvedValueRangeConstraint, - }; - - // Advanced constraint sets that doe not match any - // candidates should just get ignored: - #[test] - fn overconstrained() { - let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![ - &DEVICE_ID, - &HEIGHT, - &WIDTH, - &RESIZE_MODE, - ]); - - let settings = [ - MediaTrackSettings::from_iter([(&DEVICE_ID, "foo".into())]), - MediaTrackSettings::from_iter([(&DEVICE_ID, "bar".into())]), - ]; - - let candidates: Vec<_> = settings - .iter() - // attach a dummy fitness function: - .map(|settings| (settings, 42.0)) - .collect(); - - let constraints = ResolvedAdvancedMediaTrackConstraints::from_iter([ - ResolvedMediaTrackConstraintSet::from_iter([( - &DEVICE_ID, - ResolvedValueConstraint::default() - .exact("bazblee".to_owned()) - .into(), - )]), - ]); - - let sanitized_constraints = constraints.to_sanitized(&supported_constraints); - - let actual: Vec<_> = apply_advanced_constraints(candidates, &sanitized_constraints) - .into_iter() - // drop the dummy fitness distance: - .map(|(settings, _)| settings) - .collect(); - - let expected: Vec<_> = settings.iter().collect(); - - assert_eq!(actual, expected); - } - - #[test] - fn constrained() { - let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![ - &DEVICE_ID, - &HEIGHT, - &WIDTH, - &RESIZE_MODE, - ]); - - let settings = vec![ - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "480p".into()), - (&HEIGHT, 480.into()), - (&WIDTH, 720.into()), - (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "720p".into()), - (&HEIGHT, 720.into()), - (&WIDTH, 1280.into()), - (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "1080p".into()), - (&HEIGHT, 1080.into()), - (&WIDTH, 1920.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "1440p".into()), - (&HEIGHT, 1440.into()), - (&WIDTH, 2560.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "2160p".into()), - (&HEIGHT, 2160.into()), - (&WIDTH, 3840.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]), - ]; - - let candidates: Vec<_> = settings.iter().map(|settings| (settings, 42.0)).collect(); - - let constraints = ResolvedAdvancedMediaTrackConstraints::from_iter([ - // The first advanced constraint set of "exact 800p" does not match - // any candidate and should thus get ignored by the algorithm: - ResolvedMediaTrackConstraintSet::from_iter([( - &HEIGHT, - ResolvedValueRangeConstraint::default().exact(800).into(), - )]), - // The second advanced constraint set of "no resizing" does match - // candidates and should thus be applied by the algorithm: - ResolvedMediaTrackConstraintSet::from_iter([( - &RESIZE_MODE, - ResolvedValueConstraint::default() - .exact(ResizeMode::none()) - .into(), - )]), - // The second advanced constraint set of "max 1440p" does match - // candidates and should thus be applied by the algorithm: - ResolvedMediaTrackConstraintSet::from_iter([( - &HEIGHT, - ResolvedValueRangeConstraint::default().max(1440).into(), - )]), - ]); - - let sanitized_constraints = constraints.to_sanitized(&supported_constraints); - - let actual: Vec<_> = apply_advanced_constraints(candidates, &sanitized_constraints) - .into_iter() - // drop the dummy fitness distance: - .map(|(settings, _)| settings) - .collect(); - - let expected = vec![&settings[2], &settings[3]]; - - assert_eq!(actual, expected); - } -} diff --git a/constraints/src/algorithms/select_settings/apply_mandatory.rs b/constraints/src/algorithms/select_settings/apply_mandatory.rs deleted file mode 100644 index a9bba70e8..000000000 --- a/constraints/src/algorithms/select_settings/apply_mandatory.rs +++ /dev/null @@ -1,213 +0,0 @@ -use std::collections::HashMap; - -use crate::algorithms::select_settings::{ConstraintFailureInfo, DeviceInformationExposureMode}; -use crate::algorithms::FitnessDistance; -use crate::errors::OverconstrainedError; -use crate::{MediaTrackProperty, MediaTrackSettings, SanitizedMediaTrackConstraintSet}; - -/// Returns the set of settings for which all mandatory constraints' -/// fitness distance is finite. -/// -/// Implements step 5 of the `SelectSettings` algorithm: -/// -pub(super) fn apply_mandatory_constraints<'a, I>( - candidates: I, - mandatory_constraints: &SanitizedMediaTrackConstraintSet, - exposure_mode: DeviceInformationExposureMode, -) -> Result, OverconstrainedError> -where - I: IntoIterator, -{ - // As specified in step 3 of the `SelectSettings` algorithm: - // - // - // > For every possible settings dictionary of copy compute its fitness distance, - // > treating bare values of properties as ideal values. Let candidates be the - // > set of settings dictionaries for which the fitness distance is finite. - - let mut feasible_candidates: Vec<(&'a MediaTrackSettings, f64)> = vec![]; - let mut failed_constraints: HashMap = - Default::default(); - - for candidate in candidates { - match mandatory_constraints.fitness_distance(candidate) { - Ok(fitness_distance) => { - debug_assert!(fitness_distance.is_finite()); - - feasible_candidates.push((candidate, fitness_distance)); - } - Err(error) => { - for (property, setting_error) in error.setting_errors { - let entry = failed_constraints.entry(property).or_default(); - entry.failures += 1; - entry.errors.insert(setting_error); - } - } - } - } - - if feasible_candidates.is_empty() { - return Err(match exposure_mode { - DeviceInformationExposureMode::Exposed => { - OverconstrainedError::exposing_device_information(failed_constraints) - } - DeviceInformationExposureMode::Protected => OverconstrainedError::default(), - }); - } - - Ok(feasible_candidates) -} - -#[cfg(test)] -mod tests { - use std::iter::FromIterator; - - use super::*; - use crate::property::all::name::*; - use crate::{ - MediaTrackSupportedConstraints, ResizeMode, ResolvedMandatoryMediaTrackConstraints, - ResolvedValueConstraint, ResolvedValueRangeConstraint, - }; - - // Advanced constraint sets that do not match any candidates should just get ignored: - #[test] - fn overconstrained() { - let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![ - &DEVICE_ID, - &HEIGHT, - &WIDTH, - &RESIZE_MODE, - ]); - - let settings = [ - MediaTrackSettings::from_iter([(&DEVICE_ID, "foo".into())]), - MediaTrackSettings::from_iter([(&DEVICE_ID, "bar".into())]), - ]; - - let candidates: Vec<_> = settings.iter().collect(); - - let constraints = ResolvedMandatoryMediaTrackConstraints::from_iter([( - &DEVICE_ID, - ResolvedValueConstraint::default() - .exact("mismatched-device".to_owned()) - .into(), - )]); - - let sanitized_constraints = constraints.to_sanitized(&supported_constraints); - - // Exposed exposure mode: - - let error = apply_mandatory_constraints( - candidates.clone(), - &sanitized_constraints, - DeviceInformationExposureMode::Exposed, - ) - .unwrap_err(); - - let constraint = &error.constraint; - let err_message = error.message.as_ref().expect("Error message."); - - assert_eq!(constraint, &DEVICE_ID); - assert_eq!( - err_message, - "Setting was a mismatch ([\"bar\", \"foo\"] do not satisfy (x == \"mismatched-device\"))." - ); - - // Protected exposure mode: - - let error = apply_mandatory_constraints( - candidates, - &sanitized_constraints, - DeviceInformationExposureMode::Protected, - ) - .unwrap_err(); - - let constraint = &error.constraint; - let err_message = error.message; - - assert_eq!( - constraint, - &MediaTrackProperty::from(""), - "Constraint should not have been exposed" - ); - assert!( - err_message.is_none(), - "Error message should not have been exposed" - ); - } - - #[test] - fn constrained() { - let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![ - &DEVICE_ID, - &HEIGHT, - &WIDTH, - &RESIZE_MODE, - ]); - - let settings = vec![ - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "480p".into()), - (&HEIGHT, 480.into()), - (&WIDTH, 720.into()), - (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "720p".into()), - (&HEIGHT, 720.into()), - (&WIDTH, 1280.into()), - (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "1080p".into()), - (&HEIGHT, 1080.into()), - (&WIDTH, 1920.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "1440p".into()), - (&HEIGHT, 1440.into()), - (&WIDTH, 2560.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "2160p".into()), - (&HEIGHT, 2160.into()), - (&WIDTH, 3840.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]), - ]; - - let candidates: Vec<_> = settings.iter().collect(); - - let constraints = ResolvedMandatoryMediaTrackConstraints::from_iter([ - ( - &RESIZE_MODE, - ResolvedValueConstraint::default() - .exact(ResizeMode::none()) - .into(), - ), - ( - &HEIGHT, - ResolvedValueRangeConstraint::default().min(1000).into(), - ), - ( - &WIDTH, - ResolvedValueRangeConstraint::default().max(2000).into(), - ), - ]); - - let sanitized_constraints = constraints.to_sanitized(&supported_constraints); - - let actual = apply_mandatory_constraints( - candidates, - &sanitized_constraints, - DeviceInformationExposureMode::Exposed, - ) - .unwrap(); - - let expected = vec![(&settings[2], 0.0)]; - - assert_eq!(actual, expected); - } -} diff --git a/constraints/src/algorithms/select_settings/select_optimal.rs b/constraints/src/algorithms/select_settings/select_optimal.rs deleted file mode 100644 index 3437a4187..000000000 --- a/constraints/src/algorithms/select_settings/select_optimal.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::MediaTrackSettings; - -pub(super) fn select_optimal_candidates<'a, I>(candidates: I) -> Vec<&'a MediaTrackSettings> -where - I: IntoIterator, -{ - let mut optimal_candidates = vec![]; - let mut optimal_fitness_distance = f64::INFINITY; - - for (candidate, fitness_distance) in candidates { - use std::cmp::Ordering; - - let ordering = fitness_distance.total_cmp(&optimal_fitness_distance); - - if ordering == Ordering::Less { - // Candidate is new optimal, so drop current selection: - optimal_candidates.clear(); - optimal_fitness_distance = fitness_distance; - } - - if ordering != Ordering::Greater { - // Candidate is optimal, so add to selection: - optimal_candidates.push(candidate); - } - } - - optimal_candidates -} - -#[cfg(test)] -mod tests { - use super::select_optimal_candidates; - use crate::MediaTrackSettings; - - #[test] - fn monotonic_increasing() { - let settings = [ - MediaTrackSettings::default(), - MediaTrackSettings::default(), - MediaTrackSettings::default(), - MediaTrackSettings::default(), - ]; - - let candidates = vec![ - (&settings[0], 0.1), - (&settings[1], 0.1), - (&settings[2], 0.2), - (&settings[3], 0.3), - ]; - - let actual = select_optimal_candidates(candidates); - - let expected = vec![&settings[0], &settings[1]]; - - assert_eq!(actual, expected); - } - - #[test] - fn monotonic_decreasing() { - let settings = [ - MediaTrackSettings::default(), - MediaTrackSettings::default(), - MediaTrackSettings::default(), - MediaTrackSettings::default(), - ]; - - let candidates = vec![ - (&settings[0], 0.3), - (&settings[1], 0.2), - (&settings[2], 0.1), - (&settings[3], 0.1), - ]; - - let actual = select_optimal_candidates(candidates); - - let expected = vec![&settings[2], &settings[3]]; - - assert_eq!(actual, expected); - } - - #[test] - fn alternating() { - let settings = [ - MediaTrackSettings::default(), - MediaTrackSettings::default(), - MediaTrackSettings::default(), - MediaTrackSettings::default(), - ]; - - let candidates = vec![ - (&settings[0], 0.2), - (&settings[1], 0.1), - (&settings[2], 0.2), - (&settings[3], 0.1), - ]; - - let actual = select_optimal_candidates(candidates); - - let expected = vec![&settings[1], &settings[3]]; - - assert_eq!(actual, expected); - } -} diff --git a/constraints/src/algorithms/select_settings/tests.rs b/constraints/src/algorithms/select_settings/tests.rs deleted file mode 100644 index 5745662f4..000000000 --- a/constraints/src/algorithms/select_settings/tests.rs +++ /dev/null @@ -1,778 +0,0 @@ -use std::iter::FromIterator; - -use lazy_static::lazy_static; - -use super::DeviceInformationExposureMode; -use crate::algorithms::{select_settings_candidates, SelectSettingsError}; -use crate::errors::OverconstrainedError; -use crate::property::all::name::*; -use crate::property::all::names as all_properties; -use crate::{ - AdvancedMediaTrackConstraints, FacingMode, MandatoryMediaTrackConstraints, - MediaTrackConstraints, MediaTrackSettings, MediaTrackSupportedConstraints, ResizeMode, - ResolvedAdvancedMediaTrackConstraints, ResolvedMandatoryMediaTrackConstraints, - ResolvedMediaTrackConstraint, ResolvedMediaTrackConstraints, ResolvedValueConstraint, - ResolvedValueRangeConstraint, ResolvedValueSequenceConstraint, SanitizedMediaTrackConstraints, -}; - -lazy_static! { - static ref VIDEO_IDEAL: MediaTrackSettings = MediaTrackSettings::from_iter([ - (&ASPECT_RATIO, 0.5625.into()), - (&FACING_MODE, FacingMode::user().into()), - (&FRAME_RATE, 60.0.into()), - (&WIDTH, 1920.into()), - (&HEIGHT, 1080.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]); - static ref VIDEO_480P: MediaTrackSettings = MediaTrackSettings::from_iter([ - (&DEVICE_ID, "480p".into()), - (&ASPECT_RATIO, 0.5625.into()), - (&FACING_MODE, FacingMode::user().into()), - (&FRAME_RATE, 240.into()), - (&WIDTH, 720.into()), - (&HEIGHT, 480.into()), - (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), - ]); - static ref VIDEO_720P: MediaTrackSettings = MediaTrackSettings::from_iter([ - (&DEVICE_ID, "720p".into()), - (&ASPECT_RATIO, 0.5625.into()), - (&FACING_MODE, FacingMode::user().into()), - (&FRAME_RATE, 120.into()), - (&WIDTH, 1280.into()), - (&HEIGHT, 720.into()), - (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), - ]); - static ref VIDEO_1080P: MediaTrackSettings = MediaTrackSettings::from_iter([ - (&DEVICE_ID, "1080p".into()), - (&ASPECT_RATIO, 0.5625.into()), - (&FACING_MODE, FacingMode::user().into()), - (&FRAME_RATE, 60.into()), - (&WIDTH, 1920.into()), - (&HEIGHT, 1080.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]); - static ref VIDEO_1440P: MediaTrackSettings = MediaTrackSettings::from_iter([ - (&DEVICE_ID, "1440p".into()), - (&ASPECT_RATIO, 0.5625.into()), - (&FACING_MODE, FacingMode::user().into()), - (&FRAME_RATE, 30.into()), - (&WIDTH, 2560.into()), - (&HEIGHT, 1440.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]); - static ref VIDEO_2160P: MediaTrackSettings = MediaTrackSettings::from_iter([ - (&DEVICE_ID, "2160p".into()), - (&ASPECT_RATIO, 0.5625.into()), - (&FACING_MODE, FacingMode::user().into()), - (&FRAME_RATE, 15.into()), - (&WIDTH, 3840.into()), - (&HEIGHT, 2160.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]); -} - -fn default_possible_settings() -> Vec { - vec![ - VIDEO_480P.clone(), - VIDEO_720P.clone(), - VIDEO_1080P.clone(), - VIDEO_1440P.clone(), - VIDEO_2160P.clone(), - ] -} - -fn default_supported_constraints() -> MediaTrackSupportedConstraints { - MediaTrackSupportedConstraints::from_iter(all_properties().into_iter().cloned()) -} - -fn test_overconstrained( - possible_settings: &[MediaTrackSettings], - mandatory_constraints: ResolvedMandatoryMediaTrackConstraints, - exposure_mode: DeviceInformationExposureMode, -) -> OverconstrainedError { - let constraints = ResolvedMediaTrackConstraints { - mandatory: mandatory_constraints, - advanced: ResolvedAdvancedMediaTrackConstraints::default(), - } - .to_sanitized(&default_supported_constraints()); - - let result = select_settings_candidates(possible_settings.iter(), &constraints, exposure_mode); - - let actual = result.err().unwrap(); - - let SelectSettingsError::Overconstrained(overconstrained_error) = actual; - - overconstrained_error -} - -fn test_constrained( - possible_settings: &[MediaTrackSettings], - mandatory_constraints: ResolvedMandatoryMediaTrackConstraints, - advanced_constraints: ResolvedAdvancedMediaTrackConstraints, -) -> Vec<&MediaTrackSettings> { - let constraints = ResolvedMediaTrackConstraints { - mandatory: mandatory_constraints, - advanced: advanced_constraints, - } - .to_sanitized(&default_supported_constraints()); - - let result = select_settings_candidates( - possible_settings.iter(), - &constraints, - DeviceInformationExposureMode::Exposed, - ); - - result.unwrap() -} - -mod unconstrained { - use super::*; - - fn default_constraints() -> MediaTrackConstraints { - MediaTrackConstraints { - mandatory: MandatoryMediaTrackConstraints::default(), - advanced: AdvancedMediaTrackConstraints::default(), - } - } - - fn default_resolved_constraints() -> ResolvedMediaTrackConstraints { - default_constraints().into_resolved() - } - - fn default_sanitized_constraints() -> SanitizedMediaTrackConstraints { - default_resolved_constraints().into_sanitized(&default_supported_constraints()) - } - - #[test] - fn pass_through() { - let possible_settings = default_possible_settings(); - let sanitized_constraints = default_sanitized_constraints(); - - let actual = select_settings_candidates( - &possible_settings[..], - &sanitized_constraints, - DeviceInformationExposureMode::Exposed, - ) - .unwrap(); - let expected: Vec<_> = possible_settings.iter().collect(); - - assert_eq!(actual, expected); - } -} - -mod overconstrained { - use super::*; - use crate::MediaTrackProperty; - - #[test] - fn protected() { - let error = test_overconstrained( - &default_possible_settings(), - ResolvedMandatoryMediaTrackConstraints::from_iter([( - GROUP_ID.clone(), - ResolvedValueConstraint::default() - .exact("missing-group".to_owned()) - .into(), - )]), - DeviceInformationExposureMode::Protected, - ); - - assert_eq!(error.constraint, MediaTrackProperty::from("")); - assert_eq!(error.message, None); - } - - mod exposed { - use super::*; - - #[test] - fn missing() { - let error = test_overconstrained( - &default_possible_settings(), - ResolvedMandatoryMediaTrackConstraints::from_iter([( - GROUP_ID.clone(), - ResolvedValueConstraint::default() - .exact("missing-group".to_owned()) - .into(), - )]), - DeviceInformationExposureMode::Exposed, - ); - - let constraint = &error.constraint; - let err_message = error.message.as_ref().expect("Error message."); - - assert_eq!(constraint, &GROUP_ID); - assert_eq!( - err_message, - "Setting was missing (does not satisfy (x == \"missing-group\"))." - ); - } - - #[test] - fn mismatch() { - let error = test_overconstrained( - &default_possible_settings(), - ResolvedMandatoryMediaTrackConstraints::from_iter([( - DEVICE_ID.clone(), - ResolvedValueConstraint::default() - .exact("mismatched-device".to_owned()) - .into(), - )]), - DeviceInformationExposureMode::Exposed, - ); - - let constraint = &error.constraint; - let err_message = error.message.as_ref().expect("Error message."); - - assert_eq!(constraint, &DEVICE_ID); - assert_eq!( - err_message, - "Setting was a mismatch ([\"1080p\", \"1440p\", \"2160p\", \"480p\", \"720p\"] do not satisfy (x == \"mismatched-device\"))." - ); - } - - #[test] - fn too_small() { - let error = test_overconstrained( - &default_possible_settings(), - ResolvedMandatoryMediaTrackConstraints::from_iter([( - FRAME_RATE.clone(), - ResolvedValueRangeConstraint::default().min(1000).into(), - )]), - DeviceInformationExposureMode::Exposed, - ); - - let constraint = &error.constraint; - let err_message = error.message.as_ref().expect("Error message."); - - assert_eq!(constraint, &FRAME_RATE); - assert_eq!( - err_message, - "Setting was too small ([120, 15, 240, 30, 60] do not satisfy (1000 <= x))." - ); - } - - #[test] - fn too_large() { - let error = test_overconstrained( - &default_possible_settings(), - ResolvedMandatoryMediaTrackConstraints::from_iter([( - FRAME_RATE.clone(), - ResolvedValueRangeConstraint::default().max(10).into(), - )]), - DeviceInformationExposureMode::Exposed, - ); - - let constraint = &error.constraint; - let err_message = error.message.as_ref().expect("Error message."); - - assert_eq!(constraint, &FRAME_RATE); - assert_eq!( - err_message, - "Setting was too large ([120, 15, 240, 30, 60] do not satisfy (x <= 10))." - ); - } - } -} - -mod constrained { - use super::*; - - #[test] - fn specific_device_id() { - let possible_settings = default_possible_settings(); - - for target_settings in possible_settings.iter() { - let setting = match target_settings.get(&DEVICE_ID) { - Some(setting) => setting, - None => continue, - }; - - let actual = test_constrained( - &possible_settings, - ResolvedMandatoryMediaTrackConstraints::from_iter([( - DEVICE_ID.clone(), - ResolvedMediaTrackConstraint::exact_from(setting.clone()), - )]), - ResolvedAdvancedMediaTrackConstraints::default(), - ); - - let expected = vec![target_settings]; - - assert_eq!(actual, expected); - } - } - - mod exact { - use super::*; - - #[test] - fn value() { - let possible_settings = vec![ - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "a".into()), - (&GROUP_ID, "group-0".into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "b".into()), - (&GROUP_ID, "group-1".into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "c".into()), - (&GROUP_ID, "group-2".into()), - ]), - ]; - - let actual = test_constrained( - &possible_settings, - ResolvedMandatoryMediaTrackConstraints::from_iter([( - &GROUP_ID, - ResolvedValueConstraint::default() - .exact("group-1".to_owned()) - .into(), - )]), - ResolvedAdvancedMediaTrackConstraints::default(), - ); - - let expected = vec![&possible_settings[1]]; - - assert_eq!(actual, expected); - } - - #[test] - fn value_range() { - let possible_settings = vec![ - MediaTrackSettings::from_iter([(&DEVICE_ID, "a".into()), (&FRAME_RATE, 15.into())]), - MediaTrackSettings::from_iter([(&DEVICE_ID, "b".into()), (&FRAME_RATE, 30.into())]), - MediaTrackSettings::from_iter([(&DEVICE_ID, "c".into()), (&FRAME_RATE, 60.into())]), - ]; - - let actual = test_constrained( - &possible_settings, - ResolvedMandatoryMediaTrackConstraints::from_iter([( - &FRAME_RATE, - ResolvedValueRangeConstraint::default().exact(30).into(), - )]), - ResolvedAdvancedMediaTrackConstraints::default(), - ); - - let expected = vec![&possible_settings[1]]; - - assert_eq!(actual, expected); - } - - #[test] - fn value_sequence() { - let possible_settings = vec![ - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "a".into()), - (&GROUP_ID, "group-0".into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "b".into()), - (&GROUP_ID, "group-1".into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "c".into()), - (&GROUP_ID, "group-2".into()), - ]), - ]; - - let actual = test_constrained( - &possible_settings, - ResolvedMandatoryMediaTrackConstraints::from_iter([( - &GROUP_ID, - ResolvedValueSequenceConstraint::default() - .exact(vec!["group-1".to_owned(), "group-3".to_owned()]) - .into(), - )]), - ResolvedAdvancedMediaTrackConstraints::default(), - ); - - let expected = vec![&possible_settings[1]]; - - assert_eq!(actual, expected); - } - } - - mod ideal { - use super::*; - - #[test] - fn value() { - let possible_settings = vec![ - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "a".into()), - (&GROUP_ID, "group-0".into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "b".into()), - (&GROUP_ID, "group-1".into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "c".into()), - (&GROUP_ID, "group-2".into()), - ]), - ]; - - let actual = test_constrained( - &possible_settings, - ResolvedMandatoryMediaTrackConstraints::from_iter([( - &GROUP_ID, - ResolvedValueConstraint::default() - .ideal("group-1".to_owned()) - .into(), - )]), - ResolvedAdvancedMediaTrackConstraints::default(), - ); - - let expected = vec![&possible_settings[1]]; - - assert_eq!(actual, expected); - } - - #[test] - fn value_range() { - let possible_settings = vec![ - MediaTrackSettings::from_iter([(&DEVICE_ID, "a".into()), (&FRAME_RATE, 15.into())]), - MediaTrackSettings::from_iter([(&DEVICE_ID, "b".into()), (&FRAME_RATE, 30.into())]), - MediaTrackSettings::from_iter([(&DEVICE_ID, "c".into()), (&FRAME_RATE, 60.into())]), - ]; - - let actual = test_constrained( - &possible_settings, - ResolvedMandatoryMediaTrackConstraints::from_iter([( - &FRAME_RATE, - ResolvedValueRangeConstraint::default().ideal(32).into(), - )]), - ResolvedAdvancedMediaTrackConstraints::default(), - ); - - let expected = vec![&possible_settings[1]]; - - assert_eq!(actual, expected); - } - - #[test] - fn value_sequence() { - let possible_settings = vec![ - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "a".into()), - (&GROUP_ID, "group-0".into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "b".into()), - (&GROUP_ID, "group-1".into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "c".into()), - (&GROUP_ID, "group-2".into()), - ]), - ]; - - let actual = test_constrained( - &possible_settings, - ResolvedMandatoryMediaTrackConstraints::from_iter([( - &GROUP_ID, - ResolvedValueSequenceConstraint::default() - .ideal(vec!["group-1".to_owned(), "group-3".to_owned()]) - .into(), - )]), - ResolvedAdvancedMediaTrackConstraints::default(), - ); - - let expected = vec![&possible_settings[1]]; - - assert_eq!(actual, expected); - } - } -} - -// ``` -// ┌ -// mandatory constraints: ┤ ┄───────────────────────────────────────────┤ -// └ -// ┌ -// advanced constraints: ┤ ├─┤ ├────────────────────────────┄ -// └ -// ┌ -// possible settings: ┤ ●─────────────●──────────────●──────────────●─────────────● -// └ 480p 720p 1080p 1440p 2160p -// └───────┬──────┘ -// selected settings: ─────────────────────────────────────────┘ -// ``` -mod smoke { - use super::*; - use crate::{MediaTrackConstraintSet, ValueConstraint, ValueRangeConstraint}; - - #[test] - fn native() { - let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![ - &DEVICE_ID, - &HEIGHT, - &WIDTH, - &RESIZE_MODE, - ]); - - let possible_settings = vec![ - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "480p".into()), - (&HEIGHT, 480.into()), - (&WIDTH, 720.into()), - (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "720p".into()), - (&HEIGHT, 720.into()), - (&WIDTH, 1280.into()), - (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "1080p".into()), - (&HEIGHT, 1080.into()), - (&WIDTH, 1920.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "1440p".into()), - (&HEIGHT, 1440.into()), - (&WIDTH, 2560.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "2160p".into()), - (&HEIGHT, 2160.into()), - (&WIDTH, 3840.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]), - ]; - - let constraints = MediaTrackConstraints { - mandatory: MandatoryMediaTrackConstraints::from_iter([ - ( - &WIDTH, - ValueRangeConstraint::Constraint( - ResolvedValueRangeConstraint::default().max(2560), - ) - .into(), - ), - ( - &HEIGHT, - ValueRangeConstraint::Constraint( - ResolvedValueRangeConstraint::default().max(1440), - ) - .into(), - ), - // Unsupported constraint, which should thus get ignored: - ( - &FRAME_RATE, - ValueRangeConstraint::Constraint( - ResolvedValueRangeConstraint::default().exact(30.0), - ) - .into(), - ), - // Ideal resize-mode: - ( - &RESIZE_MODE, - ValueConstraint::Bare(ResizeMode::none()).into(), - ), - ]), - advanced: AdvancedMediaTrackConstraints::from_iter([ - // The first advanced constraint set of "exact 800p" does not match - // any candidate and should thus get ignored by the algorithm: - MediaTrackConstraintSet::from_iter([( - &HEIGHT, - ValueRangeConstraint::Constraint( - ResolvedValueRangeConstraint::default().exact(800), - ) - .into(), - )]), - // The second advanced constraint set of "no resizing" does match - // candidates and should thus be applied by the algorithm: - MediaTrackConstraintSet::from_iter([( - &RESIZE_MODE, - ValueConstraint::Constraint( - ResolvedValueConstraint::default().exact(ResizeMode::none()), - ) - .into(), - )]), - ]), - }; - - // Resolve bare values to proper constraints: - let resolved_constraints = constraints.into_resolved(); - - // Sanitize constraints, removing empty and unsupported constraints: - let sanitized_constraints = resolved_constraints.to_sanitized(&supported_constraints); - - let actual = select_settings_candidates( - &possible_settings, - &sanitized_constraints, - DeviceInformationExposureMode::Exposed, - ) - .unwrap(); - - let expected = vec![&possible_settings[2], &possible_settings[3]]; - - assert_eq!(actual, expected); - } - - #[test] - fn macros() { - use crate::macros::*; - - let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![ - &DEVICE_ID, - &HEIGHT, - &WIDTH, - &RESIZE_MODE, - ]); - - let possible_settings = vec![ - settings![ - &DEVICE_ID => "480p", - &HEIGHT => 480, - &WIDTH => 720, - &RESIZE_MODE => ResizeMode::crop_and_scale(), - ], - settings![ - &DEVICE_ID => "720p", - &HEIGHT => 720, - &WIDTH => 1280, - &RESIZE_MODE => ResizeMode::crop_and_scale(), - ], - settings![ - &DEVICE_ID => "1080p", - &HEIGHT => 1080, - &WIDTH => 1920, - &RESIZE_MODE => ResizeMode::none(), - ], - settings![ - &DEVICE_ID => "1440p", - &HEIGHT => 1440, - &WIDTH => 2560, - &RESIZE_MODE => ResizeMode::none(), - ], - settings![ - &DEVICE_ID => "2160p", - &HEIGHT => 2160, - &WIDTH => 3840, - &RESIZE_MODE => ResizeMode::none(), - ], - ]; - - let constraints = constraints! { - mandatory: { - &WIDTH => value_range_constraint!{ - max: 2560 - }, - &HEIGHT => value_range_constraint!{ - max: 1440 - }, - // Unsupported constraint, which should thus get ignored: - &FRAME_RATE => value_range_constraint!{ - exact: 30.0 - }, - }, - advanced: [ - // The first advanced constraint set of "exact 800p" does not match - // any candidate and should thus get ignored by the algorithm: - { - &HEIGHT => value_range_constraint!{ - exact: 800 - } - }, - // The second advanced constraint set of "no resizing" does match - // candidates and should thus be applied by the algorithm: - { - &RESIZE_MODE => value_constraint!{ - exact: ResizeMode::none() - } - }, - ] - }; - - // Resolve bare values to proper constraints: - let resolved_constraints = constraints.into_resolved(); - - // Sanitize constraints, removing empty and unsupported constraints: - let sanitized_constraints = resolved_constraints.to_sanitized(&supported_constraints); - - let actual = select_settings_candidates( - &possible_settings, - &sanitized_constraints, - DeviceInformationExposureMode::Exposed, - ) - .unwrap(); - - let expected = vec![&possible_settings[2], &possible_settings[3]]; - - assert_eq!(actual, expected); - } - - #[cfg(feature = "serde")] - #[test] - fn json() { - let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![ - &DEVICE_ID, - &HEIGHT, - &WIDTH, - &RESIZE_MODE, - ]); - - // Deserialize possible settings from JSON: - let possible_settings: Vec = { - let json = serde_json::json!([ - { "deviceId": "480p", "width": 720, "height": 480, "resizeMode": "crop-and-scale" }, - { "deviceId": "720p", "width": 1280, "height": 720, "resizeMode": "crop-and-scale" }, - { "deviceId": "1080p", "width": 1920, "height": 1080, "resizeMode": "none" }, - { "deviceId": "1440p", "width": 2560, "height": 1440, "resizeMode": "none" }, - { "deviceId": "2160p", "width": 3840, "height": 2160, "resizeMode": "none" }, - ]); - serde_json::from_value(json).unwrap() - }; - - // Deserialize constraints from JSON: - let constraints: MediaTrackConstraints = { - let json = serde_json::json!({ - "width": { - "max": 2560, - }, - "height": { - "max": 1440, - }, - // Unsupported constraint, which should thus get ignored: - "frameRate": { - "exact": 30.0 - }, - // Ideal resize-mode: - "resizeMode": "none", - "advanced": [ - // The first advanced constraint set of "exact 800p" does not match - // any candidate and should thus get ignored by the algorithm: - { "height": 800 }, - // The second advanced constraint set of "no resizing" does match - // candidates and should thus be applied by the algorithm: - { "resizeMode": "none" }, - ] - }); - serde_json::from_value(json).unwrap() - }; - - // Resolve bare values to proper constraints: - let resolved_constraints = constraints.into_resolved(); - - // Sanitize constraints, removing empty and unsupported constraints: - let sanitized_constraints = resolved_constraints.into_sanitized(&supported_constraints); - - let actual = select_settings_candidates( - &possible_settings, - &sanitized_constraints, - DeviceInformationExposureMode::Exposed, - ) - .unwrap(); - - let expected = vec![&possible_settings[2], &possible_settings[3]]; - - assert_eq!(actual, expected); - } -} diff --git a/constraints/src/algorithms/select_settings/tie_breaking.rs b/constraints/src/algorithms/select_settings/tie_breaking.rs deleted file mode 100644 index 52e5c2ac6..000000000 --- a/constraints/src/algorithms/select_settings/tie_breaking.rs +++ /dev/null @@ -1,187 +0,0 @@ -use std::iter::FromIterator; - -use ordered_float::NotNan; - -use crate::algorithms::FitnessDistance; -use crate::{ - MandatoryMediaTrackConstraints, MediaTrackSettings, MediaTrackSupportedConstraints, - SanitizedMandatoryMediaTrackConstraints, -}; - -/// A tie-breaking policy used for selecting a single preferred candidate -/// from a set list of equally optimal setting candidates. -pub trait TieBreakingPolicy { - /// Selects a preferred candidate from a non-empty selection of optimal candidates. - /// - /// As specified in step 6 of the `SelectSettings` algorithm: - /// - /// - /// > Select one settings dictionary from candidates, and return it as the result - /// > of the SelectSettings algorithm. The User Agent MUST use one with the - /// > smallest fitness distance, as calculated in step 3. - /// > If more than one settings dictionary have the smallest fitness distance, - /// > the User Agent chooses one of them based on system default property values - /// > and User Agent default property values. - fn select_candidate<'a, I>(&self, candidates: I) -> &'a MediaTrackSettings - where - I: IntoIterator; -} - -/// A naïve tie-breaking policy that just picks the first settings item it encounters. -pub struct FirstPolicy; - -impl FirstPolicy { - /// Creates a new policy. - pub fn new() -> Self { - Self - } -} - -impl Default for FirstPolicy { - fn default() -> Self { - Self::new() - } -} - -impl TieBreakingPolicy for FirstPolicy { - fn select_candidate<'a, I>(&self, candidates: I) -> &'a MediaTrackSettings - where - I: IntoIterator, - { - // Safety: We know that `candidates is non-empty: - candidates - .into_iter() - .next() - .expect("The `candidates` iterator should have produced at least one item.") - } -} - -/// A tie-breaking policy that picks the settings item that's closest to the specified ideal settings. -pub struct ClosestToIdealPolicy { - sanitized_constraints: SanitizedMandatoryMediaTrackConstraints, -} - -impl ClosestToIdealPolicy { - /// Creates a new policy from the given ideal settings and supported constraints. - pub fn new( - ideal_settings: MediaTrackSettings, - supported_constraints: &MediaTrackSupportedConstraints, - ) -> Self { - let sanitized_constraints = MandatoryMediaTrackConstraints::from_iter( - ideal_settings - .into_iter() - .map(|(property, setting)| (property, setting.into())), - ) - .into_resolved() - .into_sanitized(supported_constraints); - - Self { - sanitized_constraints, - } - } -} - -impl TieBreakingPolicy for ClosestToIdealPolicy { - fn select_candidate<'b, I>(&self, candidates: I) -> &'b MediaTrackSettings - where - I: IntoIterator, - { - candidates - .into_iter() - .min_by_key(|settings| { - let fitness_distance = self - .sanitized_constraints - .fitness_distance(settings) - .expect("Fitness distance should be positive."); - NotNan::new(fitness_distance).expect("Expected non-NaN fitness distance.") - }) - .expect("The `candidates` iterator should have produced at least one item.") - } -} - -#[cfg(test)] -mod tests { - use std::iter::FromIterator; - - use super::*; - use crate::property::all::name::*; - use crate::{MediaTrackSettings, MediaTrackSupportedConstraints, ResizeMode}; - - #[test] - fn first() { - let settings = vec![ - MediaTrackSettings::from_iter([(&DEVICE_ID, "device-id-0".into())]), - MediaTrackSettings::from_iter([(&DEVICE_ID, "device-id-1".into())]), - MediaTrackSettings::from_iter([(&DEVICE_ID, "device-id-2".into())]), - ]; - - let policy = FirstPolicy; - - let actual = policy.select_candidate(&settings); - - let expected = &settings[0]; - - assert_eq!(actual, expected); - } - - #[test] - fn closest_to_ideal() { - let supported_constraints = MediaTrackSupportedConstraints::from_iter(vec![ - &DEVICE_ID, - &HEIGHT, - &WIDTH, - &RESIZE_MODE, - ]); - - let settings = vec![ - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "480p".into()), - (&HEIGHT, 480.into()), - (&WIDTH, 720.into()), - (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "720p".into()), - (&HEIGHT, 720.into()), - (&WIDTH, 1280.into()), - (&RESIZE_MODE, ResizeMode::crop_and_scale().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "1080p".into()), - (&HEIGHT, 1080.into()), - (&WIDTH, 1920.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "1440p".into()), - (&HEIGHT, 1440.into()), - (&WIDTH, 2560.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]), - MediaTrackSettings::from_iter([ - (&DEVICE_ID, "2160p".into()), - (&HEIGHT, 2160.into()), - (&WIDTH, 3840.into()), - (&RESIZE_MODE, ResizeMode::none().into()), - ]), - ]; - - let ideal_settings = vec![ - MediaTrackSettings::from_iter([(&HEIGHT, 450.into()), (&WIDTH, 700.into())]), - MediaTrackSettings::from_iter([(&HEIGHT, 700.into()), (&WIDTH, 1250.into())]), - MediaTrackSettings::from_iter([(&HEIGHT, 1000.into()), (&WIDTH, 2000.into())]), - MediaTrackSettings::from_iter([(&HEIGHT, 1500.into()), (&WIDTH, 2500.into())]), - MediaTrackSettings::from_iter([(&HEIGHT, 2000.into()), (&WIDTH, 3750.into())]), - ]; - - for (index, ideal) in ideal_settings.iter().enumerate() { - let policy = ClosestToIdealPolicy::new(ideal.clone(), &supported_constraints); - - let actual = policy.select_candidate(&settings); - - let expected = &settings[index]; - - assert_eq!(actual, expected); - } - } -} diff --git a/constraints/src/capabilities.rs b/constraints/src/capabilities.rs deleted file mode 100644 index b0b0cdc62..000000000 --- a/constraints/src/capabilities.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod stream; -mod track; - -pub(crate) use self::stream::MediaStreamCapabilities; -pub use self::track::MediaTrackCapabilities; diff --git a/constraints/src/capabilities/stream.rs b/constraints/src/capabilities/stream.rs deleted file mode 100644 index cc7200099..000000000 --- a/constraints/src/capabilities/stream.rs +++ /dev/null @@ -1,58 +0,0 @@ -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use crate::MediaTrackCapabilities; - -/// The capabilities of a [`MediaStream`][media_stream] object. -/// -/// # W3C Spec Compliance -/// -/// There exists no corresponding type in the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// [media_stream]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastream -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams -#[derive(Default, Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub(crate) struct MediaStreamCapabilities { - #[cfg_attr( - feature = "serde", - serde(skip_serializing_if = "core::option::Option::is_none") - )] - pub audio: Option, - #[cfg_attr( - feature = "serde", - serde(skip_serializing_if = "core::option::Option::is_none") - )] - pub video: Option, -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use super::*; - use crate::macros::test_serde_symmetry; - - type Subject = MediaStreamCapabilities; - - #[test] - fn default() { - let subject = Subject::default(); - let json = serde_json::json!({}); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn customized() { - let subject = Subject { - audio: Some(MediaTrackCapabilities::default()), - video: None, - }; - let json = serde_json::json!({ - "audio": {} - }); - - test_serde_symmetry!(subject: subject, json: json); - } -} diff --git a/constraints/src/capabilities/track.rs b/constraints/src/capabilities/track.rs deleted file mode 100644 index 6fb7f8ac8..000000000 --- a/constraints/src/capabilities/track.rs +++ /dev/null @@ -1,167 +0,0 @@ -use std::collections::HashMap; -use std::iter::FromIterator; -use std::ops::{Deref, DerefMut}; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use crate::{MediaTrackCapability, MediaTrackProperty}; - -/// The capabilities of a [`MediaStreamTrack`][media_stream_track] object. -/// -/// # W3C Spec Compliance -/// -/// Corresponds to [`MediaTrackCapabilities`][media_track_capabilities] -/// from the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// The W3C spec defines `MediaTrackSettings` in terma of a dictionary, -/// which per the [WebIDL spec][webidl_spec] is an ordered map (e.g. `IndexMap`). -/// Since the spec however does not make use of the order of items -/// in the map we use a simple `HashMap`. -/// -/// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -/// [media_track_capabilities]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackcapabilities -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams -/// [webidl_spec]: https://webidl.spec.whatwg.org/#idl-dictionaries -#[derive(Debug, Clone, Default, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(transparent))] -pub struct MediaTrackCapabilities(HashMap); - -impl MediaTrackCapabilities { - /// Creates a capabilities value from its inner hashmap. - pub fn new(capabilities: HashMap) -> Self { - Self(capabilities) - } - - /// Consumes the value, returning its inner hashmap. - pub fn into_inner(self) -> HashMap { - self.0 - } -} - -impl Deref for MediaTrackCapabilities { - type Target = HashMap; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for MediaTrackCapabilities { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl FromIterator<(T, MediaTrackCapability)> for MediaTrackCapabilities -where - T: Into, -{ - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - Self::new(iter.into_iter().map(|(k, v)| (k.into(), v)).collect()) - } -} - -impl IntoIterator for MediaTrackCapabilities { - type Item = (MediaTrackProperty, MediaTrackCapability); - type IntoIter = std::collections::hash_map::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::property::all::name::*; - - type Subject = MediaTrackCapabilities; - - #[test] - fn into_inner() { - let hash_map = HashMap::from_iter([ - (DEVICE_ID.clone(), "device-id".into()), - (AUTO_GAIN_CONTROL.clone(), true.into()), - (CHANNEL_COUNT.clone(), (12..=34).into()), - (LATENCY.clone(), (1.2..=3.4).into()), - ]); - - let subject = Subject::new(hash_map.clone()); - - let actual = subject.into_inner(); - - let expected = hash_map; - - assert_eq!(actual, expected); - } - - #[test] - fn into_iter() { - let hash_map = HashMap::from_iter([ - (DEVICE_ID.clone(), "device-id".into()), - (AUTO_GAIN_CONTROL.clone(), true.into()), - (CHANNEL_COUNT.clone(), (12..=34).into()), - (LATENCY.clone(), (1.2..=3.4).into()), - ]); - - let subject = Subject::new(hash_map.clone()); - - let actual: HashMap<_, _> = subject.into_iter().collect(); - - let expected = hash_map; - - assert_eq!(actual, expected); - } - - #[test] - fn deref_and_deref_mut() { - let mut subject = Subject::default(); - - // Deref mut: - subject.insert(DEVICE_ID.clone(), "device-id".into()); - - // Deref: - assert!(subject.contains_key(&DEVICE_ID)); - } -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use super::*; - use crate::macros::test_serde_symmetry; - use crate::property::all::name::*; - - type Subject = MediaTrackCapabilities; - - #[test] - fn default() { - let subject = Subject::default(); - let json = serde_json::json!({}); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn customized() { - let subject = Subject::from_iter([ - (&DEVICE_ID, "device-id".into()), - (&AUTO_GAIN_CONTROL, true.into()), - (&CHANNEL_COUNT, (12..=34).into()), - (&LATENCY, (1.2..=3.4).into()), - ]); - let json = serde_json::json!({ - "deviceId": "device-id".to_owned(), - "autoGainControl": true, - "channelCount": { "min": 12, "max": 34 }, - "latency": { "min": 1.2, "max": 3.4 }, - }); - - test_serde_symmetry!(subject: subject, json: json); - } -} diff --git a/constraints/src/capability.rs b/constraints/src/capability.rs deleted file mode 100644 index 28621b643..000000000 --- a/constraints/src/capability.rs +++ /dev/null @@ -1,219 +0,0 @@ -mod value; -mod value_range; -mod value_sequence; - -use std::ops::RangeInclusive; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -pub use self::value::MediaTrackValueCapability; -pub use self::value_range::MediaTrackValueRangeCapability; -pub use self::value_sequence::MediaTrackValueSequenceCapability; - -/// A single [capability][media_track_capabilities] value of a [`MediaStreamTrack`][media_stream_track] object. -/// -/// # W3C Spec Compliance -/// -/// There exists no corresponding type in the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -/// [media_track_capabilities]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackcapabilities -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(untagged))] -pub enum MediaTrackCapability { - // IMPORTANT: - // `BoolSequence` must be ordered before `Bool(…)` in order for - // `serde` to decode the correct variant. - /// A sequence of boolean-valued media track capabilities. - BoolSequence(MediaTrackValueSequenceCapability), - /// A single boolean-valued media track capability. - Bool(MediaTrackValueCapability), - // `IntegerRange` must be ordered before `FloatRange(…)` in order for - // `serde` to decode the correct variant. - /// A range of integer-valued media track capabilities. - IntegerRange(MediaTrackValueRangeCapability), - /// A range of floating-point-valued media track capabilities. - FloatRange(MediaTrackValueRangeCapability), - // IMPORTANT: - // `StringSequence` must be ordered before `String(…)` in order for - // `serde` to decode the correct variant. - /// A sequence of string-valued media track capabilities. - StringSequence(MediaTrackValueSequenceCapability), - /// A single string-valued media track capability. - String(MediaTrackValueCapability), -} - -impl From for MediaTrackCapability { - fn from(capability: bool) -> Self { - Self::Bool(capability.into()) - } -} - -impl From> for MediaTrackCapability { - fn from(capability: Vec) -> Self { - Self::BoolSequence(capability.into()) - } -} - -impl From> for MediaTrackCapability { - fn from(capability: RangeInclusive) -> Self { - Self::IntegerRange(capability.into()) - } -} - -impl From> for MediaTrackCapability { - fn from(capability: RangeInclusive) -> Self { - Self::FloatRange(capability.into()) - } -} - -impl From for MediaTrackCapability { - fn from(capability: String) -> Self { - Self::String(capability.into()) - } -} - -impl<'a> From<&'a str> for MediaTrackCapability { - fn from(capability: &'a str) -> Self { - let capability: String = capability.to_owned(); - Self::from(capability) - } -} - -impl From> for MediaTrackCapability { - fn from(capability: Vec) -> Self { - Self::StringSequence(capability.into()) - } -} - -impl From> for MediaTrackCapability { - fn from(capability: Vec<&str>) -> Self { - let capability: Vec = capability.into_iter().map(|c| c.to_owned()).collect(); - Self::from(capability) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - type Subject = MediaTrackCapability; - - mod from { - use super::*; - - #[test] - fn bool_sequence() { - let actual = Subject::from(vec![false, true]); - let expected = Subject::BoolSequence(vec![false, true].into()); - - assert_eq!(actual, expected); - } - - #[test] - fn bool() { - let actual = Subject::from(true); - let expected = Subject::Bool(true.into()); - - assert_eq!(actual, expected); - } - - #[test] - fn integer_range() { - let actual = Subject::from(12..=34); - let expected = Subject::IntegerRange((12..=34).into()); - - assert_eq!(actual, expected); - } - - #[test] - fn float() { - let actual = Subject::from(1.2..=3.4); - let expected = Subject::FloatRange((1.2..=3.4).into()); - - assert_eq!(actual, expected); - } - - #[test] - fn string_sequence() { - let actual = Subject::from(vec!["foo".to_owned(), "bar".to_owned()]); - let expected = Subject::StringSequence(vec!["foo".to_owned(), "bar".to_owned()].into()); - - assert_eq!(actual, expected); - } - - #[test] - fn string() { - let actual = Subject::from("foo".to_owned()); - let expected = Subject::String("foo".to_owned().into()); - - assert_eq!(actual, expected); - } - } -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use super::*; - use crate::macros::test_serde_symmetry; - - type Subject = MediaTrackCapability; - - #[test] - fn bool_sequence() { - let subject = Subject::BoolSequence(vec![false, true].into()); - let json = serde_json::json!([false, true]); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn bool() { - let subject = Subject::Bool(true.into()); - let json = serde_json::json!(true); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn integer_range() { - let subject = Subject::IntegerRange((12..=34).into()); - let json = serde_json::json!({ - "min": 12, - "max": 34, - }); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn float() { - let subject = Subject::FloatRange((1.2..=3.4).into()); - let json = serde_json::json!({ - "min": 1.2, - "max": 3.4, - }); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn string_sequence() { - let subject = Subject::StringSequence(vec!["foo".to_owned(), "bar".to_owned()].into()); - let json = serde_json::json!(["foo", "bar"]); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn string() { - let subject = Subject::String("foo".to_owned().into()); - let json = serde_json::json!("foo"); - - test_serde_symmetry!(subject: subject, json: json); - } -} diff --git a/constraints/src/capability/value.rs b/constraints/src/capability/value.rs deleted file mode 100644 index a55c0a32e..000000000 --- a/constraints/src/capability/value.rs +++ /dev/null @@ -1,74 +0,0 @@ -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// A capability specifying a single supported value. -/// -/// # W3C Spec Compliance -/// -/// There exists no direct corresponding type in the -/// W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec, -/// since the `MediaTrackValueCapability` type aims to be a -/// generalization over multiple types in the W3C spec: -/// -/// | Rust | W3C | -/// | ----------------------------------- | ------------------------- | -/// | `MediaTrackValueCapability` | [`DOMString`][dom_string] | -/// -/// [dom_string]: https://webidl.spec.whatwg.org/#idl-DOMString -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/ -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(transparent))] -pub struct MediaTrackValueCapability { - pub value: T, -} - -impl From for MediaTrackValueCapability { - fn from(value: T) -> Self { - Self { value } - } -} - -impl From<&str> for MediaTrackValueCapability { - fn from(value: &str) -> Self { - Self { - value: value.to_owned(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - type Subject = MediaTrackValueCapability; - - #[test] - fn from_str() { - let subject = Subject::from("string"); - - let actual = subject.value.as_str(); - let expected = "string"; - - assert_eq!(actual, expected); - } -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use super::*; - use crate::macros::test_serde_symmetry; - - type Subject = MediaTrackValueCapability; - - #[test] - fn customized() { - let subject = Subject { - value: "string".to_owned(), - }; - let json = serde_json::json!("string"); - - test_serde_symmetry!(subject: subject, json: json); - } -} diff --git a/constraints/src/capability/value_range.rs b/constraints/src/capability/value_range.rs deleted file mode 100644 index 786b9b7c8..000000000 --- a/constraints/src/capability/value_range.rs +++ /dev/null @@ -1,207 +0,0 @@ -use std::ops::{RangeFrom, RangeInclusive, RangeToInclusive}; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// A capability specifying a range of supported values. -/// -/// # W3C Spec Compliance -/// -/// There exists no direct corresponding type in the -/// W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec, -/// since the `MediaTrackValueRangeCapability` type aims to be a -/// generalization over multiple types in the W3C spec: -/// -/// | Rust | W3C | -/// | ------------------------------------- | ----------------------------- | -/// | `MediaTrackValueRangeCapability` | [`ULongRange`][ulong_range] | -/// | `MediaTrackValueRangeCapability` | [`DoubleRange`][double_range] | -/// -/// [double_range]: https://www.w3.org/TR/mediacapture-streams/#dom-doublerange -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/ -/// [ulong_range]: https://www.w3.org/TR/mediacapture-streams/#dom-ulongrange -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct MediaTrackValueRangeCapability { - #[cfg_attr( - feature = "serde", - serde(skip_serializing_if = "core::option::Option::is_none") - )] - pub min: Option, - #[cfg_attr( - feature = "serde", - serde(skip_serializing_if = "core::option::Option::is_none") - )] - pub max: Option, -} - -impl Default for MediaTrackValueRangeCapability { - fn default() -> Self { - Self { - min: Default::default(), - max: Default::default(), - } - } -} - -impl From> for MediaTrackValueRangeCapability { - fn from(range: RangeInclusive) -> Self { - let (min, max) = range.into_inner(); - Self { - min: Some(min), - max: Some(max), - } - } -} - -impl From> for MediaTrackValueRangeCapability { - fn from(range: RangeFrom) -> Self { - Self { - min: Some(range.start), - max: None, - } - } -} - -impl From> for MediaTrackValueRangeCapability { - fn from(range: RangeToInclusive) -> Self { - Self { - min: None, - max: Some(range.end), - } - } -} - -impl MediaTrackValueRangeCapability { - pub fn contains(&self, value: &T) -> bool - where - T: PartialOrd, - { - // FIXME(regexident): replace with if-let-chain, once stabilized: - // Tracking issue: https://github.com/rust-lang/rust/issues/53667 - if let Some(ref min) = self.min { - if min > value { - return false; - } - } - // FIXME(regexident): replace with if-let-chain, once stabilized: - // Tracking issue: https://github.com/rust-lang/rust/issues/53667 - if let Some(ref max) = self.max { - if max < value { - return false; - } - } - true - } -} - -#[cfg(test)] -mod tests { - use super::*; - - type Subject = MediaTrackValueRangeCapability; - - #[test] - fn default() { - let subject = Subject::default(); - - assert_eq!(subject.min, None); - assert_eq!(subject.max, None); - } - - mod from { - use super::*; - - #[test] - fn range_inclusive() { - let subject = Subject::from(1..=5); - - assert_eq!(subject.min, Some(1)); - assert_eq!(subject.max, Some(5)); - } - - #[test] - fn range_from() { - let subject = Subject::from(1..); - - assert_eq!(subject.min, Some(1)); - assert_eq!(subject.max, None); - } - - #[test] - fn range_to_inclusive() { - let subject = Subject::from(..=5); - - assert_eq!(subject.min, None); - assert_eq!(subject.max, Some(5)); - } - } - - mod contains { - use super::*; - - #[test] - fn default() { - let subject = Subject::default(); - - assert!(subject.contains(&0)); - assert!(subject.contains(&1)); - assert!(subject.contains(&5)); - assert!(subject.contains(&6)); - } - - #[test] - fn from_range_inclusive() { - let subject = Subject::from(1..=5); - - assert!(!subject.contains(&0)); - assert!(subject.contains(&1)); - assert!(subject.contains(&5)); - assert!(!subject.contains(&6)); - } - - #[test] - fn from_range_from() { - let subject = Subject::from(1..); - - assert!(!subject.contains(&0)); - assert!(subject.contains(&1)); - assert!(subject.contains(&5)); - assert!(subject.contains(&6)); - } - - #[test] - fn from_range_to_inclusive() { - let subject = Subject::from(..=5); - - assert!(subject.contains(&0)); - assert!(subject.contains(&1)); - assert!(subject.contains(&5)); - assert!(!subject.contains(&6)); - } - } -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use super::*; - use crate::macros::test_serde_symmetry; - - type Subject = MediaTrackValueRangeCapability; - - #[test] - fn customized() { - let subject = Subject { - min: Some(12), - max: Some(34), - }; - let json = serde_json::json!({ - "min": 12, - "max": 34, - }); - - test_serde_symmetry!(subject: subject, json: json); - } -} diff --git a/constraints/src/capability/value_sequence.rs b/constraints/src/capability/value_sequence.rs deleted file mode 100644 index 0a090c499..000000000 --- a/constraints/src/capability/value_sequence.rs +++ /dev/null @@ -1,99 +0,0 @@ -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// A capability specifying a range of supported values. -/// -/// # W3C Spec Compliance -/// -/// Corresponds to [`sequence`][sequence] from the W3C ["WebIDL"][webidl_spec] spec: -/// -/// | Rust | W3C | -/// | ----------------------------------------- | --------------------- | -/// | `MediaTrackValueSequenceCapability` | `sequence` | -/// | `MediaTrackValueSequenceCapability` | `sequence` | -/// -/// [sequence]: https://webidl.spec.whatwg.org/#idl-sequence -/// [webidl_spec]: https://webidl.spec.whatwg.org/ -#[derive(Default, Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(transparent))] -pub struct MediaTrackValueSequenceCapability { - pub values: Vec, -} - -impl From for MediaTrackValueSequenceCapability { - fn from(value: T) -> Self { - Self { - values: vec![value], - } - } -} - -impl From> for MediaTrackValueSequenceCapability { - fn from(values: Vec) -> Self { - Self { values } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - type Subject = MediaTrackValueSequenceCapability; - - #[test] - fn default() { - let subject = Subject::default(); - - let actual = subject.values; - - let expected: Vec = vec![]; - - assert_eq!(actual, expected); - } - - mod from { - use super::*; - - #[test] - fn value() { - let subject = Subject::from("foo".to_owned()); - - let actual = subject.values; - - let expected: Vec = vec!["foo".to_owned()]; - - assert_eq!(actual, expected); - } - - #[test] - fn values() { - let subject = Subject::from(vec!["foo".to_owned(), "bar".to_owned()]); - - let actual = subject.values; - - let expected: Vec = vec!["foo".to_owned(), "bar".to_owned()]; - - assert_eq!(actual, expected); - } - } -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use super::*; - use crate::macros::test_serde_symmetry; - - type Subject = MediaTrackValueSequenceCapability; - - #[test] - fn customized() { - let subject = Subject { - values: vec!["foo".to_owned(), "bar".to_owned()], - }; - let json = serde_json::json!(["foo", "bar"]); - - test_serde_symmetry!(subject: subject, json: json); - } -} diff --git a/constraints/src/constraint.rs b/constraints/src/constraint.rs deleted file mode 100644 index 2ad4f10b4..000000000 --- a/constraints/src/constraint.rs +++ /dev/null @@ -1,789 +0,0 @@ -use std::ops::Deref; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -pub use self::value::{ResolvedValueConstraint, ValueConstraint}; -pub use self::value_range::{ResolvedValueRangeConstraint, ValueRangeConstraint}; -pub use self::value_sequence::{ResolvedValueSequenceConstraint, ValueSequenceConstraint}; -use crate::MediaTrackSetting; - -mod value; -mod value_range; -mod value_sequence; - -/// An empty [constraint][media_track_constraints] value for a [`MediaStreamTrack`][media_stream_track] object. -/// -/// # W3C Spec Compliance -/// -/// There exists no corresponding type in the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// The purpose of this type is to reduce parsing ambiguity, since all constraint variant types -/// support serializing from an empty map, but an empty map isn't typed, really, -/// so parsing to a specifically typed constraint would be wrong, type-wise. -/// -/// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -/// [media_track_constraints]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackconstraints -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] -pub struct EmptyConstraint {} - -/// The strategy of a track [constraint][constraint]. -/// -/// [constraint]: https://www.w3.org/TR/mediacapture-streams/#dfn-constraint -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum MediaTrackConstraintResolutionStrategy { - /// Resolve bare values to `ideal` constraints. - BareToIdeal, - /// Resolve bare values to `exact` constraints. - BareToExact, -} - -/// A single [constraint][media_track_constraints] value for a [`MediaStreamTrack`][media_stream_track] object. -/// -/// # W3C Spec Compliance -/// -/// There exists no corresponding type in the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -/// [media_track_constraints]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackconstraints -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(untagged))] -pub enum MediaTrackConstraint { - /// An empty constraint. - Empty(EmptyConstraint), - // `IntegerRange` must be ordered before `FloatRange(…)` in order for - // `serde` to decode the correct variant. - /// An integer-valued media track range constraint. - IntegerRange(ValueRangeConstraint), - /// An floating-point-valued media track range constraint. - FloatRange(ValueRangeConstraint), - // `Bool` must be ordered after `IntegerRange(…)`/`FloatRange(…)` in order for - // `serde` to decode the correct variant. - /// A single boolean-valued media track constraint. - Bool(ValueConstraint), - // `StringSequence` must be ordered before `String(…)` in order for - // `serde` to decode the correct variant. - /// A sequence of string-valued media track constraints. - StringSequence(ValueSequenceConstraint), - /// A single string-valued media track constraint. - String(ValueConstraint), -} - -impl Default for MediaTrackConstraint { - fn default() -> Self { - Self::Empty(EmptyConstraint {}) - } -} - -// Bool constraint: - -impl From for MediaTrackConstraint { - fn from(bare: bool) -> Self { - Self::Bool(bare.into()) - } -} - -impl From> for MediaTrackConstraint { - fn from(constraint: ResolvedValueConstraint) -> Self { - Self::Bool(constraint.into()) - } -} - -impl From> for MediaTrackConstraint { - fn from(constraint: ValueConstraint) -> Self { - Self::Bool(constraint) - } -} - -// Unsigned integer range constraint: - -impl From for MediaTrackConstraint { - fn from(bare: u64) -> Self { - Self::IntegerRange(bare.into()) - } -} - -impl From> for MediaTrackConstraint { - fn from(constraint: ResolvedValueRangeConstraint) -> Self { - Self::IntegerRange(constraint.into()) - } -} - -impl From> for MediaTrackConstraint { - fn from(constraint: ValueRangeConstraint) -> Self { - Self::IntegerRange(constraint) - } -} - -// Floating-point range constraint: - -impl From for MediaTrackConstraint { - fn from(bare: f64) -> Self { - Self::FloatRange(bare.into()) - } -} - -impl From> for MediaTrackConstraint { - fn from(constraint: ResolvedValueRangeConstraint) -> Self { - Self::FloatRange(constraint.into()) - } -} - -impl From> for MediaTrackConstraint { - fn from(constraint: ValueRangeConstraint) -> Self { - Self::FloatRange(constraint) - } -} - -// String sequence constraint: - -impl From> for MediaTrackConstraint { - fn from(bare: Vec) -> Self { - Self::StringSequence(bare.into()) - } -} - -impl From> for MediaTrackConstraint { - fn from(bare: Vec<&str>) -> Self { - let bare: Vec = bare.into_iter().map(|c| c.to_owned()).collect(); - Self::from(bare) - } -} - -impl From> for MediaTrackConstraint { - fn from(constraint: ResolvedValueSequenceConstraint) -> Self { - Self::StringSequence(constraint.into()) - } -} - -impl From> for MediaTrackConstraint { - fn from(constraint: ValueSequenceConstraint) -> Self { - Self::StringSequence(constraint) - } -} - -// String constraint: - -impl From for MediaTrackConstraint { - fn from(bare: String) -> Self { - Self::String(bare.into()) - } -} - -impl<'a> From<&'a str> for MediaTrackConstraint { - fn from(bare: &'a str) -> Self { - let bare: String = bare.to_owned(); - Self::from(bare) - } -} - -impl From> for MediaTrackConstraint { - fn from(constraint: ResolvedValueConstraint) -> Self { - Self::String(constraint.into()) - } -} - -impl From> for MediaTrackConstraint { - fn from(constraint: ValueConstraint) -> Self { - Self::String(constraint) - } -} - -// Conversion from settings: - -impl From for MediaTrackConstraint { - fn from(settings: MediaTrackSetting) -> Self { - match settings { - MediaTrackSetting::Bool(value) => Self::Bool(value.into()), - MediaTrackSetting::Integer(value) => { - Self::IntegerRange((value.clamp(0, i64::MAX) as u64).into()) - } - MediaTrackSetting::Float(value) => Self::FloatRange(value.into()), - MediaTrackSetting::String(value) => Self::String(value.into()), - } - } -} - -impl MediaTrackConstraint { - /// Returns `true` if `self` is empty, otherwise `false`. - pub fn is_empty(&self) -> bool { - match self { - Self::Empty(_) => true, - Self::IntegerRange(constraint) => constraint.is_empty(), - Self::FloatRange(constraint) => constraint.is_empty(), - Self::Bool(constraint) => constraint.is_empty(), - Self::StringSequence(constraint) => constraint.is_empty(), - Self::String(constraint) => constraint.is_empty(), - } - } - - /// Returns a resolved representation of the constraint - /// with bare values resolved to fully-qualified constraints. - pub fn to_resolved( - &self, - strategy: MediaTrackConstraintResolutionStrategy, - ) -> ResolvedMediaTrackConstraint { - self.clone().into_resolved(strategy) - } - - /// Consumes the constraint, returning a resolved representation of the - /// constraint with bare values resolved to fully-qualified constraints. - pub fn into_resolved( - self, - strategy: MediaTrackConstraintResolutionStrategy, - ) -> ResolvedMediaTrackConstraint { - match self { - Self::Empty(constraint) => ResolvedMediaTrackConstraint::Empty(constraint), - Self::IntegerRange(constraint) => { - ResolvedMediaTrackConstraint::IntegerRange(constraint.into_resolved(strategy)) - } - Self::FloatRange(constraint) => { - ResolvedMediaTrackConstraint::FloatRange(constraint.into_resolved(strategy)) - } - Self::Bool(constraint) => { - ResolvedMediaTrackConstraint::Bool(constraint.into_resolved(strategy)) - } - Self::StringSequence(constraint) => { - ResolvedMediaTrackConstraint::StringSequence(constraint.into_resolved(strategy)) - } - Self::String(constraint) => { - ResolvedMediaTrackConstraint::String(constraint.into_resolved(strategy)) - } - } - } -} - -/// A single [constraint][media_track_constraints] value for a [`MediaStreamTrack`][media_stream_track] object -/// with its potential bare value either resolved to an `exact` or `ideal` constraint. -/// -/// # W3C Spec Compliance -/// -/// There exists no corresponding type in the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -/// [media_track_constraints]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackconstraints -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(untagged))] -pub enum ResolvedMediaTrackConstraint { - /// An empty constraint. - Empty(EmptyConstraint), - /// An integer-valued media track range constraint. - IntegerRange(ResolvedValueRangeConstraint), - /// An floating-point-valued media track range constraint. - FloatRange(ResolvedValueRangeConstraint), - /// A single boolean-valued media track constraint. - Bool(ResolvedValueConstraint), - /// A sequence of string-valued media track constraints. - StringSequence(ResolvedValueSequenceConstraint), - /// A single string-valued media track constraint. - String(ResolvedValueConstraint), -} - -impl Default for ResolvedMediaTrackConstraint { - fn default() -> Self { - Self::Empty(EmptyConstraint {}) - } -} - -impl std::fmt::Display for ResolvedMediaTrackConstraint { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Empty(_constraint) => "".fmt(f), - Self::IntegerRange(constraint) => constraint.fmt(f), - Self::FloatRange(constraint) => constraint.fmt(f), - Self::Bool(constraint) => constraint.fmt(f), - Self::StringSequence(constraint) => constraint.fmt(f), - Self::String(constraint) => constraint.fmt(f), - } - } -} - -// Bool constraint: - -impl From> for ResolvedMediaTrackConstraint { - fn from(constraint: ResolvedValueConstraint) -> Self { - Self::Bool(constraint) - } -} - -// Unsigned integer range constraint: - -impl From> for ResolvedMediaTrackConstraint { - fn from(constraint: ResolvedValueRangeConstraint) -> Self { - Self::IntegerRange(constraint) - } -} - -// Floating-point range constraint: - -impl From> for ResolvedMediaTrackConstraint { - fn from(constraint: ResolvedValueRangeConstraint) -> Self { - Self::FloatRange(constraint) - } -} - -// String sequence constraint: - -impl From> for ResolvedMediaTrackConstraint { - fn from(constraint: ResolvedValueSequenceConstraint) -> Self { - Self::StringSequence(constraint) - } -} - -// String constraint: - -impl From> for ResolvedMediaTrackConstraint { - fn from(constraint: ResolvedValueConstraint) -> Self { - Self::String(constraint) - } -} - -impl ResolvedMediaTrackConstraint { - /// Creates a resolved media track constraint by resolving - /// bare values to exact constraints: `{ exact: bare }`. - pub fn exact_from(setting: MediaTrackSetting) -> Self { - MediaTrackConstraint::from(setting) - .into_resolved(MediaTrackConstraintResolutionStrategy::BareToExact) - } - - /// Creates a resolved media track constraint by resolving - /// bare values to ideal constraints: `{ ideal: bare }`. - pub fn ideal_from(setting: MediaTrackSetting) -> Self { - MediaTrackConstraint::from(setting) - .into_resolved(MediaTrackConstraintResolutionStrategy::BareToIdeal) - } - - /// Returns `true` if `self` is required, otherwise `false`. - pub fn is_required(&self) -> bool { - match self { - Self::Empty(_constraint) => false, - Self::IntegerRange(constraint) => constraint.is_required(), - Self::FloatRange(constraint) => constraint.is_required(), - Self::Bool(constraint) => constraint.is_required(), - Self::StringSequence(constraint) => constraint.is_required(), - Self::String(constraint) => constraint.is_required(), - } - } - - /// Returns `true` if `self` is empty, otherwise `false`. - pub fn is_empty(&self) -> bool { - match self { - Self::Empty(_constraint) => true, - Self::IntegerRange(constraint) => constraint.is_empty(), - Self::FloatRange(constraint) => constraint.is_empty(), - Self::Bool(constraint) => constraint.is_empty(), - Self::StringSequence(constraint) => constraint.is_empty(), - Self::String(constraint) => constraint.is_empty(), - } - } - - /// Returns a corresponding constraint containing only required values. - pub fn to_required_only(&self) -> Self { - self.clone().into_required_only() - } - - /// Consumes `self, returning a corresponding constraint - /// containing only required values. - pub fn into_required_only(self) -> Self { - match self { - Self::Empty(constraint) => Self::Empty(constraint), - Self::IntegerRange(constraint) => Self::IntegerRange(constraint.into_required_only()), - Self::FloatRange(constraint) => Self::FloatRange(constraint.into_required_only()), - Self::Bool(constraint) => Self::Bool(constraint.into_required_only()), - Self::StringSequence(constraint) => { - Self::StringSequence(constraint.into_required_only()) - } - Self::String(constraint) => Self::String(constraint.into_required_only()), - } - } - - /// Returns a corresponding sanitized constraint - /// if `self` is non-empty, otherwise `None`. - pub fn to_sanitized(&self) -> Option { - self.clone().into_sanitized() - } - - /// Consumes `self`, returning a corresponding sanitized constraint - /// if `self` is non-empty, otherwise `None`. - pub fn into_sanitized(self) -> Option { - if self.is_empty() { - return None; - } - - Some(SanitizedMediaTrackConstraint(self)) - } -} - -/// A single non-empty [constraint][media_track_constraints] value for a [`MediaStreamTrack`][media_stream_track] object. -/// -/// # Invariant -/// -/// The wrapped `ResolvedMediaTrackConstraint` MUST not be empty. -/// -/// To enforce this invariant the only way to create an instance of this type -/// is by calling `constraint.to_sanitized()`/`constraint.into_sanitized()` on -/// an instance of `ResolvedMediaTrackConstraint`, which returns `None` if `self` is empty. -/// -/// Further more `self.0` MUST NOT be exposed mutably, -/// as otherwise it could become empty via mutation. -/// -/// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -/// [media_track_constraints]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackconstraints -#[derive(Debug, Clone, PartialEq)] -pub struct SanitizedMediaTrackConstraint(ResolvedMediaTrackConstraint); - -impl Deref for SanitizedMediaTrackConstraint { - type Target = ResolvedMediaTrackConstraint; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl SanitizedMediaTrackConstraint { - /// Consumes `self` returning its inner resolved constraint. - pub fn into_inner(self) -> ResolvedMediaTrackConstraint { - self.0 - } -} - -#[cfg(test)] -mod tests { - use MediaTrackConstraintResolutionStrategy::*; - - use super::*; - - type Subject = MediaTrackConstraint; - - #[test] - fn default() { - let subject = Subject::default(); - - let actual = subject.is_empty(); - let expected = true; - - assert_eq!(actual, expected); - } - - mod from { - - use super::*; - - #[test] - fn setting() { - use crate::MediaTrackSetting; - - assert!(matches!( - Subject::from(MediaTrackSetting::Bool(true)), - Subject::Bool(ValueConstraint::Bare(_)) - )); - assert!(matches!( - Subject::from(MediaTrackSetting::Integer(42)), - Subject::IntegerRange(ValueRangeConstraint::Bare(_)) - )); - assert!(matches!( - Subject::from(MediaTrackSetting::Float(4.2)), - Subject::FloatRange(ValueRangeConstraint::Bare(_)) - )); - assert!(matches!( - Subject::from(MediaTrackSetting::String("string".to_owned())), - Subject::String(ValueConstraint::Bare(_)) - )); - } - - #[test] - fn bool() { - let subjects = [ - Subject::from(false), - Subject::from(ValueConstraint::::default()), - Subject::from(ResolvedValueConstraint::::default()), - ]; - - for subject in subjects { - // TODO: replace with `assert_matches!(…)`, once stabilized: - // Tracking issue: https://github.com/rust-lang/rust/issues/82775 - assert!(matches!(subject, Subject::Bool(_))); - } - } - - #[test] - fn integer_range() { - let subjects = [ - Subject::from(42_u64), - Subject::from(ValueRangeConstraint::::default()), - Subject::from(ResolvedValueRangeConstraint::::default()), - ]; - - for subject in subjects { - // TODO: replace with `assert_matches!(…)`, once stabilized: - // Tracking issue: https://github.com/rust-lang/rust/issues/82775 - assert!(matches!(subject, Subject::IntegerRange(_))); - } - } - - #[test] - fn float_range() { - let subjects = [ - Subject::from(42.0_f64), - Subject::from(ValueRangeConstraint::::default()), - Subject::from(ResolvedValueRangeConstraint::::default()), - ]; - - for subject in subjects { - // TODO: replace with `assert_matches!(…)`, once stabilized: - // Tracking issue: https://github.com/rust-lang/rust/issues/82775 - assert!(matches!(subject, Subject::FloatRange(_))); - } - } - - #[test] - fn string() { - let subjects = [ - Subject::from(""), - Subject::from(String::new()), - Subject::from(ValueConstraint::::default()), - Subject::from(ResolvedValueConstraint::::default()), - ]; - - for subject in subjects { - // TODO: replace with `assert_matches!(…)`, once stabilized: - // Tracking issue: https://github.com/rust-lang/rust/issues/82775 - assert!(matches!(subject, Subject::String(_))); - } - } - - #[test] - fn string_sequence() { - let subjects = [ - Subject::from(vec![""]), - Subject::from(vec![String::new()]), - Subject::from(ValueSequenceConstraint::::default()), - Subject::from(ResolvedValueSequenceConstraint::::default()), - ]; - - for subject in subjects { - // TODO: replace with `assert_matches!(…)`, once stabilized: - // Tracking issue: https://github.com/rust-lang/rust/issues/82775 - assert!(matches!(subject, Subject::StringSequence(_))); - } - } - } - - #[test] - fn is_empty() { - let empty_subject = Subject::Empty(EmptyConstraint {}); - - assert!(empty_subject.is_empty()); - - let non_empty_subjects = [ - Subject::Bool(ValueConstraint::Bare(true)), - Subject::FloatRange(ValueRangeConstraint::Bare(42.0)), - Subject::IntegerRange(ValueRangeConstraint::Bare(42)), - Subject::String(ValueConstraint::Bare("string".to_owned())), - Subject::StringSequence(ValueSequenceConstraint::Bare(vec!["string".to_owned()])), - ]; - - for non_empty_subject in non_empty_subjects { - assert!(!non_empty_subject.is_empty()); - } - } - - #[test] - fn to_resolved() { - let subjects = [ - ( - Subject::Empty(EmptyConstraint {}), - ResolvedMediaTrackConstraint::Empty(EmptyConstraint {}), - ), - ( - Subject::Bool(ValueConstraint::Bare(true)), - ResolvedMediaTrackConstraint::Bool(ResolvedValueConstraint::default().exact(true)), - ), - ( - Subject::FloatRange(ValueRangeConstraint::Bare(42.0)), - ResolvedMediaTrackConstraint::FloatRange( - ResolvedValueRangeConstraint::default().exact(42.0), - ), - ), - ( - Subject::IntegerRange(ValueRangeConstraint::Bare(42)), - ResolvedMediaTrackConstraint::IntegerRange( - ResolvedValueRangeConstraint::default().exact(42), - ), - ), - ( - Subject::String(ValueConstraint::Bare("string".to_owned())), - ResolvedMediaTrackConstraint::String( - ResolvedValueConstraint::default().exact("string".to_owned()), - ), - ), - ( - Subject::StringSequence(ValueSequenceConstraint::Bare(vec!["string".to_owned()])), - ResolvedMediaTrackConstraint::StringSequence( - ResolvedValueSequenceConstraint::default().exact(vec!["string".to_owned()]), - ), - ), - ]; - - for (subject, expected) in subjects { - let actual = subject.to_resolved(BareToExact); - - assert_eq!(actual, expected); - } - } - - mod resolved { - use super::*; - - type Subject = ResolvedMediaTrackConstraint; - - #[test] - fn to_string() { - let scenarios = [ - (Subject::Empty(EmptyConstraint {}), ""), - ( - Subject::Bool(ResolvedValueConstraint::default().exact(true)), - "(x == true)", - ), - ( - Subject::FloatRange(ResolvedValueRangeConstraint::default().exact(42.0)), - "(x == 42.0)", - ), - ( - Subject::IntegerRange(ResolvedValueRangeConstraint::default().exact(42)), - "(x == 42)", - ), - ( - Subject::String(ResolvedValueConstraint::default().exact("string".to_owned())), - "(x == \"string\")", - ), - ( - Subject::StringSequence( - ResolvedValueSequenceConstraint::default().exact(vec!["string".to_owned()]), - ), - "(x == [\"string\"])", - ), - ]; - - for (subject, expected) in scenarios { - let actual = subject.to_string(); - - assert_eq!(actual, expected); - } - } - } -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use super::*; - use crate::macros::test_serde_symmetry; - - type Subject = MediaTrackConstraint; - - #[test] - fn empty() { - let subject = Subject::Empty(EmptyConstraint {}); - let json = serde_json::json!({}); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn bool_bare() { - let subject = Subject::Bool(true.into()); - let json = serde_json::json!(true); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn bool_constraint() { - let subject = Subject::Bool(ResolvedValueConstraint::default().exact(true).into()); - let json = serde_json::json!({ "exact": true }); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn integer_range_bare() { - let subject = Subject::IntegerRange(42.into()); - let json = serde_json::json!(42); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn integer_range_constraint() { - let subject = - Subject::IntegerRange(ResolvedValueRangeConstraint::default().exact(42).into()); - let json = serde_json::json!({ "exact": 42 }); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn float_range_bare() { - let subject = Subject::FloatRange(4.2.into()); - let json = serde_json::json!(4.2); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn float_range_constraint() { - let subject = - Subject::FloatRange(ResolvedValueRangeConstraint::default().exact(42.0).into()); - let json = serde_json::json!({ "exact": 42.0 }); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn string_sequence_bare() { - let subject = Subject::StringSequence(vec!["foo".to_owned(), "bar".to_owned()].into()); - let json = serde_json::json!(["foo", "bar"]); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn string_sequence_constraint() { - let subject = Subject::StringSequence( - ResolvedValueSequenceConstraint::default() - .exact(vec!["foo".to_owned(), "bar".to_owned()]) - .into(), - ); - let json = serde_json::json!({ "exact": ["foo", "bar"] }); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn string_bare() { - let subject = Subject::String("foo".to_owned().into()); - let json = serde_json::json!("foo"); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn string_constraint() { - let subject = Subject::String( - ResolvedValueConstraint::default() - .exact("foo".to_owned()) - .into(), - ); - let json = serde_json::json!({ "exact": "foo" }); - - test_serde_symmetry!(subject: subject, json: json); - } -} diff --git a/constraints/src/constraint/value.rs b/constraints/src/constraint/value.rs deleted file mode 100644 index 600074b13..000000000 --- a/constraints/src/constraint/value.rs +++ /dev/null @@ -1,419 +0,0 @@ -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use crate::MediaTrackConstraintResolutionStrategy; - -/// A bare value or constraint specifying a single accepted value. -/// -/// # W3C Spec Compliance -/// -/// There exists no direct corresponding type in the -/// W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec, -/// since the `ValueConstraint` type aims to be a generalization over -/// multiple types in the spec. -/// -/// | Rust | W3C | -/// | ------------------------------ | --------------------------------------- | -/// | `ValueConstraint` | [`ConstrainBoolean`][constrain_boolean] | -/// -/// [constrain_boolean]: https://www.w3.org/TR/mediacapture-streams/#dom-constrainboolean -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/ -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(untagged))] -pub enum ValueConstraint { - /// A bare-valued media track constraint. - Bare(T), - /// A fully-qualified media track constraint. - Constraint(ResolvedValueConstraint), -} - -impl Default for ValueConstraint { - fn default() -> Self { - Self::Constraint(Default::default()) - } -} - -impl From for ValueConstraint { - fn from(bare: T) -> Self { - Self::Bare(bare) - } -} - -impl From> for ValueConstraint { - fn from(constraint: ResolvedValueConstraint) -> Self { - Self::Constraint(constraint) - } -} - -impl ValueConstraint -where - T: Clone, -{ - /// Returns a resolved representation of the constraint - /// with bare values resolved to fully-qualified constraints. - pub fn to_resolved( - &self, - strategy: MediaTrackConstraintResolutionStrategy, - ) -> ResolvedValueConstraint { - self.clone().into_resolved(strategy) - } - - /// Consumes the constraint, returning a resolved representation of the - /// constraint with bare values resolved to fully-qualified constraints. - pub fn into_resolved( - self, - strategy: MediaTrackConstraintResolutionStrategy, - ) -> ResolvedValueConstraint { - match self { - Self::Bare(bare) => match strategy { - MediaTrackConstraintResolutionStrategy::BareToIdeal => { - ResolvedValueConstraint::default().ideal(bare) - } - MediaTrackConstraintResolutionStrategy::BareToExact => { - ResolvedValueConstraint::default().exact(bare) - } - }, - Self::Constraint(constraint) => constraint, - } - } -} - -impl ValueConstraint { - /// Returns `true` if `self` is empty, otherwise `false`. - pub fn is_empty(&self) -> bool { - match self { - Self::Bare(_) => false, - Self::Constraint(constraint) => constraint.is_empty(), - } - } -} - -/// A constraint specifying a single accepted value. -/// -/// # W3C Spec Compliance -/// -/// There exists no direct corresponding type in the -/// W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec, -/// since the `ValueConstraint` type aims to be a -/// generalization over multiple types in the W3C spec: -/// -/// | Rust | W3C | -/// | ------------------------------ | --------------------------------------- | -/// | `ResolvedValueConstraint` | [`ConstrainBooleanParameters`][constrain_boolean_parameters] | -/// -/// [constrain_boolean_parameters]: https://www.w3.org/TR/mediacapture-streams/#dom-constrainbooleanparameters -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/ -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct ResolvedValueConstraint { - /// The exact required value for this property. - /// - /// This is a required value. - #[cfg_attr( - feature = "serde", - serde(skip_serializing_if = "core::option::Option::is_none") - )] - pub exact: Option, - /// The ideal (target) value for this property. - /// - /// This is an optional value. - #[cfg_attr( - feature = "serde", - serde(skip_serializing_if = "core::option::Option::is_none") - )] - pub ideal: Option, -} - -impl ResolvedValueConstraint { - /// Consumes `self`, returning a corresponding constraint - /// with the exact required value set to `exact`. - #[inline] - pub fn exact(mut self, exact: U) -> Self - where - Option: From, - { - self.exact = exact.into(); - self - } - - /// Consumes `self`, returning a corresponding constraint - /// with the ideal required value set to `ideal`. - #[inline] - pub fn ideal(mut self, ideal: U) -> Self - where - Option: From, - { - self.ideal = ideal.into(); - self - } - - /// Returns `true` if `value.is_some()` is `true` for any of its required values, - /// otherwise `false`. - pub fn is_required(&self) -> bool { - self.exact.is_some() - } - - /// Returns `true` if `value.is_none()` is `true` for all of its values, - /// otherwise `false`. - pub fn is_empty(&self) -> bool { - self.exact.is_none() && self.ideal.is_none() - } - - /// Returns a corresponding constraint containing only required values. - pub fn to_required_only(&self) -> Self - where - T: Clone, - { - self.clone().into_required_only() - } - - /// Consumes `self, returning a corresponding constraint - /// containing only required values. - pub fn into_required_only(self) -> Self { - Self { - exact: self.exact, - ideal: None, - } - } -} - -impl Default for ResolvedValueConstraint { - #[inline] - fn default() -> Self { - Self { - exact: None, - ideal: None, - } - } -} - -impl std::fmt::Display for ResolvedValueConstraint -where - T: std::fmt::Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut is_first = true; - f.write_str("(")?; - if let Some(ref exact) = &self.exact { - f.write_fmt(format_args!("x == {exact:?}"))?; - is_first = false; - } - if let Some(ref ideal) = &self.ideal { - if !is_first { - f.write_str(" && ")?; - } - f.write_fmt(format_args!("x ~= {ideal:?}"))?; - is_first = false; - } - if is_first { - f.write_str("")?; - } - f.write_str(")")?; - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn to_string() { - let scenarios = [ - (ResolvedValueConstraint::default(), "()"), - ( - ResolvedValueConstraint::default().exact(true), - "(x == true)", - ), - ( - ResolvedValueConstraint::default().ideal(true), - "(x ~= true)", - ), - ( - ResolvedValueConstraint::default().exact(true).ideal(true), - "(x == true && x ~= true)", - ), - ]; - - for (constraint, expected) in scenarios { - let actual = constraint.to_string(); - - assert_eq!(actual, expected); - } - } - - #[test] - fn is_required() { - let scenarios = [ - (ResolvedValueConstraint::default(), false), - (ResolvedValueConstraint::default().exact(true), true), - (ResolvedValueConstraint::default().ideal(true), false), - ( - ResolvedValueConstraint::default().exact(true).ideal(true), - true, - ), - ]; - - for (constraint, expected) in scenarios { - let actual = constraint.is_required(); - - assert_eq!(actual, expected); - } - } - - mod is_empty { - use super::*; - - #[test] - fn bare() { - let constraint = ValueConstraint::Bare(true); - - assert!(!constraint.is_empty()); - } - - #[test] - fn constraint() { - let scenarios = [ - (ResolvedValueConstraint::default(), true), - (ResolvedValueConstraint::default().exact(true), false), - (ResolvedValueConstraint::default().ideal(true), false), - ( - ResolvedValueConstraint::default().exact(true).ideal(true), - false, - ), - ]; - - for (constraint, expected) in scenarios { - let constraint = ValueConstraint::::Constraint(constraint); - - let actual = constraint.is_empty(); - - assert_eq!(actual, expected); - } - } - } - - #[test] - fn resolve_to_advanced() { - let constraints = [ - ValueConstraint::Bare(true), - ValueConstraint::Constraint(ResolvedValueConstraint::default().exact(true)), - ]; - let strategy = MediaTrackConstraintResolutionStrategy::BareToExact; - - for constraint in constraints { - let actuals = [ - constraint.to_resolved(strategy), - constraint.into_resolved(strategy), - ]; - - let expected = ResolvedValueConstraint::default().exact(true); - - for actual in actuals { - assert_eq!(actual, expected); - } - } - } - - #[test] - fn resolve_to_basic() { - let constraints = [ - ValueConstraint::Bare(true), - ValueConstraint::Constraint(ResolvedValueConstraint::default().ideal(true)), - ]; - let strategy = MediaTrackConstraintResolutionStrategy::BareToIdeal; - - for constraint in constraints { - let actuals = [ - constraint.to_resolved(strategy), - constraint.into_resolved(strategy), - ]; - - let expected = ResolvedValueConstraint::default().ideal(true); - - for actual in actuals { - assert_eq!(actual, expected); - } - } - } -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use super::*; - use crate::macros::test_serde_symmetry; - - macro_rules! test_serde { - ($t:ty => { - value: $value:expr - }) => { - type Subject = ValueConstraint<$t>; - - #[test] - fn default() { - let subject = Subject::default(); - let json = serde_json::json!({}); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn bare() { - let subject = Subject::Bare($value.to_owned()); - let json = serde_json::json!($value); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn exact_constraint() { - let subject = Subject::Constraint(ResolvedValueConstraint::default().exact($value.to_owned())); - let json = serde_json::json!({ - "exact": $value, - }); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn ideal_constraint() { - let subject = Subject::Constraint(ResolvedValueConstraint::default().ideal($value.to_owned())); - let json = serde_json::json!({ - "ideal": $value, - }); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn full_constraint() { - let subject = Subject::Constraint(ResolvedValueConstraint::default().exact($value.to_owned()).ideal($value.to_owned())); - let json = serde_json::json!({ - "exact": $value, - "ideal": $value, - }); - - test_serde_symmetry!(subject: subject, json: json); - } - }; - } - - mod bool { - use super::*; - - test_serde!(bool => { - value: true - }); - } - - mod string { - use super::*; - - test_serde!(String => { - value: "VALUE" - }); - } -} diff --git a/constraints/src/constraint/value_range.rs b/constraints/src/constraint/value_range.rs deleted file mode 100644 index 4fed9808a..000000000 --- a/constraints/src/constraint/value_range.rs +++ /dev/null @@ -1,513 +0,0 @@ -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use crate::MediaTrackConstraintResolutionStrategy; - -/// A bare value or constraint specifying a range of accepted values. -/// -/// # W3C Spec Compliance -/// -/// There exists no direct corresponding type in the -/// W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec, -/// since the `ValueConstraint` type aims to be a generalization over -/// multiple types in the spec. -/// -/// | Rust | W3C | -/// | ---------------------------------- | ------------------------------------- | -/// | `ValueRangeConstraint` | [`ConstrainULong`][constrain_ulong] | -/// | `ValueRangeConstraint` | [`ConstrainDouble`][constrain_double] | -/// -/// [constrain_double]: https://www.w3.org/TR/mediacapture-streams/#dom-constraindouble -/// [constrain_ulong]: https://www.w3.org/TR/mediacapture-streams/#dom-constrainulong -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/ -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(untagged))] -pub enum ValueRangeConstraint { - /// A bare-valued media track constraint. - Bare(T), - /// A fully-qualified media track constraint. - Constraint(ResolvedValueRangeConstraint), -} - -impl Default for ValueRangeConstraint { - fn default() -> Self { - Self::Constraint(Default::default()) - } -} - -impl From for ValueRangeConstraint { - fn from(bare: T) -> Self { - Self::Bare(bare) - } -} - -impl From> for ValueRangeConstraint { - fn from(constraint: ResolvedValueRangeConstraint) -> Self { - Self::Constraint(constraint) - } -} - -impl ValueRangeConstraint -where - T: Clone, -{ - /// Returns a resolved representation of the constraint - /// with bare values resolved to fully-qualified constraints. - pub fn to_resolved( - &self, - strategy: MediaTrackConstraintResolutionStrategy, - ) -> ResolvedValueRangeConstraint { - self.clone().into_resolved(strategy) - } - - /// Consumes the constraint, returning a resolved representation of the - /// constraint with bare values resolved to fully-qualified constraints. - pub fn into_resolved( - self, - strategy: MediaTrackConstraintResolutionStrategy, - ) -> ResolvedValueRangeConstraint { - match self { - Self::Bare(bare) => match strategy { - MediaTrackConstraintResolutionStrategy::BareToIdeal => { - ResolvedValueRangeConstraint::default().ideal(bare) - } - MediaTrackConstraintResolutionStrategy::BareToExact => { - ResolvedValueRangeConstraint::default().exact(bare) - } - }, - Self::Constraint(constraint) => constraint, - } - } -} - -impl ValueRangeConstraint { - /// Returns `true` if `self` is empty, otherwise `false`. - pub fn is_empty(&self) -> bool { - match self { - Self::Bare(_) => false, - Self::Constraint(constraint) => constraint.is_empty(), - } - } -} - -/// A constraint specifying a range of accepted values. -/// -/// Corresponding W3C spec types as per ["Media Capture and Streams"][spec]: -/// - `ConstrainDouble` => `ResolvedValueRangeConstraint` -/// - `ConstrainULong` => `ResolvedValueRangeConstraint` -/// -/// [spec]: https://www.w3.org/TR/mediacapture-streams -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct ResolvedValueRangeConstraint { - /// The minimum legal value of this property. - /// - /// This is a required value. - #[cfg_attr( - feature = "serde", - serde(skip_serializing_if = "core::option::Option::is_none") - )] - pub min: Option, - /// The maximum legal value of this property. - /// - /// This is a required value. - #[cfg_attr( - feature = "serde", - serde(skip_serializing_if = "core::option::Option::is_none") - )] - pub max: Option, - /// The exact required value for this property. - /// - /// This is a required value. - #[cfg_attr( - feature = "serde", - serde(skip_serializing_if = "core::option::Option::is_none") - )] - pub exact: Option, - /// The ideal (target) value for this property. - /// - /// This is an optional value. - #[cfg_attr( - feature = "serde", - serde(skip_serializing_if = "core::option::Option::is_none") - )] - pub ideal: Option, -} - -impl ResolvedValueRangeConstraint { - /// Consumes `self`, returning a corresponding constraint - /// with the exact required value set to `exact`. - #[inline] - pub fn exact(mut self, exact: U) -> Self - where - Option: From, - { - self.exact = exact.into(); - self - } - - /// Consumes `self`, returning a corresponding constraint - /// with the ideal required value set to `ideal`. - #[inline] - pub fn ideal(mut self, ideal: U) -> Self - where - Option: From, - { - self.ideal = ideal.into(); - self - } - - /// Consumes `self`, returning a corresponding constraint - /// with the minimum required value set to `min`. - #[inline] - pub fn min(mut self, min: U) -> Self - where - Option: From, - { - self.min = min.into(); - self - } - - /// Consumes `self`, returning a corresponding constraint - /// with the maximum required value set to `max`. - #[inline] - pub fn max(mut self, max: U) -> Self - where - Option: From, - { - self.max = max.into(); - self - } - - /// Returns `true` if `value.is_some()` is `true` for any of its required values, - /// otherwise `false`. - pub fn is_required(&self) -> bool { - self.min.is_some() || self.max.is_some() || self.exact.is_some() - } - - /// Returns `true` if `value.is_none()` is `true` for all of its values, - /// otherwise `false`. - pub fn is_empty(&self) -> bool { - self.min.is_none() && self.max.is_none() && self.exact.is_none() && self.ideal.is_none() - } - - /// Returns a corresponding constraint containing only required values. - pub fn to_required_only(&self) -> Self - where - T: Clone, - { - self.clone().into_required_only() - } - - /// Consumes `self, returning a corresponding constraint - /// containing only required values. - pub fn into_required_only(self) -> Self { - Self { - min: self.min, - max: self.max, - exact: self.exact, - ideal: None, - } - } -} - -impl Default for ResolvedValueRangeConstraint { - #[inline] - fn default() -> Self { - Self { - min: None, - max: None, - exact: None, - ideal: None, - } - } -} - -impl std::fmt::Display for ResolvedValueRangeConstraint -where - T: std::fmt::Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut is_first = true; - f.write_str("(")?; - if let Some(exact) = &self.exact { - f.write_fmt(format_args!("x == {exact:?}"))?; - is_first = false; - } else if let (Some(min), Some(max)) = (&self.min, &self.max) { - f.write_fmt(format_args!("{min:?} <= x <= {max:?}"))?; - is_first = false; - } else if let Some(min) = &self.min { - f.write_fmt(format_args!("{min:?} <= x"))?; - is_first = false; - } else if let Some(max) = &self.max { - f.write_fmt(format_args!("x <= {max:?}"))?; - is_first = false; - } - if let Some(ideal) = &self.ideal { - if !is_first { - f.write_str(" && ")?; - } - f.write_fmt(format_args!("x ~= {ideal:?}"))?; - is_first = false; - } - if is_first { - f.write_str("")?; - } - f.write_str(")")?; - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn to_string() { - let scenarios = [ - (ResolvedValueRangeConstraint::default(), "()"), - (ResolvedValueRangeConstraint::default().exact(1), "(x == 1)"), - (ResolvedValueRangeConstraint::default().ideal(2), "(x ~= 2)"), - ( - ResolvedValueRangeConstraint::default().exact(1).ideal(2), - "(x == 1 && x ~= 2)", - ), - ]; - - for (constraint, expected) in scenarios { - let actual = constraint.to_string(); - - assert_eq!(actual, expected); - } - } - - #[test] - fn is_required() { - for min_is_some in [false, true] { - // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)` - // once MSRV has passed 1.62.0: - let min = if min_is_some { Some(1) } else { None }; - for max_is_some in [false, true] { - // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)` - // once MSRV has passed 1.62.0: - let max = if max_is_some { Some(2) } else { None }; - for exact_is_some in [false, true] { - // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)` - // once MSRV has passed 1.62.0: - let exact = if exact_is_some { Some(3) } else { None }; - for ideal_is_some in [false, true] { - // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)` - // once MSRV has passed 1.62.0: - let ideal = if ideal_is_some { Some(4) } else { None }; - - let constraint = ResolvedValueRangeConstraint:: { - min, - max, - exact, - ideal, - }; - - let actual = constraint.is_required(); - let expected = min_is_some || max_is_some || exact_is_some; - - assert_eq!(actual, expected); - } - } - } - } - } - - mod is_empty { - use super::*; - - #[test] - fn bare() { - let constraint = ValueRangeConstraint::Bare(42); - - assert!(!constraint.is_empty()); - } - - #[test] - fn constraint() { - for min_is_some in [false, true] { - // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)` - // once MSRV has passed 1.62.0: - let min = if min_is_some { Some(1) } else { None }; - for max_is_some in [false, true] { - // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)` - // once MSRV has passed 1.62.0: - let max = if max_is_some { Some(2) } else { None }; - for exact_is_some in [false, true] { - // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)` - // once MSRV has passed 1.62.0: - let exact = if exact_is_some { Some(3) } else { None }; - for ideal_is_some in [false, true] { - // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)` - // once MSRV has passed 1.62.0: - let ideal = if ideal_is_some { Some(4) } else { None }; - - let constraint = ResolvedValueRangeConstraint:: { - min, - max, - exact, - ideal, - }; - - let actual = constraint.is_empty(); - let expected = - !(min_is_some || max_is_some || exact_is_some || ideal_is_some); - - assert_eq!(actual, expected); - } - } - } - } - } - } -} - -#[test] -fn resolve_to_advanced() { - let constraints = [ - ValueRangeConstraint::Bare(42), - ValueRangeConstraint::Constraint(ResolvedValueRangeConstraint::default().exact(42)), - ]; - let strategy = MediaTrackConstraintResolutionStrategy::BareToExact; - - for constraint in constraints { - let actuals = [ - constraint.to_resolved(strategy), - constraint.into_resolved(strategy), - ]; - - let expected = ResolvedValueRangeConstraint::default().exact(42); - - for actual in actuals { - assert_eq!(actual, expected); - } - } -} - -#[test] -fn resolve_to_basic() { - let constraints = [ - ValueRangeConstraint::Bare(42), - ValueRangeConstraint::Constraint(ResolvedValueRangeConstraint::default().ideal(42)), - ]; - let strategy = MediaTrackConstraintResolutionStrategy::BareToIdeal; - - for constraint in constraints { - let actuals = [ - constraint.to_resolved(strategy), - constraint.into_resolved(strategy), - ]; - - let expected = ResolvedValueRangeConstraint::default().ideal(42); - - for actual in actuals { - assert_eq!(actual, expected); - } - } -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use super::*; - use crate::macros::test_serde_symmetry; - - macro_rules! test_serde { - ($t:ty => { - value: $value:expr - }) => { - type Subject = ValueRangeConstraint<$t>; - - #[test] - fn default() { - let subject = Subject::default(); - let json = serde_json::json!({}); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn bare() { - let subject = Subject::Bare($value.to_owned()); - let json = serde_json::json!($value); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn min_constraint() { - let subject = Subject::Constraint(ResolvedValueRangeConstraint::default().min($value.to_owned())); - let json = serde_json::json!({ - "min": $value, - }); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn max_constraint() { - let subject = Subject::Constraint(ResolvedValueRangeConstraint::default().max($value.to_owned())); - let json = serde_json::json!({ - "max": $value, - }); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn exact_constraint() { - let subject = Subject::Constraint(ResolvedValueRangeConstraint::default().exact($value.to_owned())); - let json = serde_json::json!({ - "exact": $value, - }); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn ideal_constraint() { - let subject = Subject::Constraint(ResolvedValueRangeConstraint::default().ideal($value.to_owned())); - let json = serde_json::json!({ - "ideal": $value, - }); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn full_constraint() { - let subject = Subject::Constraint(ResolvedValueRangeConstraint::default().min($value.to_owned()).max($value.to_owned()).exact($value.to_owned()).ideal($value.to_owned())); - let json = serde_json::json!({ - "min": $value, - "max": $value, - "exact": $value, - "ideal": $value, - }); - - test_serde_symmetry!(subject: subject, json: json); - } - }; - } - - mod f64 { - use super::*; - - test_serde!(f64 => { - value: 42.0 - }); - } - - mod u64 { - use super::*; - - test_serde!(u64 => { - value: 42 - }); - } -} diff --git a/constraints/src/constraint/value_sequence.rs b/constraints/src/constraint/value_sequence.rs deleted file mode 100644 index b3d95517c..000000000 --- a/constraints/src/constraint/value_sequence.rs +++ /dev/null @@ -1,440 +0,0 @@ -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use crate::MediaTrackConstraintResolutionStrategy; - -/// A bare value or constraint specifying a sequence of accepted values. -/// -/// # W3C Spec Compliance -/// -/// There exists no direct corresponding type in the -/// W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec, -/// since the `ValueConstraint` type aims to be a generalization over -/// multiple types in the spec. -/// -/// | Rust | W3C | -/// | ---------------------------------------- | -------------------------------------------- | -/// | `ValueSequenceConstraint` | [`ConstrainDOMString`][constrain_dom_string] | -/// -/// [constrain_dom_string]: https://www.w3.org/TR/mediacapture-streams/#dom-constraindomstring -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/ -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(untagged))] -pub enum ValueSequenceConstraint { - /// A bare-valued media track constraint. - Bare(Vec), - /// A fully-qualified media track constraint. - Constraint(ResolvedValueSequenceConstraint), -} - -impl Default for ValueSequenceConstraint { - fn default() -> Self { - Self::Constraint(Default::default()) - } -} - -impl From for ValueSequenceConstraint { - fn from(bare: T) -> Self { - Self::Bare(vec![bare]) - } -} - -impl From> for ValueSequenceConstraint { - fn from(bare: Vec) -> Self { - Self::Bare(bare) - } -} - -impl From> for ValueSequenceConstraint { - fn from(constraint: ResolvedValueSequenceConstraint) -> Self { - Self::Constraint(constraint) - } -} - -impl ValueSequenceConstraint -where - T: Clone, -{ - /// Returns a resolved representation of the constraint - /// with bare values resolved to fully-qualified constraints. - pub fn to_resolved( - &self, - strategy: MediaTrackConstraintResolutionStrategy, - ) -> ResolvedValueSequenceConstraint { - self.clone().into_resolved(strategy) - } - - /// Consumes the constraint, returning a resolved representation of the - /// constraint with bare values resolved to fully-qualified constraints. - pub fn into_resolved( - self, - strategy: MediaTrackConstraintResolutionStrategy, - ) -> ResolvedValueSequenceConstraint { - match self { - Self::Bare(bare) => match strategy { - MediaTrackConstraintResolutionStrategy::BareToIdeal => { - ResolvedValueSequenceConstraint::default().ideal(bare) - } - MediaTrackConstraintResolutionStrategy::BareToExact => { - ResolvedValueSequenceConstraint::default().exact(bare) - } - }, - Self::Constraint(constraint) => constraint, - } - } -} - -impl ValueSequenceConstraint { - /// Returns `true` if `self` is empty, otherwise `false`. - pub fn is_empty(&self) -> bool { - match self { - Self::Bare(bare) => bare.is_empty(), - Self::Constraint(constraint) => constraint.is_empty(), - } - } -} - -/// A constraint specifying a sequence of accepted values. -/// -/// # W3C Spec Compliance -/// -/// There exists no direct corresponding type in the -/// W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec, -/// since the `ValueSequenceConstraint` type aims to be a -/// generalization over multiple types in the W3C spec: -/// -/// | Rust | W3C | -/// | --------------------------------- | ----------------------------------------------------------------- | -/// | `ResolvedValueSequenceConstraint` | [`ConstrainDOMStringParameters`][constrain_dom_string_parameters] | -/// -/// [constrain_dom_string_parameters]: https://www.w3.org/TR/mediacapture-streams/#dom-constraindomstringparameters -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/ -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct ResolvedValueSequenceConstraint { - /// The exact required value for this property. - /// - /// This is a required value. - #[cfg_attr( - feature = "serde", - serde(skip_serializing_if = "core::option::Option::is_none") - )] - pub exact: Option>, - /// The ideal (target) value for this property. - /// - /// This is an optional value. - #[cfg_attr( - feature = "serde", - serde(skip_serializing_if = "core::option::Option::is_none") - )] - pub ideal: Option>, -} - -impl ResolvedValueSequenceConstraint { - /// Consumes `self`, returning a corresponding constraint - /// with the exact required value set to `exact`. - #[inline] - pub fn exact(mut self, exact: U) -> Self - where - Option>: From, - { - self.exact = exact.into(); - self - } - - /// Consumes `self`, returning a corresponding constraint - /// with the ideal required value set to `ideal`. - #[inline] - pub fn ideal(mut self, ideal: U) -> Self - where - Option>: From, - { - self.ideal = ideal.into(); - self - } - - /// Returns `true` if `value.is_some()` is `true` for any of its required values, - /// otherwise `false`. - pub fn is_required(&self) -> bool { - self.exact.is_some() - } - - /// Returns `true` if `value.is_none()` is `true` for all of its values, - /// otherwise `false`. - pub fn is_empty(&self) -> bool { - let exact_is_empty = self.exact.as_ref().map_or(true, Vec::is_empty); - let ideal_is_empty = self.ideal.as_ref().map_or(true, Vec::is_empty); - exact_is_empty && ideal_is_empty - } - - /// Returns a corresponding constraint containing only required values. - pub fn to_required_only(&self) -> Self - where - T: Clone, - { - self.clone().into_required_only() - } - - /// Consumes `self, returning a corresponding constraint - /// containing only required values. - pub fn into_required_only(self) -> Self { - Self { - exact: self.exact, - ideal: None, - } - } -} - -impl Default for ResolvedValueSequenceConstraint { - fn default() -> Self { - Self { - exact: None, - ideal: None, - } - } -} - -impl std::fmt::Display for ResolvedValueSequenceConstraint -where - T: std::fmt::Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut is_first = true; - f.write_str("(")?; - if let Some(ref exact) = &self.exact { - f.write_fmt(format_args!("x == {exact:?}"))?; - is_first = false; - } - if let Some(ref ideal) = &self.ideal { - if !is_first { - f.write_str(" && ")?; - } - f.write_fmt(format_args!("x ~= {ideal:?}"))?; - is_first = false; - } - if is_first { - f.write_str("")?; - } - f.write_str(")")?; - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn to_string() { - let scenarios = [ - (ResolvedValueSequenceConstraint::default(), "()"), - ( - ResolvedValueSequenceConstraint::default().exact(vec![1, 2]), - "(x == [1, 2])", - ), - ( - ResolvedValueSequenceConstraint::default().ideal(vec![2, 3]), - "(x ~= [2, 3])", - ), - ( - ResolvedValueSequenceConstraint::default() - .exact(vec![1, 2]) - .ideal(vec![2, 3]), - "(x == [1, 2] && x ~= [2, 3])", - ), - ]; - - for (constraint, expected) in scenarios { - let actual = constraint.to_string(); - - assert_eq!(actual, expected); - } - } - - #[test] - fn is_required() { - let scenarios = [ - (ResolvedValueSequenceConstraint::default(), false), - ( - ResolvedValueSequenceConstraint::default().exact(vec![true]), - true, - ), - ( - ResolvedValueSequenceConstraint::default().ideal(vec![true]), - false, - ), - ( - ResolvedValueSequenceConstraint::default() - .exact(vec![true]) - .ideal(vec![true]), - true, - ), - ]; - - for (constraint, expected) in scenarios { - let actual = constraint.is_required(); - - assert_eq!(actual, expected); - } - } - - mod is_empty { - use super::*; - - #[test] - fn bare() { - let constraint = ValueSequenceConstraint::Bare(vec![true]); - - assert!(!constraint.is_empty()); - } - - #[test] - fn constraint() { - let scenarios = [ - (ResolvedValueSequenceConstraint::default(), true), - ( - ResolvedValueSequenceConstraint::default().exact(vec![true]), - false, - ), - ( - ResolvedValueSequenceConstraint::default().ideal(vec![true]), - false, - ), - ( - ResolvedValueSequenceConstraint::default() - .exact(vec![true]) - .ideal(vec![true]), - false, - ), - ]; - - for (constraint, expected) in scenarios { - let constraint = ValueSequenceConstraint::::Constraint(constraint); - - let actual = constraint.is_empty(); - - assert_eq!(actual, expected); - } - } - } - - #[test] - fn resolve_to_advanced() { - let constraints = [ - ValueSequenceConstraint::Bare(vec![true]), - ValueSequenceConstraint::Constraint( - ResolvedValueSequenceConstraint::default().exact(vec![true]), - ), - ]; - let strategy = MediaTrackConstraintResolutionStrategy::BareToExact; - - for constraint in constraints { - let actuals = [ - constraint.to_resolved(strategy), - constraint.into_resolved(strategy), - ]; - - let expected = ResolvedValueSequenceConstraint::default().exact(vec![true]); - - for actual in actuals { - assert_eq!(actual, expected); - } - } - } - - #[test] - fn resolve_to_basic() { - let constraints = [ - ValueSequenceConstraint::Bare(vec![true]), - ValueSequenceConstraint::Constraint( - ResolvedValueSequenceConstraint::default().ideal(vec![true]), - ), - ]; - let strategy = MediaTrackConstraintResolutionStrategy::BareToIdeal; - - for constraint in constraints { - let actuals = [ - constraint.to_resolved(strategy), - constraint.into_resolved(strategy), - ]; - - let expected = ResolvedValueSequenceConstraint::default().ideal(vec![true]); - - for actual in actuals { - assert_eq!(actual, expected); - } - } - } -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use super::*; - use crate::macros::test_serde_symmetry; - - macro_rules! test_serde { - ($t:ty => { - values: [$($values:expr),*] - }) => { - type Subject = ValueSequenceConstraint<$t>; - - #[test] - fn default() { - let subject = Subject::default(); - let json = serde_json::json!({}); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn bare() { - let subject = Subject::Bare(vec![$($values.to_owned()),*].into()); - let json = serde_json::json!([$($values),*]); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn exact_constraint() { - let subject = Subject::Constraint(ResolvedValueSequenceConstraint::default().exact(vec![$($values.to_owned()),*])); - let json = serde_json::json!({ - "exact": [$($values),*], - }); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn ideal_constraint() { - let subject = Subject::Constraint(ResolvedValueSequenceConstraint::default().ideal(vec![$($values.to_owned()),*])); - let json = serde_json::json!({ - "ideal": [$($values),*], - }); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn full_constraint() { - let subject = Subject::Constraint(ResolvedValueSequenceConstraint::default().exact(vec![$($values.to_owned()),*]).ideal(vec![$($values.to_owned()),*])); - let json = serde_json::json!({ - "exact": [$($values),*], - "ideal": [$($values),*], - }); - - test_serde_symmetry!(subject: subject, json: json); - } - }; - } - - mod string { - use super::*; - - test_serde!(String => { - values: ["VALUE_0", "VALUE_1"] - }); - } -} diff --git a/constraints/src/constraints.rs b/constraints/src/constraints.rs deleted file mode 100644 index 8b89b909e..000000000 --- a/constraints/src/constraints.rs +++ /dev/null @@ -1,22 +0,0 @@ -mod advanced; -mod constraint_set; -mod mandatory; -mod stream; -mod track; - -pub use self::advanced::{ - AdvancedMediaTrackConstraints, ResolvedAdvancedMediaTrackConstraints, - SanitizedAdvancedMediaTrackConstraints, -}; -pub use self::constraint_set::{ - MediaTrackConstraintSet, ResolvedMediaTrackConstraintSet, SanitizedMediaTrackConstraintSet, -}; -pub use self::mandatory::{ - MandatoryMediaTrackConstraints, ResolvedMandatoryMediaTrackConstraints, - SanitizedMandatoryMediaTrackConstraints, -}; -pub use self::stream::MediaStreamConstraints; -pub use self::track::{ - BoolOrMediaTrackConstraints, MediaTrackConstraints, ResolvedMediaTrackConstraints, - SanitizedMediaTrackConstraints, -}; diff --git a/constraints/src/constraints/advanced.rs b/constraints/src/constraints/advanced.rs deleted file mode 100644 index d6e893199..000000000 --- a/constraints/src/constraints/advanced.rs +++ /dev/null @@ -1,191 +0,0 @@ -use std::iter::FromIterator; -use std::ops::{Deref, DerefMut}; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use super::constraint_set::GenericMediaTrackConstraintSet; -use crate::{ - MediaTrackConstraint, MediaTrackConstraintResolutionStrategy, MediaTrackSupportedConstraints, - ResolvedMediaTrackConstraint, SanitizedMediaTrackConstraint, -}; - -/// Advanced media track constraints that contain sets of either bare values or constraints. -pub type AdvancedMediaTrackConstraints = GenericAdvancedMediaTrackConstraints; - -/// Advanced media track constraints that contain sets of constraints (both, empty and non-empty). -pub type ResolvedAdvancedMediaTrackConstraints = - GenericAdvancedMediaTrackConstraints; - -/// Advanced media track constraints that contain sets of only non-empty constraints. -pub type SanitizedAdvancedMediaTrackConstraints = - GenericAdvancedMediaTrackConstraints; - -/// The list of advanced constraint sets for a [`MediaStreamTrack`][media_stream_track] object. -/// -/// # W3C Spec Compliance -/// -/// Corresponds to [`ResolvedMediaTrackConstraints.advanced`][media_track_constraints_advanced] -/// from the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -/// [media_track_constraints_advanced]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackconstraints-advanced -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/ -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(transparent))] -pub struct GenericAdvancedMediaTrackConstraints(Vec>); - -impl GenericAdvancedMediaTrackConstraints { - pub fn new(constraints: Vec>) -> Self { - Self(constraints) - } - - pub fn into_inner(self) -> Vec> { - self.0 - } -} - -impl Deref for GenericAdvancedMediaTrackConstraints { - type Target = Vec>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for GenericAdvancedMediaTrackConstraints { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Default for GenericAdvancedMediaTrackConstraints { - fn default() -> Self { - Self(Default::default()) - } -} - -impl FromIterator> - for GenericAdvancedMediaTrackConstraints -{ - fn from_iter(iter: I) -> Self - where - I: IntoIterator>, - { - Self::new(iter.into_iter().collect()) - } -} - -impl IntoIterator for GenericAdvancedMediaTrackConstraints { - type Item = GenericMediaTrackConstraintSet; - type IntoIter = std::vec::IntoIter>; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl AdvancedMediaTrackConstraints { - pub fn to_resolved(&self) -> ResolvedAdvancedMediaTrackConstraints { - self.clone().into_resolved() - } - - pub fn into_resolved(self) -> ResolvedAdvancedMediaTrackConstraints { - let strategy = MediaTrackConstraintResolutionStrategy::BareToExact; - ResolvedAdvancedMediaTrackConstraints::from_iter( - self.into_iter() - .map(|constraint_set| constraint_set.into_resolved(strategy)), - ) - } -} - -impl ResolvedAdvancedMediaTrackConstraints { - pub fn to_sanitized( - &self, - supported_constraints: &MediaTrackSupportedConstraints, - ) -> SanitizedAdvancedMediaTrackConstraints { - self.clone().into_sanitized(supported_constraints) - } - - pub fn into_sanitized( - self, - supported_constraints: &MediaTrackSupportedConstraints, - ) -> SanitizedAdvancedMediaTrackConstraints { - SanitizedAdvancedMediaTrackConstraints::from_iter( - self.into_iter() - .map(|constraint_set| constraint_set.into_sanitized(supported_constraints)) - .filter(|constraint_set| !constraint_set.is_empty()), - ) - } -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use super::*; - use crate::property::all::name::*; - use crate::MediaTrackConstraintSet; - - #[test] - fn serialize_default() { - let advanced = AdvancedMediaTrackConstraints::default(); - let actual = serde_json::to_value(advanced).unwrap(); - let expected = serde_json::json!([]); - - assert_eq!(actual, expected); - } - - #[test] - fn deserialize_default() { - let json = serde_json::json!([]); - let actual: AdvancedMediaTrackConstraints = serde_json::from_value(json).unwrap(); - let expected = AdvancedMediaTrackConstraints::default(); - - assert_eq!(actual, expected); - } - - #[test] - fn serialize() { - let advanced = - AdvancedMediaTrackConstraints::new(vec![MediaTrackConstraintSet::from_iter([ - (&DEVICE_ID, "device-id".into()), - (&AUTO_GAIN_CONTROL, true.into()), - (&CHANNEL_COUNT, 2.into()), - (&LATENCY, 0.123.into()), - ])]); - let actual = serde_json::to_value(advanced).unwrap(); - let expected = serde_json::json!([ - { - "deviceId": "device-id".to_owned(), - "autoGainControl": true, - "channelCount": 2, - "latency": 0.123, - } - ]); - - assert_eq!(actual, expected); - } - - #[test] - fn deserialize() { - let json = serde_json::json!([ - { - "deviceId": "device-id".to_owned(), - "autoGainControl": true, - "channelCount": 2, - "latency": 0.123, - } - ]); - let actual: AdvancedMediaTrackConstraints = serde_json::from_value(json).unwrap(); - let expected = - AdvancedMediaTrackConstraints::new(vec![MediaTrackConstraintSet::from_iter([ - (&DEVICE_ID, "device-id".into()), - (&AUTO_GAIN_CONTROL, true.into()), - (&CHANNEL_COUNT, 2.into()), - (&LATENCY, 0.123.into()), - ])]); - - assert_eq!(actual, expected); - } -} diff --git a/constraints/src/constraints/constraint_set.rs b/constraints/src/constraints/constraint_set.rs deleted file mode 100644 index 89680963b..000000000 --- a/constraints/src/constraints/constraint_set.rs +++ /dev/null @@ -1,200 +0,0 @@ -use std::iter::FromIterator; -use std::ops::{Deref, DerefMut}; - -use indexmap::IndexMap; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use crate::constraint::SanitizedMediaTrackConstraint; -use crate::{ - MediaTrackConstraint, MediaTrackConstraintResolutionStrategy, MediaTrackProperty, - MediaTrackSupportedConstraints, ResolvedMediaTrackConstraint, -}; - -/// Media track constraint set that contains either bare values or constraints. -pub type MediaTrackConstraintSet = GenericMediaTrackConstraintSet; - -/// Media track constraint set that contains only constraints (both, empty and non-empty). -pub type ResolvedMediaTrackConstraintSet = - GenericMediaTrackConstraintSet; - -/// Media track constraint set that contains only non-empty constraints. -pub type SanitizedMediaTrackConstraintSet = - GenericMediaTrackConstraintSet; - -/// The set of constraints for a [`MediaStreamTrack`][media_stream_track] object. -/// -/// # W3C Spec Compliance -/// -/// Corresponds to [`ResolvedMediaTrackConstraintSet`][media_track_constraint_set] -/// from the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -/// [media_track_constraint_set]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackconstraintset -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/ -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(transparent))] -pub struct GenericMediaTrackConstraintSet(IndexMap); - -impl GenericMediaTrackConstraintSet { - pub fn new(constraint_set: IndexMap) -> Self { - Self(constraint_set) - } - - pub fn into_inner(self) -> IndexMap { - self.0 - } -} - -impl Deref for GenericMediaTrackConstraintSet { - type Target = IndexMap; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for GenericMediaTrackConstraintSet { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Default for GenericMediaTrackConstraintSet { - fn default() -> Self { - Self(IndexMap::new()) - } -} - -impl FromIterator<(U, T)> for GenericMediaTrackConstraintSet -where - U: Into, -{ - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - Self::new(iter.into_iter().map(|(k, v)| (k.into(), v)).collect()) - } -} - -impl IntoIterator for GenericMediaTrackConstraintSet { - type Item = (MediaTrackProperty, T); - type IntoIter = indexmap::map::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl MediaTrackConstraintSet { - pub fn to_resolved( - &self, - strategy: MediaTrackConstraintResolutionStrategy, - ) -> ResolvedMediaTrackConstraintSet { - self.clone().into_resolved(strategy) - } - - pub fn into_resolved( - self, - strategy: MediaTrackConstraintResolutionStrategy, - ) -> ResolvedMediaTrackConstraintSet { - ResolvedMediaTrackConstraintSet::new( - self.into_iter() - .map(|(property, constraint)| (property, constraint.into_resolved(strategy))) - .collect(), - ) - } -} - -impl ResolvedMediaTrackConstraintSet { - pub fn to_sanitized( - &self, - supported_constraints: &MediaTrackSupportedConstraints, - ) -> SanitizedMediaTrackConstraintSet { - self.clone().into_sanitized(supported_constraints) - } - - pub fn into_sanitized( - self, - supported_constraints: &MediaTrackSupportedConstraints, - ) -> SanitizedMediaTrackConstraintSet { - let index_map: IndexMap = self - .into_iter() - .filter_map(|(property, constraint)| { - if supported_constraints.contains(&property) { - constraint - .into_sanitized() - .map(|constraint| (property, constraint)) - } else { - None - } - }) - .collect(); - SanitizedMediaTrackConstraintSet::new(index_map) - } -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use super::*; - use crate::property::all::name::*; - - #[test] - fn serialize_default() { - let constraint_set = MediaTrackConstraintSet::default(); - let actual = serde_json::to_value(constraint_set).unwrap(); - let expected = serde_json::json!({}); - - assert_eq!(actual, expected); - } - - #[test] - fn deserialize_default() { - let json = serde_json::json!({}); - let actual: MediaTrackConstraintSet = serde_json::from_value(json).unwrap(); - let expected = MediaTrackConstraintSet::default(); - - assert_eq!(actual, expected); - } - - #[test] - fn serialize() { - let constraint_set = MediaTrackConstraintSet::from_iter([ - (&DEVICE_ID, "device-id".into()), - (&AUTO_GAIN_CONTROL, true.into()), - (&CHANNEL_COUNT, 2.into()), - (&LATENCY, 0.123.into()), - ]); - let actual = serde_json::to_value(constraint_set).unwrap(); - let expected = serde_json::json!({ - "deviceId": "device-id".to_owned(), - "autoGainControl": true, - "channelCount": 2, - "latency": 0.123, - }); - - assert_eq!(actual, expected); - } - - #[test] - fn deserialize() { - let json = serde_json::json!({ - "deviceId": "device-id".to_owned(), - "autoGainControl": true, - "channelCount": 2, - "latency": 0.123, - }); - let actual: MediaTrackConstraintSet = serde_json::from_value(json).unwrap(); - let expected = MediaTrackConstraintSet::from_iter([ - (&DEVICE_ID, "device-id".into()), - (&AUTO_GAIN_CONTROL, true.into()), - (&CHANNEL_COUNT, 2.into()), - (&LATENCY, 0.123.into()), - ]); - - assert_eq!(actual, expected); - } -} diff --git a/constraints/src/constraints/mandatory.rs b/constraints/src/constraints/mandatory.rs deleted file mode 100644 index 95dd8fc24..000000000 --- a/constraints/src/constraints/mandatory.rs +++ /dev/null @@ -1,321 +0,0 @@ -use std::iter::FromIterator; -use std::ops::{Deref, DerefMut}; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use super::constraint_set::GenericMediaTrackConstraintSet; -use crate::{ - MediaTrackConstraint, MediaTrackConstraintResolutionStrategy, MediaTrackProperty, - MediaTrackSupportedConstraints, ResolvedMediaTrackConstraint, SanitizedMediaTrackConstraint, -}; - -/// The list of mandatory constraint sets for a [`MediaStreamTrack`][media_stream_track] object. -/// -/// # W3C Spec Compliance -/// -/// Corresponds to [`ResolvedMediaTrackConstraints.mandatory`][media_track_constraints_mandatory] -/// from the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// Unlike `ResolvedMandatoryMediaTrackConstraints` this type may contain constraints with bare values. -/// -/// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -/// [media_track_constraints_mandatory]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackconstraints-mandatory -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/ -pub type MandatoryMediaTrackConstraints = - GenericMandatoryMediaTrackConstraints; - -/// The list of mandatory constraint sets for a [`MediaStreamTrack`][media_stream_track] object. -/// -/// # W3C Spec Compliance -/// -/// Corresponds to [`ResolvedMediaTrackConstraintSet`][media_track_constraints_mandatory] -/// from the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// Unlike `MandatoryMediaTrackConstraints` this type does not contain constraints -/// with bare values, but has them resolved to full constraints instead. -/// -/// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -/// [media_track_constraints_mandatory]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackconstraints-mandatory -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/ -pub type ResolvedMandatoryMediaTrackConstraints = - GenericMandatoryMediaTrackConstraints; - -/// Set of mandatory media track constraints that contains only non-empty constraints. -pub type SanitizedMandatoryMediaTrackConstraints = - GenericMandatoryMediaTrackConstraints; - -/// The set of constraints for a [`MediaStreamTrack`][media_stream_track] object. -/// -/// # W3C Spec Compliance -/// -/// Corresponds to [`ResolvedMediaTrackConstraintSet`][media_track_constraint_set] -/// from the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -/// [media_track_constraint_set]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackconstraintset -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/ -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(transparent))] -pub struct GenericMandatoryMediaTrackConstraints(GenericMediaTrackConstraintSet); - -impl GenericMandatoryMediaTrackConstraints { - pub fn new(constraints: GenericMediaTrackConstraintSet) -> Self { - Self(constraints) - } - - pub fn into_inner(self) -> GenericMediaTrackConstraintSet { - self.0 - } -} - -impl GenericMandatoryMediaTrackConstraints { - pub fn basic(&self) -> GenericMediaTrackConstraintSet { - self.basic_or_required(false) - } - - pub fn required(&self) -> GenericMediaTrackConstraintSet { - self.basic_or_required(true) - } - - fn basic_or_required( - &self, - required: bool, - ) -> GenericMediaTrackConstraintSet { - GenericMediaTrackConstraintSet::new( - self.0 - .iter() - .filter_map(|(property, constraint)| { - if constraint.is_required() == required { - Some((property.clone(), constraint.clone())) - } else { - None - } - }) - .collect(), - ) - } -} - -impl Deref for GenericMandatoryMediaTrackConstraints { - type Target = GenericMediaTrackConstraintSet; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for GenericMandatoryMediaTrackConstraints { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Default for GenericMandatoryMediaTrackConstraints { - fn default() -> Self { - Self(Default::default()) - } -} - -impl FromIterator<(U, T)> for GenericMandatoryMediaTrackConstraints -where - U: Into, -{ - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - Self::new(iter.into_iter().map(|(k, v)| (k.into(), v)).collect()) - } -} - -impl IntoIterator for GenericMandatoryMediaTrackConstraints { - type Item = (MediaTrackProperty, T); - type IntoIter = indexmap::map::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl MandatoryMediaTrackConstraints { - pub fn to_resolved(&self) -> ResolvedMandatoryMediaTrackConstraints { - self.clone().into_resolved() - } - - pub fn into_resolved(self) -> ResolvedMandatoryMediaTrackConstraints { - let strategy = MediaTrackConstraintResolutionStrategy::BareToIdeal; - ResolvedMandatoryMediaTrackConstraints::new(self.0.into_resolved(strategy)) - } -} - -impl ResolvedMandatoryMediaTrackConstraints { - pub fn to_sanitized( - &self, - supported_constraints: &MediaTrackSupportedConstraints, - ) -> SanitizedMandatoryMediaTrackConstraints { - self.clone().into_sanitized(supported_constraints) - } - - pub fn into_sanitized( - self, - supported_constraints: &MediaTrackSupportedConstraints, - ) -> SanitizedMandatoryMediaTrackConstraints { - SanitizedMandatoryMediaTrackConstraints::new(self.0.into_sanitized(supported_constraints)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::property::all::name::*; - use crate::{ - ResolvedMediaTrackConstraintSet, ResolvedValueConstraint, ResolvedValueRangeConstraint, - }; - - #[test] - fn basic() { - let mandatory = ResolvedMandatoryMediaTrackConstraints::new( - ResolvedMediaTrackConstraintSet::from_iter([ - ( - &DEVICE_ID, - ResolvedValueConstraint::default() - .exact("device-id".to_owned()) - .into(), - ), - ( - &AUTO_GAIN_CONTROL, - ResolvedValueConstraint::default().ideal(true).into(), - ), - ( - &CHANNEL_COUNT, - ResolvedValueRangeConstraint::default() - .exact(2) - .ideal(3) - .into(), - ), - ]), - ); - - let actual = mandatory.basic(); - let expected = ResolvedMediaTrackConstraintSet::from_iter([( - &AUTO_GAIN_CONTROL, - ResolvedValueConstraint::default().ideal(true).into(), - )]); - - assert_eq!(actual, expected); - } - - #[test] - fn required() { - let mandatory = ResolvedMandatoryMediaTrackConstraints::new( - ResolvedMediaTrackConstraintSet::from_iter([ - ( - &DEVICE_ID, - ResolvedValueConstraint::default() - .exact("device-id".to_owned()) - .into(), - ), - ( - &AUTO_GAIN_CONTROL, - ResolvedValueConstraint::default().ideal(true).into(), - ), - ( - &CHANNEL_COUNT, - ResolvedValueRangeConstraint::default() - .exact(2) - .ideal(3) - .into(), - ), - ]), - ); - - let actual = mandatory.required(); - let expected = ResolvedMediaTrackConstraintSet::from_iter([ - ( - &DEVICE_ID, - ResolvedValueConstraint::default() - .exact("device-id".to_owned()) - .into(), - ), - ( - &CHANNEL_COUNT, - ResolvedValueRangeConstraint::default() - .exact(2) - .ideal(3) - .into(), - ), - ]); - - assert_eq!(actual, expected); - } -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use super::*; - use crate::property::all::name::*; - use crate::MediaTrackConstraintSet; - - #[test] - fn serialize_default() { - let mandatory = MandatoryMediaTrackConstraints::default(); - let actual = serde_json::to_value(mandatory).unwrap(); - let expected = serde_json::json!({}); - - assert_eq!(actual, expected); - } - - #[test] - fn deserialize_default() { - let json = serde_json::json!({}); - let actual: MandatoryMediaTrackConstraints = serde_json::from_value(json).unwrap(); - let expected = MandatoryMediaTrackConstraints::default(); - - assert_eq!(actual, expected); - } - - #[test] - fn serialize() { - let mandatory = MandatoryMediaTrackConstraints::new(MediaTrackConstraintSet::from_iter([ - (&DEVICE_ID, "device-id".into()), - (&AUTO_GAIN_CONTROL, true.into()), - (&CHANNEL_COUNT, 2.into()), - (&LATENCY, 0.123.into()), - ])); - let actual = serde_json::to_value(mandatory).unwrap(); - let expected = serde_json::json!( - { - "deviceId": "device-id".to_owned(), - "autoGainControl": true, - "channelCount": 2, - "latency": 0.123, - } - ); - - assert_eq!(actual, expected); - } - - #[test] - fn deserialize() { - let json = serde_json::json!( - { - "deviceId": "device-id".to_owned(), - "autoGainControl": true, - "channelCount": 2, - "latency": 0.123, - } - ); - let actual: MandatoryMediaTrackConstraints = serde_json::from_value(json).unwrap(); - let expected = MandatoryMediaTrackConstraints::new(MediaTrackConstraintSet::from_iter([ - (&DEVICE_ID, "device-id".into()), - (&AUTO_GAIN_CONTROL, true.into()), - (&CHANNEL_COUNT, 2.into()), - (&LATENCY, 0.123.into()), - ])); - - assert_eq!(actual, expected); - } -} diff --git a/constraints/src/constraints/stream.rs b/constraints/src/constraints/stream.rs deleted file mode 100644 index c3268160f..000000000 --- a/constraints/src/constraints/stream.rs +++ /dev/null @@ -1,90 +0,0 @@ -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use super::track::GenericBoolOrMediaTrackConstraints; -use crate::MediaTrackConstraint; - -/// The constraints for a [`MediaStream`][media_stream] object. -/// -/// # W3C Spec Compliance -/// -/// Corresponds to [`MediaStreamConstraints`][media_stream_constraints] -/// from the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// [media_stream]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastream -/// [media_stream_constraints]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamconstraints -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/ -pub type MediaStreamConstraints = GenericMediaStreamConstraints; - -/// The constraints for a [`MediaStream`][media_stream] object. -/// -/// # W3C Spec Compliance -/// -/// Corresponds to [`MediaStreamConstraints`][media_stream_constraints] -/// from the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// [media_stream]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastream -/// [media_stream_constraints]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamconstraints -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/ -#[derive(Debug, Clone, Default, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct GenericMediaStreamConstraints { - #[cfg_attr(feature = "serde", serde(default))] - pub audio: GenericBoolOrMediaTrackConstraints, - #[cfg_attr(feature = "serde", serde(default))] - pub video: GenericBoolOrMediaTrackConstraints, -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod tests { - use std::iter::FromIterator; - - use super::*; - use crate::constraints::advanced::AdvancedMediaTrackConstraints; - use crate::constraints::mandatory::MandatoryMediaTrackConstraints; - use crate::constraints::track::{BoolOrMediaTrackConstraints, MediaTrackConstraints}; - use crate::macros::test_serde_symmetry; - use crate::property::all::name::*; - use crate::MediaTrackConstraintSet; - - type Subject = MediaStreamConstraints; - - #[test] - fn default() { - let subject = Subject::default(); - let json = serde_json::json!({ - "audio": false, - "video": false, - }); - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn customized() { - let subject = Subject { - audio: BoolOrMediaTrackConstraints::Constraints(MediaTrackConstraints { - mandatory: MandatoryMediaTrackConstraints::from_iter([ - (&DEVICE_ID, "microphone".into()), - (&CHANNEL_COUNT, 2.into()), - ]), - advanced: AdvancedMediaTrackConstraints::new(vec![ - MediaTrackConstraintSet::from_iter([(&LATENCY, 0.123.into())]), - ]), - }), - video: BoolOrMediaTrackConstraints::Bool(true), - }; - let json = serde_json::json!({ - "audio": { - "deviceId": "microphone", - "channelCount": 2_i64, - "advanced": [ - { "latency": 0.123_f64, } - ] - }, - "video": true, - }); - test_serde_symmetry!(subject: subject, json: json); - } -} diff --git a/constraints/src/constraints/track.rs b/constraints/src/constraints/track.rs deleted file mode 100644 index 425fd6c54..000000000 --- a/constraints/src/constraints/track.rs +++ /dev/null @@ -1,339 +0,0 @@ -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use super::advanced::GenericAdvancedMediaTrackConstraints; -use super::mandatory::GenericMandatoryMediaTrackConstraints; -use crate::constraint::SanitizedMediaTrackConstraint; -use crate::{MediaTrackConstraint, MediaTrackSupportedConstraints, ResolvedMediaTrackConstraint}; - -/// A boolean on/off flag or bare value or constraints for a [`MediaStreamTrack`][media_stream_track] object. -/// -/// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -pub type BoolOrMediaTrackConstraints = GenericBoolOrMediaTrackConstraints; - -/// A boolean on/off flag or constraints for a [`MediaStreamTrack`][media_stream_track] object. -/// -/// # W3C Spec Compliance -/// -/// There exists no direct corresponding type in the -/// W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec, -/// since the `BoolOrMediaTrackConstraints` type aims to be a -/// generalization over multiple types in the W3C spec: -/// -/// | Rust | W3C | -/// | ----------------------------- | -------------------------------------------------------------------------------------------------- | -/// | `BoolOrMediaTrackConstraints` | [`MediaStreamConstraints`][media_stream_constraints]'s [`video`][video] / [`audio`][audio] members | -/// -/// [media_stream_constraints]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamconstraints-video -/// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -/// [video]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamconstraints-video -/// [audio]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamconstraints-audio -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/ -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(untagged))] -pub enum GenericBoolOrMediaTrackConstraints { - /// Boolean track selector. - Bool(bool), - /// Constraints-based track selector. - Constraints(GenericMediaTrackConstraints), -} - -impl GenericBoolOrMediaTrackConstraints -where - T: Clone, -{ - pub fn to_constraints(&self) -> Option> { - self.clone().into_constraints() - } - - pub fn into_constraints(self) -> Option> { - match self { - Self::Bool(false) => None, - Self::Bool(true) => Some(GenericMediaTrackConstraints::default()), - Self::Constraints(constraints) => Some(constraints), - } - } -} - -impl Default for GenericBoolOrMediaTrackConstraints { - fn default() -> Self { - Self::Bool(false) - } -} - -impl From for GenericBoolOrMediaTrackConstraints { - fn from(flag: bool) -> Self { - Self::Bool(flag) - } -} - -impl From> for GenericBoolOrMediaTrackConstraints { - fn from(constraints: GenericMediaTrackConstraints) -> Self { - Self::Constraints(constraints) - } -} - -/// Media track constraints that contains either bare values or constraints. -pub type MediaTrackConstraints = GenericMediaTrackConstraints; - -/// Media track constraints that contains only constraints (both, empty and non-empty). -pub type ResolvedMediaTrackConstraints = GenericMediaTrackConstraints; - -/// Media track constraints that contains only non-empty constraints. -pub type SanitizedMediaTrackConstraints = - GenericMediaTrackConstraints; - -/// The constraints for a [`MediaStreamTrack`][media_stream_track] object. -/// -/// # W3C Spec Compliance -/// -/// Corresponds to [`MediaTrackConstraints`][media_track_constraints] -/// from the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -/// [media_track_constraints]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackconstraints -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/ -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct GenericMediaTrackConstraints { - /// Mandatory (i.e required or optional basic) constraints, as defined in the [spec][spec]. - /// - /// [spec]: https://www.w3.org/TR/mediacapture-streams/#dfn-constraint - #[cfg_attr(feature = "serde", serde(flatten))] - pub mandatory: GenericMandatoryMediaTrackConstraints, - - /// Advanced constraints, as defined in the [spec][spec]. - /// - /// [spec]: https://www.w3.org/TR/mediacapture-streams/#dfn-constraint - #[cfg_attr( - feature = "serde", - serde(default = "Default::default"), - serde(skip_serializing_if = "should_skip_advanced") - )] - pub advanced: GenericAdvancedMediaTrackConstraints, -} - -#[cfg(feature = "serde")] -fn should_skip_advanced(advanced: &GenericAdvancedMediaTrackConstraints) -> bool { - advanced.is_empty() -} - -impl Default for GenericMediaTrackConstraints { - fn default() -> Self { - Self { - mandatory: Default::default(), - advanced: Default::default(), - } - } -} - -impl MediaTrackConstraints { - pub fn to_resolved(&self) -> ResolvedMediaTrackConstraints { - self.clone().into_resolved() - } - - pub fn into_resolved(self) -> ResolvedMediaTrackConstraints { - let Self { - mandatory, - advanced, - } = self; - ResolvedMediaTrackConstraints { - mandatory: mandatory.into_resolved(), - advanced: advanced.into_resolved(), - } - } -} - -impl ResolvedMediaTrackConstraints { - pub fn to_sanitized( - &self, - supported_constraints: &MediaTrackSupportedConstraints, - ) -> SanitizedMediaTrackConstraints { - self.clone().into_sanitized(supported_constraints) - } - - pub fn into_sanitized( - self, - supported_constraints: &MediaTrackSupportedConstraints, - ) -> SanitizedMediaTrackConstraints { - let mandatory = self.mandatory.into_sanitized(supported_constraints); - let advanced = self.advanced.into_sanitized(supported_constraints); - SanitizedMediaTrackConstraints { - mandatory, - advanced, - } - } -} - -#[cfg(test)] -mod tests { - use std::iter::FromIterator; - - use super::*; - use crate::constraints::mandatory::MandatoryMediaTrackConstraints; - use crate::property::all::name::*; - use crate::{ - AdvancedMediaTrackConstraints, ResolvedAdvancedMediaTrackConstraints, - ResolvedMandatoryMediaTrackConstraints, ResolvedValueConstraint, - }; - - type Subject = BoolOrMediaTrackConstraints; - - #[test] - fn default() { - let actual = Subject::default(); - let expected = Subject::Bool(false); - - assert_eq!(actual, expected); - } - - mod from { - use super::*; - - #[test] - fn bool() { - for value in [false, true] { - let actual = Subject::from(value); - let expected = Subject::Bool(value); - - assert_eq!(actual, expected); - } - } - - #[test] - fn constraints() { - let constraints = GenericMediaTrackConstraints { - mandatory: MandatoryMediaTrackConstraints::from_iter([( - &DEVICE_ID, - "microphone".into(), - )]), - advanced: AdvancedMediaTrackConstraints::new(vec![]), - }; - - let actual = Subject::from(constraints.clone()); - let expected = Subject::Constraints(constraints); - - assert_eq!(actual, expected); - } - } - - mod to_constraints { - use super::*; - - #[test] - fn bool_false() { - let subject = Subject::Bool(false); - - let actual = subject.to_constraints(); - let expected = None; - - assert_eq!(actual, expected); - } - - #[test] - fn bool_true() { - let subject = Subject::Bool(true); - - let actual = subject.to_constraints(); - let expected = Some(GenericMediaTrackConstraints::default()); - - assert_eq!(actual, expected); - } - - #[test] - fn constraints() { - let constraints = GenericMediaTrackConstraints { - mandatory: MandatoryMediaTrackConstraints::from_iter([( - &DEVICE_ID, - "microphone".into(), - )]), - advanced: AdvancedMediaTrackConstraints::new(vec![]), - }; - - let subject = Subject::Constraints(constraints.clone()); - - let actual = subject.to_constraints(); - let expected = Some(constraints); - - assert_eq!(actual, expected); - } - } - - #[test] - fn to_resolved() { - let subject = MediaTrackConstraints { - mandatory: MandatoryMediaTrackConstraints::from_iter([( - &DEVICE_ID, - "microphone".into(), - )]), - advanced: AdvancedMediaTrackConstraints::new(vec![]), - }; - - let actual = subject.to_resolved(); - let expected = ResolvedMediaTrackConstraints { - mandatory: ResolvedMandatoryMediaTrackConstraints::from_iter([( - &DEVICE_ID, - ResolvedValueConstraint::default() - .ideal("microphone".to_owned()) - .into(), - )]), - advanced: ResolvedAdvancedMediaTrackConstraints::new(vec![]), - }; - - assert_eq!(actual, expected); - } -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use std::iter::FromIterator; - - use super::*; - use crate::constraints::mandatory::MandatoryMediaTrackConstraints; - use crate::macros::test_serde_symmetry; - use crate::property::all::name::*; - use crate::{AdvancedMediaTrackConstraints, MediaTrackConstraintSet}; - - type Subject = MediaTrackConstraints; - - #[test] - fn default() { - let subject = Subject::default(); - let json = serde_json::json!({}); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn customized() { - let subject = Subject { - mandatory: MandatoryMediaTrackConstraints::from_iter([( - &DEVICE_ID, - "microphone".into(), - )]), - advanced: AdvancedMediaTrackConstraints::new(vec![ - MediaTrackConstraintSet::from_iter([ - (&AUTO_GAIN_CONTROL, true.into()), - (&CHANNEL_COUNT, 2.into()), - ]), - MediaTrackConstraintSet::from_iter([(&LATENCY, 0.123.into())]), - ]), - }; - let json = serde_json::json!({ - "deviceId": "microphone", - "advanced": [ - { - "autoGainControl": true, - "channelCount": 2, - }, - { - "latency": 0.123, - }, - ] - }); - - test_serde_symmetry!(subject: subject, json: json); - } -} diff --git a/constraints/src/enumerations.rs b/constraints/src/enumerations.rs deleted file mode 100644 index 2c890a866..000000000 --- a/constraints/src/enumerations.rs +++ /dev/null @@ -1,131 +0,0 @@ -/// The directions that the camera can face, as seen from the user's perspective. -/// -/// # Note -/// The enumeration is not exhaustive and merely provides a list of known values. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum FacingMode { - /// The source is facing toward the user (a self-view camera). - User, - - /// The source is facing away from the user (viewing the environment). - Environment, - - /// The source is facing to the left of the user. - Left, - - /// The source is facing to the right of the user. - Right, -} - -impl FacingMode { - /// Returns `"user"`, the string-value of the `User` facing mode. - pub fn user() -> String { - Self::User.to_string() - } - - /// Returns `"environment"`, the string-value of the `Environment` facing mode. - pub fn environment() -> String { - Self::Environment.to_string() - } - - /// Returns `"left"`, the string-value of the `Left` facing mode. - pub fn left() -> String { - Self::Left.to_string() - } - - /// Returns `"right"`, the string-value of the `Right` facing mode. - pub fn right() -> String { - Self::Right.to_string() - } -} - -impl std::fmt::Display for FacingMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::User => f.write_str("user"), - Self::Environment => f.write_str("environment"), - Self::Left => f.write_str("left"), - Self::Right => f.write_str("right"), - } - } -} - -/// The means by which the resolution can be derived by the client. -/// -/// # Note -/// The enumeration is not exhaustive and merely provides a list of known values. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum ResizeMode { - /// This resolution and frame rate is offered by the camera, its driver, or the OS. - /// - /// # Note - /// The user agent MAY report this value to disguise concurrent use, - /// but only when the camera is in use in another browsing context. - /// - /// # Important - /// This value is a possible finger-printing surface. - None, - - /// This resolution is downscaled and/or cropped from a higher camera resolution by the user agent, - /// or its frame rate is decimated by the User Agent. - /// - /// # Important - /// The media MUST NOT be upscaled, stretched or have fake data created that did not occur in the input source. - CropAndScale, -} - -impl ResizeMode { - /// Returns `"none"`, the string-value of the `None` resize mode. - pub fn none() -> String { - Self::None.to_string() - } - - /// Returns `"crop-and-scale"`, the string-value of the `CropAndScale` resize mode. - pub fn crop_and_scale() -> String { - Self::CropAndScale.to_string() - } -} - -impl std::fmt::Display for ResizeMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::None => f.write_str("none"), - Self::CropAndScale => f.write_str("crop-and-scale"), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - mod facing_mode { - use super::*; - - #[test] - fn to_string() { - assert_eq!(FacingMode::User.to_string(), "user"); - assert_eq!(FacingMode::Environment.to_string(), "environment"); - assert_eq!(FacingMode::Left.to_string(), "left"); - assert_eq!(FacingMode::Right.to_string(), "right"); - - assert_eq!(FacingMode::user(), "user"); - assert_eq!(FacingMode::environment(), "environment"); - assert_eq!(FacingMode::left(), "left"); - assert_eq!(FacingMode::right(), "right"); - } - } - - mod resize_mode { - use super::*; - - #[test] - fn to_string() { - assert_eq!(ResizeMode::None.to_string(), "none"); - assert_eq!(ResizeMode::CropAndScale.to_string(), "crop-and-scale"); - - assert_eq!(ResizeMode::none(), "none"); - assert_eq!(ResizeMode::crop_and_scale(), "crop-and-scale"); - } - } -} diff --git a/constraints/src/errors.rs b/constraints/src/errors.rs deleted file mode 100644 index cdd1a2a1c..000000000 --- a/constraints/src/errors.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! Errors, as defined in the ["Media Capture and Streams"][mediacapture_streams] spec. -//! -//! [mediacapture_streams]: https://www.w3.org/TR/mediacapture-streams/ - -use std::collections::HashMap; - -use thiserror::Error; - -use crate::algorithms::{ConstraintFailureInfo, SettingFitnessDistanceErrorKind}; -use crate::MediaTrackProperty; - -/// An error indicating one or more over-constrained settings. -#[derive(Error, Clone, Eq, PartialEq, Debug)] -pub struct OverconstrainedError { - /// The offending constraint's name. - pub constraint: MediaTrackProperty, - /// An error message, or `None` if exposure-mode was `Protected`. - pub message: Option, -} - -impl Default for OverconstrainedError { - fn default() -> Self { - Self { - constraint: MediaTrackProperty::from(""), - message: Default::default(), - } - } -} - -impl std::fmt::Display for OverconstrainedError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Over-constrained property {:?}", self.constraint)?; - if let Some(message) = self.message.as_ref() { - write!(f, ": {message}")?; - } - Ok(()) - } -} - -impl OverconstrainedError { - pub(super) fn exposing_device_information( - failed_constraints: HashMap, - ) -> Self { - let failed_constraint = failed_constraints - .into_iter() - .max_by_key(|(_, failure_info)| failure_info.failures); - - let (constraint, failure_info) = - failed_constraint.expect("Empty candidates implies non-empty failed constraints"); - - struct Violation { - constraint: String, - settings: Vec, - } - let mut violators_by_kind: HashMap = - HashMap::default(); - - for error in failure_info.errors { - let violation = violators_by_kind.entry(error.kind).or_insert(Violation { - constraint: error.constraint.clone(), - settings: vec![], - }); - assert_eq!(violation.constraint, error.constraint); - if let Some(setting) = error.setting { - violation.settings.push(setting.clone()); - } - } - - let formatted_reasons: Vec<_> = violators_by_kind - .into_iter() - .map(|(kind, violation)| { - let kind_str = match kind { - SettingFitnessDistanceErrorKind::Missing => "missing", - SettingFitnessDistanceErrorKind::Mismatch => "a mismatch", - SettingFitnessDistanceErrorKind::TooSmall => "too small", - SettingFitnessDistanceErrorKind::TooLarge => "too large", - }; - - let mut settings = violation.settings; - - if settings.is_empty() { - return format!("{} (does not satisfy {})", kind_str, violation.constraint); - } - - settings.sort(); - - format!( - "{} ([{}] do not satisfy {})", - kind_str, - settings.join(", "), - violation.constraint - ) - }) - .collect(); - - let formatted_reason = match &formatted_reasons[..] { - [] => unreachable!(), - [reason] => reason.clone(), - [reasons @ .., reason] => { - let reasons = reasons.join(", "); - format!("either {reasons}, or {reason}") - } - }; - let message = Some(format!("Setting was {formatted_reason}.")); - - Self { - constraint, - message, - } - } -} diff --git a/constraints/src/lib.rs b/constraints/src/lib.rs deleted file mode 100644 index dca56e368..000000000 --- a/constraints/src/lib.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! Pure Rust implementation of the constraint logic defined in the ["Media Capture and Streams"][mediacapture_streams] spec. -//! -//! [mediacapture_streams]: https://www.w3.org/TR/mediacapture-streams/ -#![warn(rust_2018_idioms)] -#![allow(dead_code)] - -pub mod algorithms; -pub mod errors; -pub mod macros; -pub mod property; - -mod capabilities; -mod capability; -mod constraint; -mod constraints; -mod enumerations; -mod setting; -mod settings; -mod supported_constraints; - -#[allow(unused_imports)] -pub(crate) use self::{capabilities::MediaStreamCapabilities, settings::MediaStreamSettings}; -#[allow(unused_imports)] -pub use self::{ - capabilities::MediaTrackCapabilities, - capability::MediaTrackCapability, - constraint::{ - MediaTrackConstraint, MediaTrackConstraintResolutionStrategy, ResolvedMediaTrackConstraint, - ResolvedValueConstraint, ResolvedValueRangeConstraint, ResolvedValueSequenceConstraint, - SanitizedMediaTrackConstraint, ValueConstraint, ValueRangeConstraint, - ValueSequenceConstraint, - }, - constraints::{ - AdvancedMediaTrackConstraints, BoolOrMediaTrackConstraints, MandatoryMediaTrackConstraints, - MediaStreamConstraints, MediaTrackConstraintSet, MediaTrackConstraints, - ResolvedAdvancedMediaTrackConstraints, ResolvedMandatoryMediaTrackConstraints, - ResolvedMediaTrackConstraintSet, ResolvedMediaTrackConstraints, - SanitizedMandatoryMediaTrackConstraints, SanitizedMediaTrackConstraintSet, - SanitizedMediaTrackConstraints, - }, - enumerations::{FacingMode, ResizeMode}, - property::MediaTrackProperty, - setting::MediaTrackSetting, - settings::MediaTrackSettings, - supported_constraints::MediaTrackSupportedConstraints, -}; diff --git a/constraints/src/macros.rs b/constraints/src/macros.rs deleted file mode 100644 index 73f518204..000000000 --- a/constraints/src/macros.rs +++ /dev/null @@ -1,450 +0,0 @@ -//! Convenience macros. - -/// A convenience macro for defining settings. -#[macro_export] -macro_rules! settings { - [ - $($p:expr => $c:expr),* $(,)? - ] => { - <$crate::MediaTrackSettings as std::iter::FromIterator<_>>::from_iter([ - $(($p, $c.into())),* - ]) - }; -} - -pub use settings; - -/// A convenience macro for defining individual "value" constraints. -#[macro_export] -macro_rules! value_constraint { - ($($p:ident: $c:expr),+ $(,)?) => { - $crate::ValueConstraint::Constraint( - #[allow(clippy::needless_update)] - $crate::ResolvedValueConstraint { - $($p: Some($c)),+, - ..Default::default() - } - ) - }; - ($c:expr) => { - $crate::ValueConstraint::Bare($c) - }; -} - -pub use value_constraint; - -/// A convenience macro for defining individual "value range" constraints. -#[macro_export] -macro_rules! value_range_constraint { - {$($p:ident: $c:expr),+ $(,)?} => { - $crate::ValueRangeConstraint::Constraint( - $crate::ResolvedValueRangeConstraint { - $($p: Some($c)),+, - ..Default::default() - } - ) - }; - ($c:expr) => { - $crate::ValueRangeConstraint::Bare($c) - }; -} - -pub use value_range_constraint; - -/// A convenience macro for defining individual "value sequence" constraints. -#[macro_export] -macro_rules! value_sequence_constraint { - {$($p:ident: $c:expr),+ $(,)?} => { - $crate::ValueSequenceConstraint::Constraint( - $crate::ResolvedValueSequenceConstraint { - $($p: Some($c)),*, - ..Default::default() - } - ) - }; - ($c:expr) => { - $crate::ValueSequenceConstraint::Bare($c) - }; -} - -pub use value_sequence_constraint; - -/// A convenience macro for defining constraint sets. -#[macro_export] -macro_rules! constraint_set { - { - $($p:expr => $c:expr),* $(,)? - } => { - <$crate::MediaTrackConstraintSet as std::iter::FromIterator<_>>::from_iter([ - $(($p, $c.into())),* - ]) - }; -} - -pub use constraint_set; - -/// A convenience macro for defining "mandatory" constraints. -#[macro_export] -macro_rules! mandatory_constraints { - { - $($p:expr => $c:expr),* $(,)? - } => { - $crate::MandatoryMediaTrackConstraints::new( - constraint_set!{ - $($p => $c),* - } - ) - }; -} - -pub use mandatory_constraints; - -/// A convenience macro for defining "advanced" constraints. -#[macro_export] -macro_rules! advanced_constraints { - [ - $({ - $($p:expr => $c:expr),* $(,)? - }),* $(,)? - ] => { - <$crate::AdvancedMediaTrackConstraints as std::iter::FromIterator<_>>::from_iter([ - $(constraint_set!{ - $($p => $c),* - }),* - ]) - }; -} - -pub use advanced_constraints; - -/// A convenience macro for defining constraints. -#[macro_export] -macro_rules! constraints { - [ - mandatory: {$($mp:expr => $mc:expr),* $(,)?}, - advanced: [$( - {$($ap:expr => $ac:expr),* $(,)?} - ),* $(,)?] - ] => { - $crate::MediaTrackConstraints { - mandatory: mandatory_constraints!($($mp => $mc),*), - advanced: advanced_constraints!($({ $($ap => $ac),* }),*) - } - }; -} - -pub use constraints; - -#[allow(unused_macros)] -#[cfg(test)] -macro_rules! test_serde_symmetry { - (subject: $s:expr, json: $j:expr) => { - // Serialize: - { - let actual = serde_json::to_value($s.clone()).unwrap(); - let expected = $j.clone(); - - assert_eq!(actual, expected); - } - - // Deserialize: - { - let actual: Subject = serde_json::from_value($j).unwrap(); - let expected = $s; - - assert_eq!(actual, expected); - } - }; -} - -#[allow(unused_imports)] -#[cfg(test)] -pub(crate) use test_serde_symmetry; - -#[cfg(test)] -mod tests { - use crate::property::all::name::*; - use crate::{ - AdvancedMediaTrackConstraints, FacingMode, MandatoryMediaTrackConstraints, - MediaTrackConstraintSet, MediaTrackConstraints, MediaTrackSettings, - ResolvedValueConstraint, ResolvedValueRangeConstraint, ResolvedValueSequenceConstraint, - ValueConstraint, ValueRangeConstraint, ValueSequenceConstraint, - }; - - #[test] - fn settings() { - let actual: MediaTrackSettings = settings![ - &DEVICE_ID => "foobar".to_owned(), - &FRAME_RATE => 30.0, - &HEIGHT => 1080, - &FACING_MODE => FacingMode::user(), - ]; - - let expected = >::from_iter([ - (&DEVICE_ID, "foobar".to_owned().into()), - (&FRAME_RATE, 30.0.into()), - (&HEIGHT, 1080.into()), - (&FACING_MODE, FacingMode::user().into()), - ]); - - assert_eq!(actual, expected); - } - - mod constraint { - use super::*; - - #[test] - fn value() { - // Bare: - - let actual = value_constraint!("foobar".to_owned()); - - let expected = ValueConstraint::Bare("foobar".to_owned()); - - assert_eq!(actual, expected); - - // Constraint: - - let actual = value_constraint! { - exact: "foobar".to_owned(), - ideal: "bazblee".to_owned(), - }; - - let expected = ValueConstraint::Constraint( - ResolvedValueConstraint::default() - .exact("foobar".to_owned()) - .ideal("bazblee".to_owned()), - ); - - assert_eq!(actual, expected); - } - - #[test] - fn range() { - // Bare: - - let actual = value_range_constraint!(42); - - let expected = ValueRangeConstraint::Bare(42); - - assert_eq!(actual, expected); - - // Constraint: - - let actual = value_range_constraint! { - min: 30.0, - max: 60.0, - }; - - let expected = ValueRangeConstraint::Constraint( - ResolvedValueRangeConstraint::default().min(30.0).max(60.0), - ); - - assert_eq!(actual, expected); - } - - #[test] - fn sequence() { - // Bare: - - let actual = value_sequence_constraint![vec![FacingMode::user()]]; - - let expected = ValueSequenceConstraint::Bare(vec![FacingMode::user()]); - - assert_eq!(actual, expected); - - // Constraint: - - let actual = value_sequence_constraint! { - ideal: vec![FacingMode::user()], - }; - - let expected = ValueSequenceConstraint::Constraint( - ResolvedValueSequenceConstraint::default().ideal(vec![FacingMode::user()]), - ); - - assert_eq!(actual, expected); - } - } - - #[test] - fn mandatory_constraints() { - let actual = mandatory_constraints! { - &DEVICE_ID => value_constraint! { - exact: "foobar".to_owned(), - ideal: "bazblee".to_owned(), - }, - &FRAME_RATE => value_range_constraint! { - min: 30.0, - max: 60.0, - }, - &FACING_MODE => value_sequence_constraint! { - exact: vec![FacingMode::user(), FacingMode::environment()] - }, - }; - - let expected = >::from_iter([ - ( - &DEVICE_ID, - ValueConstraint::Constraint( - ResolvedValueConstraint::default() - .exact("foobar".to_owned()) - .ideal("bazblee".to_owned()), - ) - .into(), - ), - ( - &FRAME_RATE, - ValueRangeConstraint::Constraint( - ResolvedValueRangeConstraint::default().min(30.0).max(60.0), - ) - .into(), - ), - ( - &FACING_MODE, - ValueSequenceConstraint::Constraint( - ResolvedValueSequenceConstraint::default() - .exact(vec![FacingMode::user(), FacingMode::environment()]), - ) - .into(), - ), - ]); - - assert_eq!(actual, expected); - } - - #[test] - fn advanced_constraints() { - let actual = advanced_constraints! [ - { - &DEVICE_ID => value_constraint! { - exact: "foobar".to_owned(), - ideal: "bazblee".to_owned(), - }, - }, - { - &FRAME_RATE => value_range_constraint! { - min: 30.0, - max: 60.0, - }, - }, - { - &FACING_MODE => value_sequence_constraint! { - exact: vec![FacingMode::user(), FacingMode::environment()] - }, - }, - ]; - - let expected = >::from_iter([ - >::from_iter([( - &DEVICE_ID, - ResolvedValueConstraint::default() - .exact("foobar".to_owned()) - .ideal("bazblee".to_owned()) - .into(), - )]), - >::from_iter([( - &FRAME_RATE, - ResolvedValueRangeConstraint::default() - .min(30.0) - .max(60.0) - .into(), - )]), - >::from_iter([( - &FACING_MODE, - ResolvedValueSequenceConstraint::default() - .exact(vec![FacingMode::user(), FacingMode::environment()]) - .into(), - )]), - ]); - - assert_eq!(actual, expected); - } - - #[test] - fn constraints() { - let actual: MediaTrackConstraints = constraints!( - mandatory: { - &DEVICE_ID => value_constraint! { - exact: "foobar".to_owned(), - ideal: "bazblee".to_owned(), - }, - &FRAME_RATE => value_range_constraint! { - min: 30.0, - max: 60.0, - }, - &FACING_MODE => value_sequence_constraint! { - exact: vec![FacingMode::user(), FacingMode::environment()] - }, - }, - advanced: [ - { - &DEVICE_ID => value_constraint! { - exact: "foobar".to_owned(), - ideal: "bazblee".to_owned(), - }, - }, - { - &FRAME_RATE => value_range_constraint! { - min: 30.0, - max: 60.0, - }, - }, - { - &FACING_MODE => value_sequence_constraint! { - exact: vec![FacingMode::user(), FacingMode::environment()] - }, - }, - ] - ); - - let expected = MediaTrackConstraints { - mandatory: >::from_iter([ - ( - &DEVICE_ID, - ResolvedValueConstraint::default() - .exact("foobar".to_owned()) - .ideal("bazblee".to_owned()) - .into(), - ), - ( - &FRAME_RATE, - ResolvedValueRangeConstraint::default() - .min(30.0) - .max(60.0) - .into(), - ), - ( - &FACING_MODE, - ResolvedValueSequenceConstraint::default() - .exact(vec![FacingMode::user(), FacingMode::environment()]) - .into(), - ), - ]), - advanced: >::from_iter([ - >::from_iter([( - &DEVICE_ID, - ResolvedValueConstraint::default() - .exact("foobar".to_owned()) - .ideal("bazblee".to_owned()) - .into(), - )]), - >::from_iter([( - &FRAME_RATE, - ResolvedValueRangeConstraint::default() - .min(30.0) - .max(60.0) - .into(), - )]), - >::from_iter([( - &FACING_MODE, - ResolvedValueSequenceConstraint::default() - .exact(vec![FacingMode::user(), FacingMode::environment()]) - .into(), - )]), - ]), - }; - - assert_eq!(actual, expected); - } -} diff --git a/constraints/src/property.rs b/constraints/src/property.rs deleted file mode 100644 index 473095773..000000000 --- a/constraints/src/property.rs +++ /dev/null @@ -1,301 +0,0 @@ -//! Constants identifying the properties of a [`MediaStreamTrack`][media_stream_track] object, -//! as defined in the ["Media Capture and Streams"][media_track_supported_constraints] spec. -//! -//! [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#mediastreamtrack -//! [media_track_supported_constraints]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatracksupportedconstraints - -use std::borrow::Cow; -use std::fmt::Display; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// An identifier for a media track property. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(transparent))] -#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct MediaTrackProperty(Cow<'static, str>); - -impl From<&MediaTrackProperty> for MediaTrackProperty { - fn from(borrowed: &MediaTrackProperty) -> Self { - borrowed.clone() - } -} - -impl From for MediaTrackProperty { - /// Creates a property from an owned representation of its name. - fn from(owned: String) -> Self { - Self(Cow::Owned(owned)) - } -} - -impl From<&str> for MediaTrackProperty { - /// Creates a property from an owned representation of its name. - /// - /// Use `MediaTrackProperty::named(str)` if your property name - /// is statically borrowed (i.e. `&'static str`). - fn from(borrowed: &str) -> Self { - Self(Cow::Owned(borrowed.to_owned())) - } -} - -impl Display for MediaTrackProperty { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0) - } -} - -impl MediaTrackProperty { - /// Creates a property from a statically borrowed representation of its name. - pub const fn named(name: &'static str) -> Self { - Self(Cow::Borrowed(name)) - } - - /// The property's name. - pub fn name(&self) -> &str { - &self.0 - } -} - -/// Standard properties that apply to both, audio and video device types. -pub mod common { - use super::*; - - /// Names of common properties. - pub mod name { - use super::*; - - /// The identifier of the device generating the content of the track, - /// as defined in the [spec][spec]. - /// - /// [spec]: https://www.w3.org/TR/mediacapture-streams/#dfn-deviceid - pub static DEVICE_ID: MediaTrackProperty = MediaTrackProperty::named("deviceId"); - - /// The document-unique group identifier for the device generating the content - /// of the track, as defined in the [spec][spec]. - /// - /// [spec]: https://www.w3.org/TR/mediacapture-streams/#dfn-groupid - pub static GROUP_ID: MediaTrackProperty = MediaTrackProperty::named("groupId"); - } - - /// Names of common properties. - pub fn names() -> Vec<&'static MediaTrackProperty> { - use self::name::*; - - vec![&DEVICE_ID, &GROUP_ID] - } -} - -/// Standard properties that apply only to audio device types. -pub mod audio_only { - use super::*; - - /// Names of audio-only properties. - pub mod name { - use super::*; - - /// Automatic gain control is often desirable on the input signal recorded - /// by the microphone, as defined in the [spec][spec]. - /// - /// [spec]: https://www.w3.org/TR/mediacapture-streams/#dfn-autogaincontrol - pub static AUTO_GAIN_CONTROL: MediaTrackProperty = - MediaTrackProperty::named("autoGainControl"); - - /// The number of independent channels of sound that the audio data contains, - /// i.e. the number of audio samples per sample frame, as defined in the [spec][spec]. - /// - /// [spec]: https://www.w3.org/TR/mediacapture-streams/#dfn-channelcount - pub static CHANNEL_COUNT: MediaTrackProperty = MediaTrackProperty::named("channelCount"); - - /// When one or more audio streams is being played in the processes of - /// various microphones, it is often desirable to attempt to remove - /// all the sound being played from the input signals recorded by the microphones. - /// This is referred to as echo cancellation, as defined in the [spec][spec]. - /// - /// [spec]: https://www.w3.org/TR/mediacapture-streams/#dfn-echocancellation - pub static ECHO_CANCELLATION: MediaTrackProperty = - MediaTrackProperty::named("echoCancellation"); - - /// The latency or latency range, in seconds, as defined in the [spec][spec]. - /// - /// [spec]: https://www.w3.org/TR/mediacapture-streams/#dfn-latency - pub static LATENCY: MediaTrackProperty = MediaTrackProperty::named("latency"); - - /// Noise suppression is often desirable on the input signal recorded by the microphone, - /// as defined in the [spec][spec]. - /// - /// [spec]: https://www.w3.org/TR/mediacapture-streams/#dfn-noisesuppression - pub static NOISE_SUPPRESSION: MediaTrackProperty = - MediaTrackProperty::named("noiseSuppression"); - - /// The sample rate in samples per second for the audio data, as defined in the [spec][spec]. - /// - /// [spec]: https://www.w3.org/TR/mediacapture-streams/#dfn-samplerate - pub static SAMPLE_RATE: MediaTrackProperty = MediaTrackProperty::named("sampleRate"); - - /// The linear sample size in bits. This constraint can only - /// be satisfied for audio devices that produce linear samples, as defined in the [spec][spec]. - /// - /// [spec]: https://www.w3.org/TR/mediacapture-streams/#dfn-samplesize - pub static SAMPLE_SIZE: MediaTrackProperty = MediaTrackProperty::named("sampleSize"); - } - - /// Names of all audio-only properties. - pub fn names() -> Vec<&'static MediaTrackProperty> { - use self::name::*; - - vec![ - &AUTO_GAIN_CONTROL, - &CHANNEL_COUNT, - &ECHO_CANCELLATION, - &LATENCY, - &NOISE_SUPPRESSION, - &SAMPLE_RATE, - &SAMPLE_SIZE, - ] - } -} - -/// Standard properties that apply only to video device types. -pub mod video_only { - use super::*; - - /// Names of audio-only properties. - pub mod name { - use super::*; - - /// The exact aspect ratio (width in pixels divided by height in pixels, - /// represented as a double rounded to the tenth decimal place), - /// as defined in the [spec][spec]. - /// - /// [spec]: https://www.w3.org/TR/mediacapture-streams/#dfn-aspectratio - pub static ASPECT_RATIO: MediaTrackProperty = MediaTrackProperty::named("aspectRatio"); - - /// The directions that the camera can face, as seen from the user's perspective, - /// as defined in the [spec][spec]. - /// - /// [spec]: https://www.w3.org/TR/mediacapture-streams/#dfn-facingmode - pub static FACING_MODE: MediaTrackProperty = MediaTrackProperty::named("facingMode"); - - /// The exact frame rate (frames per second) or frame rate range, - /// as defined in the [spec][spec]. - /// - /// [spec]: https://www.w3.org/TR/mediacapture-streams/#dfn-framerate - pub static FRAME_RATE: MediaTrackProperty = MediaTrackProperty::named("frameRate"); - - /// The height or height range, in pixels, as defined in the [spec][spec]. - /// - /// [spec]: https://www.w3.org/TR/mediacapture-streams/#dfn-height - pub static HEIGHT: MediaTrackProperty = MediaTrackProperty::named("height"); - - /// The width or width range, in pixels, as defined in the [spec][spec]. - /// - /// [spec]: https://www.w3.org/TR/mediacapture-streams/#dfn-width - pub static WIDTH: MediaTrackProperty = MediaTrackProperty::named("width"); - - /// The means by which the resolution can be derived by the client, as defined in the [spec][spec]. - /// - /// In other words, whether the client is allowed to use cropping and downscaling on the camera output. - /// - /// [spec]: https://www.w3.org/TR/mediacapture-streams/#dfn-resizemode - pub static RESIZE_MODE: MediaTrackProperty = MediaTrackProperty::named("resizeMode"); - } - - /// Names of all video-only properties. - pub fn names() -> Vec<&'static MediaTrackProperty> { - use self::name::*; - vec![ - &ASPECT_RATIO, - &FACING_MODE, - &FRAME_RATE, - &HEIGHT, - &WIDTH, - &RESIZE_MODE, - ] - } -} - -/// The union of all standard properties (i.e. common + audio + video). -pub mod all { - use super::*; - - /// Names of all properties. - pub mod name { - pub use super::audio_only::name::*; - pub use super::common::name::*; - pub use super::video_only::name::*; - } - - /// Names of all properties. - pub fn names() -> Vec<&'static MediaTrackProperty> { - let mut all = vec![]; - all.append(&mut self::common::names()); - all.append(&mut self::audio_only::names()); - all.append(&mut self::video_only::names()); - all - } -} - -#[cfg(test)] -mod tests { - use super::*; - - type Subject = MediaTrackProperty; - - mod from { - use super::*; - - #[test] - fn owned() { - let actuals = [Subject::from("string"), Subject::from("string".to_owned())]; - let expected = MediaTrackProperty(Cow::Owned("string".to_owned())); - - for actual in actuals { - assert_eq!(actual, expected); - - // TODO: remove feature-gate, once stabilized: - #[cfg(feature = "cow_is_borrowed")] - assert!(actual.0.is_owned()); - } - } - - #[test] - fn borrowed() { - let actual = Subject::named("string"); - let expected = MediaTrackProperty(Cow::Borrowed("string")); - - assert_eq!(actual, expected); - - // TODO: remove feature-gate, once stabilized: - #[cfg(feature = "cow_is_borrowed")] - assert!(actual.0.is_borrowed()); - } - } - - #[test] - fn name() { - assert_eq!(Subject::named("string").name(), "string"); - } - - #[test] - fn to_string() { - assert_eq!(Subject::named("string").to_string(), "string"); - } -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use super::*; - use crate::macros::test_serde_symmetry; - - type Subject = MediaTrackProperty; - - #[test] - fn is_symmetric() { - let subject = Subject::named("string"); - let json = serde_json::json!("string"); - - test_serde_symmetry!(subject: subject, json: json); - } -} diff --git a/constraints/src/setting.rs b/constraints/src/setting.rs deleted file mode 100644 index 15dc24c64..000000000 --- a/constraints/src/setting.rs +++ /dev/null @@ -1,159 +0,0 @@ -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// A single [setting][media_track_settings] value of a [`MediaStreamTrack`][media_stream_track] object. -/// -/// # W3C Spec Compliance -/// -/// There exists no corresponding type in the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -/// [media_track_settings]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatracksettings -/// [media_track_supported_constraints]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatracksupportedconstraints -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(untagged))] -pub enum MediaTrackSetting { - /// A boolean-valued track setting. - Bool(bool), - /// An integer-valued track setting. - Integer(i64), - /// A floating-point-valued track setting. - Float(f64), - /// A string-valued track setting. - String(String), -} - -impl std::fmt::Display for MediaTrackSetting { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Bool(setting) => f.write_fmt(format_args!("{setting:?}")), - Self::Integer(setting) => f.write_fmt(format_args!("{setting:?}")), - Self::Float(setting) => f.write_fmt(format_args!("{setting:?}")), - Self::String(setting) => f.write_fmt(format_args!("{setting:?}")), - } - } -} - -impl From for MediaTrackSetting { - fn from(setting: bool) -> Self { - Self::Bool(setting) - } -} - -impl From for MediaTrackSetting { - fn from(setting: i64) -> Self { - Self::Integer(setting) - } -} - -impl From for MediaTrackSetting { - fn from(setting: f64) -> Self { - Self::Float(setting) - } -} - -impl From for MediaTrackSetting { - fn from(setting: String) -> Self { - Self::String(setting) - } -} - -impl<'a> From<&'a str> for MediaTrackSetting { - fn from(setting: &'a str) -> Self { - Self::String(setting.to_owned()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - type Subject = MediaTrackSetting; - - mod from { - use super::*; - - #[test] - fn bool() { - let actual = Subject::from(true); - let expected = Subject::Bool(true); - - assert_eq!(actual, expected); - } - - #[test] - fn integer() { - let actual = Subject::from(42); - let expected = Subject::Integer(42); - - assert_eq!(actual, expected); - } - - #[test] - fn float() { - let actual = Subject::from(4.2); - let expected = Subject::Float(4.2); - - assert_eq!(actual, expected); - } - - #[test] - fn string() { - let actual = Subject::from("string".to_owned()); - let expected = Subject::String("string".to_owned()); - - assert_eq!(actual, expected); - } - } - - #[test] - fn to_string() { - assert_eq!(Subject::from(true).to_string(), "true"); - assert_eq!(Subject::from(42).to_string(), "42"); - assert_eq!(Subject::from(4.2).to_string(), "4.2"); - assert_eq!(Subject::from("string".to_owned()).to_string(), "\"string\""); - } -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use super::*; - use crate::macros::test_serde_symmetry; - - type Subject = MediaTrackSetting; - - #[test] - fn bool() { - let subject = Subject::Bool(true); - let json = serde_json::json!(true); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn integer() { - let subject = Subject::Integer(42); - let json = serde_json::json!(42); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn float() { - let subject = Subject::Float(4.2); - let json = serde_json::json!(4.2); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn string() { - let subject = Subject::String("string".to_owned()); - let json = serde_json::json!("string"); - - test_serde_symmetry!(subject: subject, json: json); - } -} diff --git a/constraints/src/settings.rs b/constraints/src/settings.rs deleted file mode 100644 index b69a2c76a..000000000 --- a/constraints/src/settings.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod stream; -mod track; - -pub(crate) use self::stream::MediaStreamSettings; -pub use self::track::MediaTrackSettings; diff --git a/constraints/src/settings/stream.rs b/constraints/src/settings/stream.rs deleted file mode 100644 index 79f99cc99..000000000 --- a/constraints/src/settings/stream.rs +++ /dev/null @@ -1,58 +0,0 @@ -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use crate::MediaTrackSettings; - -/// The settings of a [`MediaStream`][media_stream] object. -/// -/// # W3C Spec Compliance -/// -/// There exists no corresponding type in the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// [media_stream]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastream -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams -#[derive(Default, Debug, Clone, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub(crate) struct MediaStreamSettings { - #[cfg_attr( - feature = "serde", - serde(skip_serializing_if = "core::option::Option::is_none") - )] - pub audio: Option, - #[cfg_attr( - feature = "serde", - serde(skip_serializing_if = "core::option::Option::is_none") - )] - pub video: Option, -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use super::*; - use crate::macros::test_serde_symmetry; - - type Subject = MediaStreamSettings; - - #[test] - fn default() { - let subject = Subject::default(); - let json = serde_json::json!({}); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn customized() { - let subject = MediaStreamSettings { - audio: Some(MediaTrackSettings::default()), - video: None, - }; - let json = serde_json::json!({ - "audio": {} - }); - - test_serde_symmetry!(subject: subject, json: json); - } -} diff --git a/constraints/src/settings/track.rs b/constraints/src/settings/track.rs deleted file mode 100644 index 625e7bc4d..000000000 --- a/constraints/src/settings/track.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::collections::HashMap; -use std::iter::FromIterator; -use std::ops::{Deref, DerefMut}; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use crate::{MediaTrackProperty, MediaTrackSetting}; - -/// The settings of a [`MediaStreamTrack`][media_stream_track] object. -/// -/// # W3C Spec Compliance -/// -/// Corresponds to [`MediaTrackSettings`][media_track_settings] -/// from the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// The W3C spec defines `MediaTrackSettings` in terma of a dictionary, -/// which per the [WebIDL spec][webidl_spec] is an ordered map (e.g. [`IndexMap`][index_map]). -/// Since the spec however does not make use of the order of items -/// in the map we use a simple [`HashMap`][hash_map]. -/// -/// [hash_map]: https://doc.rust-lang.org/std/collections/struct.HashMap.html -/// [index_map]: https://docs.rs/indexmap/latest/indexmap/set/struct.IndexMap.html -/// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -/// [media_track_settings]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatracksettings -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams -/// [webidl_spec]: https://webidl.spec.whatwg.org/#idl-dictionaries -#[derive(Debug, Clone, Default, PartialEq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(transparent))] -pub struct MediaTrackSettings(HashMap); - -impl MediaTrackSettings { - /// Creates a settings value from its inner hashmap. - pub fn new(settings: HashMap) -> Self { - Self(settings) - } - - /// Consumes the value, returning its inner hashmap. - pub fn into_inner(self) -> HashMap { - self.0 - } -} - -impl Deref for MediaTrackSettings { - type Target = HashMap; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for MediaTrackSettings { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl FromIterator<(T, MediaTrackSetting)> for MediaTrackSettings -where - T: Into, -{ - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - Self::new(iter.into_iter().map(|(k, v)| (k.into(), v)).collect()) - } -} - -impl IntoIterator for MediaTrackSettings { - type Item = (MediaTrackProperty, MediaTrackSetting); - type IntoIter = std::collections::hash_map::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::property::all::name::*; - - type Subject = MediaTrackSettings; - - #[test] - fn into_inner() { - let hash_map = HashMap::from_iter([ - (DEVICE_ID.clone(), "device-id".into()), - (AUTO_GAIN_CONTROL.clone(), true.into()), - (CHANNEL_COUNT.clone(), 20.into()), - (LATENCY.clone(), 2.0.into()), - ]); - - let subject = Subject::new(hash_map.clone()); - - let actual = subject.into_inner(); - - let expected = hash_map; - - assert_eq!(actual, expected); - } - - #[test] - fn into_iter() { - let hash_map = HashMap::from_iter([ - (DEVICE_ID.clone(), "device-id".into()), - (AUTO_GAIN_CONTROL.clone(), true.into()), - (CHANNEL_COUNT.clone(), 20.into()), - (LATENCY.clone(), 2.0.into()), - ]); - - let subject = Subject::new(hash_map.clone()); - - let actual: HashMap<_, _> = subject.into_iter().collect(); - - let expected = hash_map; - - assert_eq!(actual, expected); - } - - #[test] - fn deref_and_deref_mut() { - let mut subject = Subject::default(); - - // Deref mut: - subject.insert(DEVICE_ID.clone(), "device-id".into()); - - // Deref: - assert!(subject.contains_key(&DEVICE_ID)); - } -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use super::*; - use crate::macros::test_serde_symmetry; - use crate::property::all::name::*; - - type Subject = MediaTrackSettings; - - #[test] - fn default() { - let subject = Subject::default(); - let json = serde_json::json!({}); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn customized() { - let subject = Subject::from_iter([ - (&DEVICE_ID, "device-id".into()), - (&AUTO_GAIN_CONTROL, true.into()), - (&CHANNEL_COUNT, 2.into()), - (&LATENCY, 0.123.into()), - ]); - let json = serde_json::json!({ - "deviceId": "device-id".to_owned(), - "autoGainControl": true, - "channelCount": 2, - "latency": 0.123, - }); - - test_serde_symmetry!(subject: subject, json: json); - } -} diff --git a/constraints/src/supported_constraints.rs b/constraints/src/supported_constraints.rs deleted file mode 100644 index 21ca96b08..000000000 --- a/constraints/src/supported_constraints.rs +++ /dev/null @@ -1,252 +0,0 @@ -use std::collections::HashSet; -use std::iter::FromIterator; -use std::ops::{Deref, DerefMut}; - -#[cfg(feature = "serde")] -use serde::{ - de::{MapAccess, Visitor}, - ser::SerializeMap, - Deserialize, Deserializer, Serialize, Serializer, -}; - -use crate::MediaTrackProperty; - -/// The list of constraints recognized by a User Agent for controlling the -/// capabilities of a [`MediaStreamTrack`][media_stream_track] object. -/// -/// # W3C Spec Compliance -/// -/// Corresponds to [`MediaTrackSupportedConstraints`][media_track_supported_constraints] -/// from the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. -/// -/// The W3C spec defines `MediaTrackSupportedConstraints` in terma of a dictionary, -/// which per the [WebIDL spec][webidl_spec] is an ordered map (e.g. [`IndexSet`][index_set]). -/// Since the spec however does not make use of the order of items -/// in the map we use a simple `HashSet`. -/// -/// [hash_set]: https://doc.rust-lang.org/std/collections/struct.HashSet.html -/// [index_set]: https://docs.rs/indexmap/latest/indexmap/set/struct.IndexSet.html -/// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack -/// [media_track_supported_constraints]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatracksupportedconstraints -/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams -/// [webidl_spec]: https://webidl.spec.whatwg.org/#idl-dictionaries -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct MediaTrackSupportedConstraints(HashSet); - -impl MediaTrackSupportedConstraints { - /// Creates a supported constraints value from its inner hashmap. - pub fn new(properties: HashSet) -> Self { - Self(properties) - } - - /// Consumes the value, returning its inner hashmap. - pub fn into_inner(self) -> HashSet { - self.0 - } -} - -impl Deref for MediaTrackSupportedConstraints { - type Target = HashSet; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for MediaTrackSupportedConstraints { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Default for MediaTrackSupportedConstraints { - /// [Default values][default_values] as defined by the W3C specification. - /// - /// [default_values]: https://www.w3.org/TR/mediacapture-streams/#dictionary-mediatracksupportedconstraints-members - fn default() -> Self { - use crate::property::all::names as property_names; - - Self::from_iter(property_names().into_iter().cloned()) - } -} - -impl FromIterator for MediaTrackSupportedConstraints -where - T: Into, -{ - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - Self(iter.into_iter().map(|property| property.into()).collect()) - } -} - -impl IntoIterator for MediaTrackSupportedConstraints { - type Item = MediaTrackProperty; - type IntoIter = std::collections::hash_set::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for MediaTrackSupportedConstraints { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_map(SerdeVisitor) - } -} - -#[cfg(feature = "serde")] -impl Serialize for MediaTrackSupportedConstraints { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut map = serializer.serialize_map(Some(self.0.len()))?; - for property in &self.0 { - map.serialize_entry(property, &true)?; - } - map.end() - } -} - -#[cfg(feature = "serde")] -struct SerdeVisitor; - -#[cfg(feature = "serde")] -impl<'de> Visitor<'de> for SerdeVisitor { - type Value = MediaTrackSupportedConstraints; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("an object with strings as keys and `true` as values") - } - - fn visit_map(self, mut access: M) -> Result - where - M: MapAccess<'de>, - { - let mut set = HashSet::with_capacity(access.size_hint().unwrap_or(0)); - while let Some((key, value)) = access.next_entry()? { - if value { - set.insert(key); - } - } - Ok(MediaTrackSupportedConstraints(set)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::property::all::name::*; - - type Subject = MediaTrackSupportedConstraints; - - #[test] - fn into_inner() { - let hash_set = HashSet::from_iter([ - DEVICE_ID.clone(), - AUTO_GAIN_CONTROL.clone(), - CHANNEL_COUNT.clone(), - LATENCY.clone(), - ]); - - let subject = Subject::new(hash_set.clone()); - - let actual = subject.into_inner(); - - let expected = hash_set; - - assert_eq!(actual, expected); - } - - #[test] - fn into_iter() { - let hash_set = HashSet::from_iter([ - DEVICE_ID.clone(), - AUTO_GAIN_CONTROL.clone(), - CHANNEL_COUNT.clone(), - LATENCY.clone(), - ]); - - let subject = Subject::new(hash_set.clone()); - - let actual: HashSet<_, _> = subject.into_iter().collect(); - - let expected = hash_set; - - assert_eq!(actual, expected); - } - - #[test] - fn deref_and_deref_mut() { - let mut subject = Subject::default(); - - // Deref mut: - subject.insert(DEVICE_ID.clone()); - - // Deref: - assert!(subject.contains(&DEVICE_ID)); - } -} - -#[cfg(feature = "serde")] -#[cfg(test)] -mod serde_tests { - use super::*; - use crate::macros::test_serde_symmetry; - use crate::property::all::name::*; - - type Subject = MediaTrackSupportedConstraints; - - #[test] - fn default() { - let subject = Subject::default(); - let json = serde_json::json!({ - "deviceId": true, - "groupId": true, - "autoGainControl": true, - "channelCount": true, - "echoCancellation": true, - "latency": true, - "noiseSuppression": true, - "sampleRate": true, - "sampleSize": true, - "aspectRatio": true, - "facingMode": true, - "frameRate": true, - "height": true, - "width": true, - "resizeMode": true, - }); - - test_serde_symmetry!(subject: subject, json: json); - } - - #[test] - fn customized() { - let subject = Subject::from_iter([ - &DEVICE_ID, - &GROUP_ID, - &AUTO_GAIN_CONTROL, - &CHANNEL_COUNT, - &ASPECT_RATIO, - &FACING_MODE, - ]); - let json = serde_json::json!({ - "deviceId": true, - "groupId": true, - "autoGainControl": true, - "channelCount": true, - "aspectRatio": true, - "facingMode": true - }); - - test_serde_symmetry!(subject: subject, json: json); - } -} diff --git a/constraints/tests/w3c_spec_examples.rs b/constraints/tests/w3c_spec_examples.rs deleted file mode 100644 index f46144f00..000000000 --- a/constraints/tests/w3c_spec_examples.rs +++ /dev/null @@ -1,216 +0,0 @@ -#[cfg(feature = "serde")] -use webrtc_constraints::{ - property::all::name::*, AdvancedMediaTrackConstraints, BoolOrMediaTrackConstraints, - MediaTrackConstraintSet, MediaTrackConstraints, ResolvedValueRangeConstraint, - ValueRangeConstraint, -}; - -// -#[cfg(feature = "serde")] -#[test] -fn w3c_spec_example_1() { - use std::iter::FromIterator; - - use webrtc_constraints::{MandatoryMediaTrackConstraints, MediaStreamConstraints}; - - let actual: MediaStreamConstraints = { - let json = serde_json::json!({ - "video": { - "width": 1280, - "height": 720, - "aspectRatio": 1.5, - } - }); - serde_json::from_value(json).unwrap() - }; - let expected = MediaStreamConstraints { - audio: BoolOrMediaTrackConstraints::Bool(false), - video: BoolOrMediaTrackConstraints::Constraints(MediaTrackConstraints { - mandatory: MandatoryMediaTrackConstraints::from_iter([ - (&WIDTH, 1280.into()), - (&HEIGHT, 720.into()), - (&ASPECT_RATIO, 1.5.into()), - ]), - advanced: AdvancedMediaTrackConstraints::default(), - }), - }; - - assert_eq!(actual, expected); -} - -// -#[cfg(feature = "serde")] -#[test] -fn w3c_spec_example_2() { - use std::iter::FromIterator; - - use webrtc_constraints::{MandatoryMediaTrackConstraints, MediaStreamConstraints}; - - let actual: MediaStreamConstraints = { - let json = serde_json::json!({ - "video": { - "width": { "min": 640, "ideal": 1280 }, - "height": { "min": 480, "ideal": 720 }, - "aspectRatio": 1.5, - "frameRate": { "min": 20.0 }, - } - }); - serde_json::from_value(json).unwrap() - }; - - let expected = MediaStreamConstraints { - audio: BoolOrMediaTrackConstraints::Bool(false), - video: BoolOrMediaTrackConstraints::Constraints(MediaTrackConstraints { - mandatory: MandatoryMediaTrackConstraints::from_iter([ - ( - &WIDTH, - ValueRangeConstraint::Constraint(ResolvedValueRangeConstraint { - min: Some(640), - max: None, - exact: None, - ideal: Some(1280), - }) - .into(), - ), - ( - &HEIGHT, - ValueRangeConstraint::Constraint(ResolvedValueRangeConstraint { - min: Some(480), - max: None, - exact: None, - ideal: Some(720), - }) - .into(), - ), - (&ASPECT_RATIO, ValueRangeConstraint::Bare(1.5).into()), - ( - &FRAME_RATE, - ValueRangeConstraint::Constraint(ResolvedValueRangeConstraint { - min: Some(20.0), - max: None, - exact: None, - ideal: None, - }) - .into(), - ), - ]), - advanced: AdvancedMediaTrackConstraints::default(), - }), - }; - - assert_eq!(actual, expected); -} - -// -#[cfg(feature = "serde")] -#[test] -fn w3c_spec_example_3() { - use std::iter::FromIterator; - - use webrtc_constraints::{MandatoryMediaTrackConstraints, MediaStreamConstraints}; - - let actual: MediaStreamConstraints = { - let json = serde_json::json!({ - "video": { - "height": { "min": 480, "ideal": 720 }, - "width": { "min": 640, "ideal": 1280 }, - "frameRate": { "min": 30.0 }, - "advanced": [ - {"width": 1920, "height": 1280 }, - {"aspectRatio": 1.333}, - {"frameRate": {"min": 50.0 } }, - {"frameRate": {"min": 40.0 } } - ] - } - }); - serde_json::from_value(json).unwrap() - }; - - let expected = MediaStreamConstraints { - audio: BoolOrMediaTrackConstraints::Bool(false), - video: BoolOrMediaTrackConstraints::Constraints(MediaTrackConstraints { - mandatory: MandatoryMediaTrackConstraints::from_iter([ - ( - &HEIGHT, - ResolvedValueRangeConstraint { - min: Some(480), - max: None, - exact: None, - ideal: Some(720), - } - .into(), - ), - ( - &WIDTH, - ResolvedValueRangeConstraint { - min: Some(640), - max: None, - exact: None, - ideal: Some(1280), - } - .into(), - ), - ( - &FRAME_RATE, - ResolvedValueRangeConstraint { - min: Some(30.0), - max: None, - exact: None, - ideal: None, - } - .into(), - ), - ]), - advanced: AdvancedMediaTrackConstraints::new(vec![ - MediaTrackConstraintSet::from_iter([(&WIDTH, 1920.into()), (&HEIGHT, 1280.into())]), - MediaTrackConstraintSet::from_iter([(&ASPECT_RATIO, 1.333.into())]), - MediaTrackConstraintSet::from_iter([( - &FRAME_RATE, - ResolvedValueRangeConstraint { - min: Some(50.0), - max: None, - exact: None, - ideal: None, - } - .into(), - )]), - MediaTrackConstraintSet::from_iter([( - &FRAME_RATE, - ResolvedValueRangeConstraint { - min: Some(40.0), - max: None, - exact: None, - ideal: None, - } - .into(), - )]), - ]), - }), - }; - - assert_eq!(actual, expected); -} - -// -#[cfg(feature = "serde")] -#[test] -fn w3c_spec_example_4() { - use std::iter::FromIterator; - - let actual: MediaTrackConstraintSet = { - let json = serde_json::json!({ - "width": 1920, - "height": 1080, - "frameRate": 30, - }); - serde_json::from_value(json).unwrap() - }; - - let expected = MediaTrackConstraintSet::from_iter([ - (&WIDTH, ValueRangeConstraint::Bare(1920).into()), - (&HEIGHT, ValueRangeConstraint::Bare(1080).into()), - (&FRAME_RATE, ValueRangeConstraint::Bare(30).into()), - ]); - - assert_eq!(actual, expected); -} diff --git a/data/Cargo.toml b/data/Cargo.toml index 47a3337b2..73209a232 100644 --- a/data/Cargo.toml +++ b/data/Cargo.toml @@ -34,6 +34,3 @@ portable-atomic = "1.6" tokio-test = "0.4" # must match the min version of the `tokio` crate above env_logger = "0.11.3" chrono = "0.4.28" - -[lints] -workspace = true diff --git a/dtls/Cargo.toml b/dtls/Cargo.toml index 8019cb2fe..4fe9120fe 100644 --- a/dtls/Cargo.toml +++ b/dtls/Cargo.toml @@ -60,9 +60,6 @@ chrono = "0.4.28" clap = "3" hub = {path = "examples/hub"} -[lints] -workspace = true - [features] pem = ["dep:pem"] diff --git a/ice/Cargo.toml b/ice/Cargo.toml index 71b7cedf2..e3cbd15be 100644 --- a/ice/Cargo.toml +++ b/ice/Cargo.toml @@ -51,9 +51,6 @@ lazy_static = "1" hyper = { version = "0.14.27", features = ["full"] } sha1 = "0.10" -[lints] -workspace = true - [[example]] name = "ping_pong" path = "examples/ping_pong.rs" diff --git a/interceptor/Cargo.toml b/interceptor/Cargo.toml index de657e488..3edbcd3d0 100644 --- a/interceptor/Cargo.toml +++ b/interceptor/Cargo.toml @@ -27,6 +27,3 @@ portable-atomic = "1.6" [dev-dependencies] tokio-test = "0.4" chrono = "0.4.28" - -[lints] -workspace = true diff --git a/mdns/Cargo.toml b/mdns/Cargo.toml index 0f92cf918..82264ef4b 100644 --- a/mdns/Cargo.toml +++ b/mdns/Cargo.toml @@ -37,9 +37,6 @@ env_logger = "0.11.3" chrono = "0.4.28" clap = "3" -[lints] -workspace = true - [[example]] name = "mdns_query" path = "examples/mdns_query.rs" diff --git a/media/Cargo.toml b/media/Cargo.toml index 7fea43128..b89f3d5b9 100644 --- a/media/Cargo.toml +++ b/media/Cargo.toml @@ -21,9 +21,6 @@ rand = "0.8" criterion = { version = "0.5", features = ["html_reports"] } nearly_eq = "0.2" -[lints] -workspace = true - [[bench]] name = "audio_buffer" harness = false diff --git a/rtcp/Cargo.toml b/rtcp/Cargo.toml index 948948294..e5ed6febc 100644 --- a/rtcp/Cargo.toml +++ b/rtcp/Cargo.toml @@ -14,6 +14,3 @@ util = { version = "0.9.0", path = "../util", package = "webrtc-util", default-f bytes = "1" thiserror = "1" - -[lints] -workspace = true diff --git a/rtp/Cargo.toml b/rtp/Cargo.toml index c7ddac407..6c54464aa 100644 --- a/rtp/Cargo.toml +++ b/rtp/Cargo.toml @@ -24,9 +24,6 @@ memchr = "2.1.1" chrono = "0.4.28" criterion = "0.5" -[lints] -workspace = true - [[bench]] name = "packet_bench" harness = false diff --git a/sctp/Cargo.toml b/sctp/Cargo.toml index 30dd668fb..7fa514434 100644 --- a/sctp/Cargo.toml +++ b/sctp/Cargo.toml @@ -40,9 +40,6 @@ env_logger = "0.11.3" chrono = "0.4.28" clap = "3" -[lints] -workspace = true - [[example]] name = "ping" path = "examples/ping.rs" @@ -52,3 +49,6 @@ bench = false name = "pong" path = "examples/pong.rs" bench = false + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(fuzzing)'] } diff --git a/sdp/Cargo.toml b/sdp/Cargo.toml index 483f02784..6f5eda650 100644 --- a/sdp/Cargo.toml +++ b/sdp/Cargo.toml @@ -18,9 +18,6 @@ substring = "1" [dev-dependencies] criterion = "0.5" -[lints] -workspace = true - [[bench]] name = "bench" harness = false diff --git a/srtp/Cargo.toml b/srtp/Cargo.toml index 599fabdaf..097b89db1 100644 --- a/srtp/Cargo.toml +++ b/srtp/Cargo.toml @@ -52,9 +52,6 @@ criterion = { version = "0.5", features = ["async_futures"] } tokio-test = "0.4" lazy_static = "1" -[lints] -workspace = true - [[bench]] name = "srtp_bench" harness = false diff --git a/stun/Cargo.toml b/stun/Cargo.toml index 44d696212..4fa04d60c 100644 --- a/stun/Cargo.toml +++ b/stun/Cargo.toml @@ -43,9 +43,6 @@ tokio-test = "0.4" clap = "3" criterion = "0.5" -[lints] -workspace = true - [[bench]] name = "bench" harness = false diff --git a/turn/Cargo.toml b/turn/Cargo.toml index acb10c985..e45b154bd 100644 --- a/turn/Cargo.toml +++ b/turn/Cargo.toml @@ -44,9 +44,6 @@ hex = "0.4" clap = "3" criterion = "0.5" -[lints] -workspace = true - [features] metrics = [] diff --git a/util/Cargo.toml b/util/Cargo.toml index 60247b1cd..bf0f68b45 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -61,9 +61,6 @@ chrono = "0.4.28" criterion = { version = "0.5", features = ["async_futures"] } async-global-executor = "2" -[lints] -workspace = true - [[bench]] name = "bench" harness = false diff --git a/webrtc/Cargo.toml b/webrtc/Cargo.toml index ad22367f7..56262d224 100644 --- a/webrtc/Cargo.toml +++ b/webrtc/Cargo.toml @@ -65,9 +65,6 @@ portable-atomic = "1.6" tokio-test = "0.4" env_logger = "0.11.3" -[lints] -workspace = true - [features] pem = ["dep:pem", "dtls/pem"] openssl = ["srtp/openssl"]