├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── bulk.yaml ├── src └── lib.rs └── vagga.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.vagga 3 | /Cargo.lock 4 | /tmp 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: trusty 3 | language: rust 4 | 5 | cache: 6 | - cargo 7 | 8 | before_cache: 9 | - rm -r $TRAVIS_BUILD_DIR/target/debug 10 | 11 | jobs: 12 | include: 13 | - os: linux 14 | rust: stable 15 | - os: linux 16 | rust: beta 17 | - os: linux 18 | rust: nightly 19 | 20 | # deploy 21 | - stage: publish 22 | os: linux 23 | rust: stable 24 | env: 25 | # CARGO_TOKEN 26 | - secure: "HoUAOF9ifTpXUxPZNUgtWXLHGCg5i/LSDgQ9wo1l41JZCdjQE/sQ3FMBb6r0rq4JHj66q/sf0ySJedDN0lmC7VwwauKtN4xiSAUlnCoP8PnCGLvBLLfwHYGOUNoJjRuw1CoLielafzt3wqB0+Byz0OX0GrK5E3t7iLJ7XQplCBcKfWddDmgn0IQvVP9GHQKdGlEF2bjycL9dPKeM+O9OQ+sT0KmjF7UgcFDeJTLzP9rwqgKOpebtZPyT2XGCRPWi3p9c+CPw3ObarieLqHkM4aA5euyKHhL2p3P8pEeLU5w9VrUA4XVGmDK8UHCK+U5wrRkcdbb8dN5lUqE2VGUGz9iGafzvc+cQlzBD3t3iTdkhgbm244GW5FRSkJefLkknADHL/xtNbDhSYCYuOpr5Mxc0mXOSWXG1vSvlaj6FBShWdhdZY5kaTAyP2Aqhl6naX9+Aj4EK6HAnM57qwhdc1fkJ1tCrNDmeyc9YpubiAe09Y7jrwftkhTWsyy6wURdVj3+xSCOta0AoC9rTJDZLM06KrKKtgoSeS4BghfxKcBqvV+3pOGEct3gFb7fGSvs1bzelrTAFZx/N4jvL/e5L9RODEvoPBr+iDaCMfgYNSmrOmD0dBeGK+JEP7exKFJZfFpMPYp2YMWhrR5+HKrsZf0TRqQpdD/9B4dDMGRmgjCc=" 27 | install: true 28 | script: true 29 | 30 | deploy: 31 | - provider: script 32 | script: 'cargo publish --verbose --token=$CARGO_TOKEN' 33 | on: 34 | tags: true 35 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "serde_regex" 3 | description = """ 4 | A serde wrapper that (de)serializes regex as strings 5 | """ 6 | license = "MIT/Apache-2.0" 7 | readme = "README.md" 8 | keywords = ["serde", "regex"] 9 | categories = ["encoding"] 10 | homepage = "https://github.com/tailhook/serde-regex" 11 | documentation = "https://docs.rs/serde_regex" 12 | version = "1.1.0" 13 | authors = ["paul@colomiets.name"] 14 | edition = "2018" 15 | 16 | [dependencies] 17 | serde = "1.0.0" 18 | regex = "1.5.5" 19 | 20 | [dev-dependencies] 21 | serde_derive = "1.0.0" 22 | serde_json = "1.0.39" 23 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 The serde_regex Developers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | Parts of the code extracted from futures-rs with the following copyright: 22 | 23 | Copyright (c) 2016 Alex Crichton 24 | 25 | Permission is hereby granted, free of charge, to any 26 | person obtaining a copy of this software and associated 27 | documentation files (the "Software"), to deal in the 28 | Software without restriction, including without 29 | limitation the rights to use, copy, modify, merge, 30 | publish, distribute, sublicense, and/or sell copies of 31 | the Software, and to permit persons to whom the Software 32 | is furnished to do so, subject to the following 33 | conditions: 34 | 35 | The above copyright notice and this permission notice 36 | shall be included in all copies or substantial portions 37 | of the Software. 38 | 39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 40 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 41 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 42 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 43 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 44 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 45 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 46 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 47 | DEALINGS IN THE SOFTWARE. 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Serde Regex 2 | ============ 3 | 4 | [Documentation](https://docs.rs/serde_regex) | 5 | [Github](https://github.com/tailhook/serde-regex) | 6 | [Crate](https://crates.io/crates/serde_regex) 7 | 8 | A serde wrapper, that can be used to serialize regular expressions as strings. 9 | It's often useful to read regexes from configuration file. 10 | 11 | Note: regex is read with default settings. So DoS attack is probably possible 12 | if reading regex from untrusted source. I.e. reading from config file is 13 | okay, reading from API request is not. 14 | 15 | 16 | Example 17 | ------- 18 | 19 | ```rust 20 | #[macro_use] 21 | extern crate serde_derive; 22 | 23 | extern crate serde; 24 | extern crate serde_regex; 25 | 26 | use regex::Regex; 27 | 28 | #[derive(Serialize, Deserialize)] 29 | struct Timestamps { 30 | #[serde(with = "serde_regex")] 31 | pattern: Regex, 32 | } 33 | ``` 34 | 35 | 36 | License 37 | ======= 38 | 39 | Licensed under either of 40 | 41 | * Apache License, Version 2.0, 42 | (./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) 43 | * MIT license (./LICENSE-MIT or http://opensource.org/licenses/MIT) 44 | at your option. 45 | 46 | Contribution 47 | ------------ 48 | 49 | Unless you explicitly state otherwise, any contribution intentionally 50 | submitted for inclusion in the work by you, as defined in the Apache-2.0 51 | license, shall be dual licensed as above, without any additional terms or 52 | conditions. 53 | 54 | -------------------------------------------------------------------------------- /bulk.yaml: -------------------------------------------------------------------------------- 1 | minimum-bulk: v0.4.5 2 | 3 | versions: 4 | 5 | - file: Cargo.toml 6 | block-start: ^\[package\] 7 | block-end: ^\[.*\] 8 | regex: ^version\s*=\s*"(\S+)" 9 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Serde Regex 2 | //! 3 | //! [Documentation](https://docs.rs/serde_regex) | 4 | //! [Github](https://github.com/tailhook/serde-regex) | 5 | //! [Crate](https://crates.io/crates/serde_regex) 6 | //! 7 | //! A (de)serializer for `regex::Regex` 8 | //! 9 | //! # Example 10 | //! 11 | //! ```rust 12 | //! 13 | //! use regex::Regex; 14 | //! use serde::{Deserialize, Serialize}; 15 | //! use serde_derive::{Serialize, Deserialize}; 16 | //! 17 | //! 18 | //! #[derive(Serialize, Deserialize)] 19 | //! struct Timestamps { 20 | //! #[serde(with = "serde_regex")] 21 | //! pattern: Regex, 22 | //! } 23 | //! 24 | //! # 25 | //! # fn main() {} 26 | //! ``` 27 | #![warn(missing_docs)] 28 | #![warn(missing_debug_implementations)] 29 | 30 | use regex::{Regex, RegexSet, bytes}; 31 | use std::{ 32 | borrow::Cow, 33 | collections::HashMap, 34 | fmt, 35 | hash::{BuildHasher, Hash}, 36 | marker::PhantomData, 37 | ops::{Deref, DerefMut} 38 | }; 39 | 40 | use serde::{ 41 | Deserialize, 42 | Deserializer, 43 | Serialize, 44 | Serializer, 45 | de::{Error, MapAccess, SeqAccess, Visitor}, 46 | ser::{SerializeMap, SerializeSeq} 47 | }; 48 | 49 | /// A wrapper type which implements `Serialize` and `Deserialize` for 50 | /// types involving `Regex` 51 | #[derive(Debug, Clone, Eq, Hash, PartialEq)] 52 | pub struct Serde(pub T); 53 | 54 | struct RegexVecVisitor; 55 | struct BytesRegexVecVisitor; 56 | 57 | impl<'a> Visitor<'a> for RegexVecVisitor { 58 | type Value = Serde>; 59 | 60 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 61 | formatter.write_str("valid sequence") 62 | } 63 | fn visit_seq(self, mut seq: A) -> Result 64 | where 65 | A: SeqAccess<'a>, 66 | { 67 | let mut vec = match seq.size_hint() { 68 | Some(size) => Vec::with_capacity(size), 69 | None => Vec::new(), 70 | }; 71 | while let Some(Serde(el)) = seq.next_element()? { 72 | vec.push(el); 73 | } 74 | return Ok(Serde(vec)); 75 | } 76 | } 77 | 78 | impl<'a> Visitor<'a> for BytesRegexVecVisitor { 79 | type Value = Serde>; 80 | 81 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 82 | formatter.write_str("valid sequence") 83 | } 84 | fn visit_seq(self, mut seq: A) -> Result 85 | where 86 | A: SeqAccess<'a>, 87 | { 88 | let mut vec = match seq.size_hint() { 89 | Some(size) => Vec::with_capacity(size), 90 | None => Vec::new(), 91 | }; 92 | while let Some(Serde(el)) = seq.next_element()? { 93 | vec.push(el); 94 | } 95 | return Ok(Serde(vec)); 96 | } 97 | } 98 | 99 | 100 | struct RegexHashMapVisitor(PhantomData<(K, S)>); 101 | struct BytesRegexHashMapVisitor(PhantomData<(K, S)>); 102 | 103 | impl Default for RegexHashMapVisitor { 104 | fn default() -> Self { 105 | Self(Default::default()) 106 | } 107 | } 108 | 109 | impl Default for BytesRegexHashMapVisitor { 110 | fn default() -> Self { 111 | Self(Default::default()) 112 | } 113 | } 114 | 115 | 116 | impl<'a, K, S> Visitor<'a> for RegexHashMapVisitor 117 | where 118 | K: Hash + Eq + Deserialize<'a>, 119 | S: BuildHasher + Default, 120 | { 121 | type Value = Serde>; 122 | 123 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 124 | formatter.write_str("valid map") 125 | } 126 | fn visit_map(self, mut map: A) -> Result 127 | where 128 | A: MapAccess<'a> 129 | { 130 | let mut hashmap = match map.size_hint() { 131 | Some(size) => HashMap::with_capacity_and_hasher(size, S::default()), 132 | None => HashMap::with_hasher(S::default()), 133 | }; 134 | while let Some((key, Serde(value))) = map.next_entry()? { 135 | hashmap.insert(key, value); 136 | } 137 | return Ok(Serde(hashmap)); 138 | } 139 | } 140 | 141 | impl<'a, K, S> Visitor<'a> for BytesRegexHashMapVisitor 142 | where 143 | K: Hash + Eq + Deserialize<'a>, 144 | S: BuildHasher + Default, 145 | { 146 | type Value = Serde>; 147 | 148 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 149 | formatter.write_str("valid map") 150 | } 151 | fn visit_map(self, mut map: A) -> Result 152 | where 153 | A: MapAccess<'a> 154 | { 155 | let mut hashmap = match map.size_hint() { 156 | Some(size) => HashMap::with_capacity_and_hasher(size, S::default()), 157 | None => HashMap::with_hasher(S::default()), 158 | }; 159 | while let Some((key, Serde(value))) = map.next_entry()? { 160 | hashmap.insert(key, value); 161 | } 162 | return Ok(Serde(hashmap)); 163 | } 164 | } 165 | 166 | 167 | impl<'de> Deserialize<'de> for Serde> { 168 | fn deserialize(d: D) -> Result>, D::Error> 169 | where 170 | D: Deserializer<'de>, 171 | { 172 | match Option::>::deserialize(d)? { 173 | Some(Serde(regex)) => Ok(Serde(Some(regex))), 174 | None => Ok(Serde(None)), 175 | } 176 | } 177 | } 178 | 179 | impl<'de> Deserialize<'de> for Serde { 180 | fn deserialize(d: D) -> Result, D::Error> 181 | where 182 | D: Deserializer<'de>, 183 | { 184 | let s = >::deserialize(d)?; 185 | 186 | match s.parse() { 187 | Ok(regex) => Ok(Serde(regex)), 188 | Err(err) => Err(D::Error::custom(err)), 189 | } 190 | } 191 | } 192 | 193 | impl<'de> Deserialize<'de> for Serde> { 194 | fn deserialize(d: D) -> Result>, D::Error> 195 | where 196 | D: Deserializer<'de>, 197 | { 198 | match Option::>::deserialize(d)? { 199 | Some(Serde(regexset)) => Ok(Serde(Some(regexset))), 200 | None => Ok(Serde(None)), 201 | } 202 | } 203 | } 204 | 205 | impl<'de> Deserialize<'de> for Serde { 206 | fn deserialize(d: D) -> Result, D::Error> 207 | where 208 | D: Deserializer<'de>, 209 | { 210 | let regexes = >>::deserialize(d)?; 211 | 212 | match RegexSet::new(regexes) { 213 | Ok(regexset) => Ok(Serde(regexset)), 214 | Err(err) => Err(D::Error::custom(err)), 215 | } 216 | } 217 | } 218 | 219 | impl<'de> Deserialize<'de> for Serde> { 220 | fn deserialize(d: D) -> Result>, D::Error> 221 | where 222 | D: Deserializer<'de>, 223 | { 224 | d.deserialize_seq(RegexVecVisitor) 225 | } 226 | } 227 | 228 | impl<'de, K, S> Deserialize<'de> for Serde> 229 | where 230 | K: Hash + Eq + Deserialize<'de>, 231 | S: BuildHasher + Default, 232 | { 233 | fn deserialize(d: D) -> Result 234 | where 235 | D: Deserializer<'de>, 236 | { 237 | d.deserialize_map(RegexHashMapVisitor::default()) 238 | } 239 | } 240 | 241 | impl<'de> Deserialize<'de> for Serde>> { 242 | fn deserialize(d: D) -> Result>>, D::Error> 243 | where 244 | D: Deserializer<'de>, 245 | { 246 | match Option::>>::deserialize(d)? { 247 | Some(Serde(regex)) => Ok(Serde(Some(regex))), 248 | None => Ok(Serde(None)), 249 | } 250 | } 251 | } 252 | 253 | impl<'de, K, S> Deserialize<'de> for Serde>> 254 | where 255 | K: Hash + Eq + Deserialize<'de>, 256 | S: BuildHasher + Default, 257 | { 258 | fn deserialize(d: D) -> Result 259 | where 260 | D: Deserializer<'de>, 261 | { 262 | match Option::>>::deserialize(d)? { 263 | Some(Serde(map)) => Ok(Serde(Some(map))), 264 | None => Ok(Serde(None)), 265 | } 266 | } 267 | } 268 | 269 | impl<'de> Deserialize<'de> for Serde>> { 270 | fn deserialize(d: D) -> Result>>, D::Error> 271 | where 272 | D: Deserializer<'de>, 273 | { 274 | match Option::>>::deserialize(d)? { 275 | Some(Serde(regex)) => Ok(Serde(Some(regex))), 276 | None => Ok(Serde(None)), 277 | } 278 | } 279 | } 280 | 281 | impl<'de, K, S> Deserialize<'de> for Serde>> 282 | where 283 | K: Hash + Eq + Deserialize<'de>, 284 | S: BuildHasher + Default, 285 | { 286 | fn deserialize(d: D) -> Result 287 | where 288 | D: Deserializer<'de>, 289 | { 290 | match Option::>>::deserialize(d)? { 291 | Some(Serde(map)) => Ok(Serde(Some(map))), 292 | None => Ok(Serde(None)), 293 | } 294 | } 295 | } 296 | 297 | impl<'de> Deserialize<'de> for Serde> { 298 | fn deserialize(d: D) -> Result>, D::Error> 299 | where 300 | D: Deserializer<'de>, 301 | { 302 | match Option::>::deserialize(d)? { 303 | Some(Serde(regex)) => Ok(Serde(Some(regex))), 304 | None => Ok(Serde(None)), 305 | } 306 | } 307 | } 308 | 309 | impl<'de> Deserialize<'de> for Serde { 310 | fn deserialize(d: D) -> Result, D::Error> 311 | where 312 | D: Deserializer<'de>, 313 | { 314 | let s = >::deserialize(d)?; 315 | 316 | match s.parse() { 317 | Ok(regex) => Ok(Serde(regex)), 318 | Err(err) => Err(D::Error::custom(err)), 319 | } 320 | } 321 | } 322 | 323 | impl<'de> Deserialize<'de> for Serde { 324 | fn deserialize(d: D) -> Result, D::Error> 325 | where 326 | D: Deserializer<'de>, 327 | { 328 | let regexes = >>::deserialize(d)?; 329 | 330 | match bytes::RegexSet::new(regexes) { 331 | Ok(regexset) => Ok(Serde(regexset)), 332 | Err(err) => Err(D::Error::custom(err)), 333 | } 334 | } 335 | } 336 | 337 | impl<'de> Deserialize<'de> for Serde> { 338 | fn deserialize(d: D) -> Result>, D::Error> 339 | where 340 | D: Deserializer<'de>, 341 | { 342 | d.deserialize_seq(BytesRegexVecVisitor) 343 | } 344 | } 345 | impl<'de, K, S> Deserialize<'de> for Serde> 346 | where 347 | K: Hash + Eq + Deserialize<'de>, 348 | S: BuildHasher + Default, 349 | { 350 | fn deserialize(d: D) -> Result 351 | where 352 | D: Deserializer<'de>, 353 | { 354 | d.deserialize_map(BytesRegexHashMapVisitor::default()) 355 | } 356 | } 357 | /// Deserialize function, see crate docs to see how to use it 358 | pub fn deserialize<'de, T, D>(deserializer: D) -> Result 359 | where 360 | D: Deserializer<'de>, 361 | Serde: Deserialize<'de>, 362 | { 363 | Serde::deserialize(deserializer).map(|x| x.0) 364 | } 365 | 366 | /// Serialize function, see crate docs to see how to use it 367 | pub fn serialize(value: &T, serializer: S) -> Result 368 | where 369 | S: Serializer, 370 | for<'a> Serde<&'a T>: Serialize, 371 | { 372 | Serde(value).serialize(serializer) 373 | } 374 | 375 | impl Deref for Serde { 376 | type Target = T; 377 | 378 | fn deref(&self) -> &T { 379 | &self.0 380 | } 381 | } 382 | 383 | impl DerefMut for Serde { 384 | fn deref_mut(&mut self) -> &mut T { 385 | &mut self.0 386 | } 387 | } 388 | 389 | impl Serde { 390 | /// Consumes the `Serde`, returning the inner value. 391 | pub fn into_inner(self) -> T { 392 | self.0 393 | } 394 | } 395 | 396 | impl From for Serde { 397 | fn from(val: T) -> Serde { 398 | Serde(val) 399 | } 400 | } 401 | 402 | impl<'a> Serialize for Serde<&'a Regex> { 403 | fn serialize(&self, serializer: S) -> Result 404 | where 405 | S: Serializer, 406 | { 407 | self.0.as_str().serialize(serializer) 408 | } 409 | } 410 | 411 | impl Serialize for Serde { 412 | fn serialize(&self, serializer: S) -> Result 413 | where 414 | S: Serializer, 415 | { 416 | self.0.as_str().serialize(serializer) 417 | } 418 | } 419 | 420 | impl Serialize for Serde> { 421 | fn serialize(&self, serializer: S) -> Result 422 | where 423 | S: Serializer, 424 | { 425 | match self.0 { 426 | Some(ref value) => value.patterns().serialize(serializer), 427 | None => serializer.serialize_none(), 428 | } 429 | } 430 | } 431 | 432 | impl Serialize for Serde { 433 | fn serialize(&self, serializer: S) -> Result 434 | where 435 | S: Serializer, 436 | { 437 | self.0.patterns().serialize(serializer) 438 | } 439 | } 440 | 441 | impl<'a> Serialize for Serde<&'a Option> { 442 | fn serialize(&self, serializer: S) -> Result 443 | where 444 | S: Serializer, 445 | { 446 | match self.0 { 447 | &Some(ref value) => serializer.serialize_some(&Serde(value)), 448 | &None => serializer.serialize_none(), 449 | } 450 | } 451 | } 452 | 453 | impl Serialize for Serde> { 454 | fn serialize(&self, serializer: S) -> Result 455 | where 456 | S: Serializer, 457 | { 458 | Serde(&self.0).serialize(serializer) 459 | } 460 | } 461 | 462 | impl Serialize for Serde> { 463 | fn serialize(&self, serializer: S) -> Result 464 | where 465 | S: Serializer, 466 | { 467 | Serde(&self.0).serialize(serializer) 468 | } 469 | } 470 | 471 | impl Serialize for Serde> 472 | where 473 | K: Hash + Eq + Serialize, 474 | S: BuildHasher + Default, 475 | { 476 | fn serialize(&self, serializer: Se) -> Result 477 | where 478 | Se: Serializer, 479 | { 480 | Serde(&self.0).serialize(serializer) 481 | } 482 | } 483 | 484 | impl<'a> Serialize for Serde<&'a Vec> { 485 | fn serialize(&self, serializer: S) -> Result 486 | where 487 | S: Serializer, 488 | { 489 | let mut seq = serializer.serialize_seq(Some(self.len()))?; 490 | for element in self.0 { 491 | seq.serialize_element(&Serde(element))?; 492 | } 493 | seq.end() 494 | } 495 | } 496 | 497 | impl<'a> Serialize for Serde<&'a Option>> { 498 | fn serialize(&self, serializer: S) -> Result 499 | where 500 | S: Serializer, 501 | { 502 | match self.0 { 503 | &Some(ref value) => serializer.serialize_some(&Serde(value)), 504 | &None => serializer.serialize_none(), 505 | } 506 | } 507 | } 508 | 509 | impl<'a, K, S> Serialize for Serde<&'a HashMap> 510 | where 511 | K: Hash + Eq + Serialize, 512 | S: BuildHasher + Default, 513 | { 514 | fn serialize(&self, serializer: Se) -> Result 515 | where 516 | Se: Serializer, 517 | { 518 | let mut map = serializer.serialize_map(Some(self.len()))?; 519 | for (key, value) in self.0.iter() { 520 | map.serialize_entry(key, &Serde(value))?; 521 | } 522 | map.end() 523 | } 524 | } 525 | 526 | impl<'a> Serialize for Serde<&'a bytes::Regex> { 527 | fn serialize(&self, serializer: S) -> Result 528 | where 529 | S: Serializer, 530 | { 531 | self.0.as_str().serialize(serializer) 532 | } 533 | } 534 | 535 | impl Serialize for Serde { 536 | fn serialize(&self, serializer: S) -> Result 537 | where 538 | S: Serializer, 539 | { 540 | self.0.as_str().serialize(serializer) 541 | } 542 | } 543 | 544 | impl<'a> Serialize for Serde<&'a Option> { 545 | fn serialize(&self, serializer: S) -> Result 546 | where 547 | S: Serializer, 548 | { 549 | match self.0 { 550 | &Some(ref value) => serializer.serialize_some(&Serde(value)), 551 | &None => serializer.serialize_none(), 552 | } 553 | } 554 | } 555 | 556 | impl Serialize for Serde> { 557 | fn serialize(&self, serializer: S) -> Result 558 | where 559 | S: Serializer, 560 | { 561 | Serde(&self.0).serialize(serializer) 562 | } 563 | } 564 | 565 | impl Serialize for Serde { 566 | fn serialize(&self, serializer: S) -> Result 567 | where 568 | S: Serializer, 569 | { 570 | self.0.patterns().serialize(serializer) 571 | } 572 | } 573 | 574 | impl Serialize for Serde> { 575 | fn serialize(&self, serializer: S) -> Result 576 | where 577 | S: Serializer, 578 | { 579 | Serde(&self.0).serialize(serializer) 580 | } 581 | } 582 | 583 | impl<'a> Serialize for Serde<&'a Option>> { 584 | fn serialize(&self, serializer: S) -> Result 585 | where 586 | S: Serializer, 587 | { 588 | match self.0 { 589 | &Some(ref value) => serializer.serialize_some(&Serde(value)), 590 | &None => serializer.serialize_none(), 591 | } 592 | } 593 | } 594 | 595 | impl Serialize for Serde> 596 | where 597 | K: Hash + Eq + Serialize, 598 | S: BuildHasher + Default, 599 | { 600 | fn serialize(&self, serializer: Se) -> Result 601 | where 602 | Se: Serializer, 603 | { 604 | Serde(&self.0).serialize(serializer) 605 | } 606 | } 607 | 608 | impl<'a> Serialize for Serde<&'a Vec> { 609 | fn serialize(&self, serializer: S) -> Result 610 | where 611 | S: Serializer, 612 | { 613 | let mut seq = serializer.serialize_seq(Some(self.len()))?; 614 | for element in self.0 { 615 | seq.serialize_element(&Serde(element))?; 616 | } 617 | seq.end() 618 | } 619 | } 620 | 621 | impl<'a, K, S> Serialize for Serde<&'a HashMap> 622 | where 623 | K: Hash + Eq + Serialize, 624 | S: BuildHasher + Default, 625 | { 626 | fn serialize(&self, serializer: Se) -> Result 627 | where 628 | Se: Serializer, 629 | { 630 | let mut map = serializer.serialize_map(Some(self.len()))?; 631 | for (key, value) in self.0.iter() { 632 | map.serialize_entry(key, &Serde(value))?; 633 | } 634 | map.end() 635 | } 636 | } 637 | 638 | #[cfg(test)] 639 | mod test { 640 | use std::collections::HashMap; 641 | 642 | use serde_json::{json, from_value, from_str, to_string, to_value}; 643 | use regex::{Regex, RegexSet, bytes}; 644 | use crate::Serde; 645 | 646 | const SAMPLE: &str = r#"[a-z"\]]+\d{1,10}""#; 647 | const SAMPLE_JSON: &str = r#""[a-z\"\\]]+\\d{1,10}\"""#; 648 | 649 | #[test] 650 | fn test_set() -> Result<(), Box> { 651 | let regexes = &[ 652 | "my(regex)?", 653 | "other[regex]+", 654 | ]; 655 | let json = json!(regexes); 656 | let set: Serde = from_value(json.clone())?; 657 | assert_eq!(set.patterns(), regexes); 658 | assert_eq!( 659 | to_value(set).expect("serialization error"), 660 | json 661 | ); 662 | Ok(()) 663 | } 664 | 665 | #[test] 666 | fn test_set_option_some() -> Result<(), Box> { 667 | let regexes = &[ 668 | "my(regex)?", 669 | "other[regex]+", 670 | ]; 671 | let json = json!(regexes); 672 | let set: Serde> = from_value(json.clone())?; 673 | assert_eq!(set.as_ref().unwrap().patterns(), regexes); 674 | assert_eq!( 675 | to_value(set).expect("serialization error"), 676 | json 677 | ); 678 | Ok(()) 679 | } 680 | 681 | #[test] 682 | fn test_set_option_none() -> Result<(), Box> { 683 | let set: Serde> = from_str("null").unwrap(); 684 | assert!(set.is_none()); 685 | assert_eq!(to_string(&set).unwrap(), "null"); 686 | Ok(()) 687 | } 688 | 689 | #[test] 690 | fn test_vec() -> Result<(), Box> { 691 | let json = json!(["a.*b", "c?d"]); 692 | let vec: Serde> = from_value(json)?; 693 | assert!(vec.0[0].as_str() == "a.*b"); 694 | assert!(vec.0[1].as_str() == "c?d"); 695 | assert!(vec.len() == 2); 696 | Ok(()) 697 | } 698 | 699 | #[test] 700 | fn test_hashmap() -> Result<(), Box> { 701 | let json = json!({"a": "a.*b", "b": "c?d"}); 702 | let map: Serde> = from_value(json)?; 703 | assert!(map.0["a"].as_str() == "a.*b"); 704 | assert!(map.0["b"].as_str() == "c?d"); 705 | assert!(map.len() == 2); 706 | Ok(()) 707 | } 708 | 709 | #[test] 710 | fn test_simple() { 711 | let re: Serde = from_str(SAMPLE_JSON).unwrap(); 712 | assert_eq!(re.as_str(), SAMPLE); 713 | assert_eq!(to_string(&re).unwrap(), SAMPLE_JSON); 714 | } 715 | 716 | #[test] 717 | fn test_option_some() { 718 | let re: Serde> = from_str(SAMPLE_JSON).unwrap(); 719 | assert_eq!(re.as_ref().map(|regex| regex.as_str()), Some(SAMPLE)); 720 | assert_eq!(to_string(&re).unwrap(), SAMPLE_JSON); 721 | } 722 | 723 | #[test] 724 | fn test_option_none() { 725 | let re: Serde> = from_str("null").unwrap(); 726 | assert!(re.is_none()); 727 | assert_eq!(to_string(&re).unwrap(), "null"); 728 | } 729 | 730 | #[test] 731 | fn test_set_bytes() -> Result<(), Box> { 732 | let regexes = &[ 733 | "regex.*test", 734 | "test( )??regex+", 735 | ]; 736 | let json = json!(regexes); 737 | let set: Serde = from_value(json.clone())?; 738 | assert_eq!(set.patterns(), regexes); 739 | assert_eq!( 740 | to_value(set).expect("serialization error"), 741 | json 742 | ); 743 | Ok(()) 744 | } 745 | 746 | #[test] 747 | fn test_vec_bytes() -> Result<(), Box> { 748 | let json = json!(["a.*b", "c?d"]); 749 | let vec: Serde> = from_value(json)?; 750 | assert!(vec.0[0].as_str() == "a.*b"); 751 | assert!(vec.0[1].as_str() == "c?d"); 752 | assert!(vec.len() == 2); 753 | Ok(()) 754 | } 755 | 756 | #[test] 757 | fn test_hashmap_bytes() -> Result<(), Box> { 758 | // let json = json!(["a.*b", "c?d"]); 759 | let json = json!({ "c": "a.*b", "d": "c?d" }); 760 | let map: Serde> = from_value(json)?; 761 | assert!(map.0["c"].as_str() == "a.*b"); 762 | assert!(map.0["d"].as_str() == "c?d"); 763 | assert!(map.len() == 2); 764 | Ok(()) 765 | } 766 | 767 | #[test] 768 | fn test_option_vec() -> Result<(), Box> { 769 | let json = json!(["a.*b", "c?d"]); 770 | let vec: Serde>> = from_value(json)?; 771 | assert!(vec.is_some()); 772 | let v = vec.0.unwrap(); 773 | assert!(v[0].as_str() == "a.*b"); 774 | assert!(v[1].as_str() == "c?d"); 775 | assert!(v.len() == 2); 776 | Ok(()) 777 | } 778 | #[test] 779 | fn test_option_hashmap() -> Result<(), Box> { 780 | let json = json!({"a": "a.*b", "b": "c?d"}); 781 | let map: Serde>> = from_value(json)?; 782 | assert!(map.is_some()); 783 | let v = map.0.unwrap(); 784 | assert!(v["a"].as_str() == "a.*b"); 785 | assert!(v["b"].as_str() == "c?d"); 786 | assert!(v.len() == 2); 787 | Ok(()) 788 | } 789 | #[test] 790 | fn test_option_vec_bytes() -> Result<(), Box> { 791 | let json = json!(["a.*b", "c?d"]); 792 | let vec: Serde>> = from_value(json)?; 793 | assert!(vec.is_some()); 794 | let v = vec.0.unwrap(); 795 | assert!(v[0].as_str() == "a.*b"); 796 | assert!(v[1].as_str() == "c?d"); 797 | assert!(v.len() == 2); 798 | Ok(()) 799 | } 800 | #[test] 801 | fn test_option_hashamp_bytes() -> Result<(), Box> { 802 | let json = json!({"a": "a.*b", "b": "c?d"}); 803 | let map: Serde>> = from_value(json)?; 804 | assert!(map.is_some()); 805 | let v = map.0.unwrap(); 806 | assert!(v["a"].as_str() == "a.*b"); 807 | assert!(v["b"].as_str() == "c?d"); 808 | assert!(v.len() == 2); 809 | Ok(()) 810 | } 811 | #[test] 812 | fn test_option_vec_none() -> Result<(), Box> { 813 | let vec: Serde>> = from_str("null")?; 814 | assert!(vec.is_none()); 815 | Ok(()) 816 | } 817 | #[test] 818 | fn test_option_hashmap_none() -> Result<(), Box> { 819 | let map: Serde>> = from_str("null")?; 820 | assert!(map.is_none()); 821 | Ok(()) 822 | } 823 | 824 | #[test] 825 | fn test_bytes_simple() { 826 | let re: Serde = from_str(SAMPLE_JSON).unwrap(); 827 | assert_eq!(re.as_str(), SAMPLE); 828 | assert_eq!(to_string(&re).unwrap(), SAMPLE_JSON); 829 | } 830 | 831 | #[test] 832 | fn test_bytes_option_some() { 833 | let re: Serde> = from_str(SAMPLE_JSON).unwrap(); 834 | assert_eq!(re.as_ref().map(|regex| regex.as_str()), Some(SAMPLE)); 835 | assert_eq!(to_string(&re).unwrap(), SAMPLE_JSON); 836 | } 837 | 838 | #[test] 839 | fn test_bytes_option_none() { 840 | let re: Serde> = from_str("null").unwrap(); 841 | assert!(re.is_none()); 842 | assert_eq!(to_string(&re).unwrap(), "null"); 843 | } 844 | } 845 | -------------------------------------------------------------------------------- /vagga.yaml: -------------------------------------------------------------------------------- 1 | commands: 2 | 3 | make: !Command 4 | description: Build the library 5 | container: ubuntu 6 | run: [cargo, build] 7 | 8 | cargo: !Command 9 | description: Run arbitrary cargo command 10 | symlink-name: cargo 11 | container: ubuntu 12 | run: [cargo] 13 | 14 | test: !Command 15 | description: Run tests 16 | container: ubuntu 17 | run: [cargo, test] 18 | 19 | _bulk: !Command 20 | description: Run `bulk` command (for version bookkeeping) 21 | container: ubuntu 22 | run: [bulk] 23 | 24 | containers: 25 | 26 | ubuntu: 27 | setup: 28 | - !Ubuntu bionic 29 | - !Install [ca-certificates, git, build-essential, vim] 30 | 31 | - !TarInstall 32 | url: "https://static.rust-lang.org/dist/rust-1.34.0-x86_64-unknown-linux-gnu.tar.gz" 33 | script: "./install.sh --prefix=/usr \ 34 | --components=rustc,rust-std-x86_64-unknown-linux-gnu,cargo" 35 | - &bulk !Tar 36 | url: "https://github.com/tailhook/bulk/releases/download/v0.4.11/bulk-v0.4.11.tar.gz" 37 | sha256: b718bb8448e726690c94d98d004bf7575f7a429106ec26ad3faf11e0fd9a7978 38 | path: / 39 | 40 | environ: 41 | HOME: /work/target 42 | --------------------------------------------------------------------------------