diff options
Diffstat (limited to 'third_party/rust/serde_with_macros')
-rw-r--r-- | third_party/rust/serde_with_macros/.cargo-checksum.json | 1 | ||||
-rw-r--r-- | third_party/rust/serde_with_macros/CHANGELOG.md | 296 | ||||
-rw-r--r-- | third_party/rust/serde_with_macros/Cargo.toml | 105 | ||||
-rw-r--r-- | third_party/rust/serde_with_macros/LICENSE-APACHE | 201 | ||||
-rw-r--r-- | third_party/rust/serde_with_macros/LICENSE-MIT | 25 | ||||
-rw-r--r-- | third_party/rust/serde_with_macros/README.md | 212 | ||||
-rw-r--r-- | third_party/rust/serde_with_macros/src/apply.rs | 313 | ||||
-rw-r--r-- | third_party/rust/serde_with_macros/src/lib.rs | 1318 | ||||
-rw-r--r-- | third_party/rust/serde_with_macros/src/utils.rs | 77 | ||||
-rw-r--r-- | third_party/rust/serde_with_macros/tests/apply.rs | 247 | ||||
-rw-r--r-- | third_party/rust/serde_with_macros/tests/compiler-messages.rs | 9 | ||||
-rw-r--r-- | third_party/rust/serde_with_macros/tests/serde_as_issue_267.rs | 9 | ||||
-rw-r--r-- | third_party/rust/serde_with_macros/tests/serde_as_issue_538.rs | 30 | ||||
-rw-r--r-- | third_party/rust/serde_with_macros/tests/skip_serializing_null.rs | 149 | ||||
-rw-r--r-- | third_party/rust/serde_with_macros/tests/version_numbers.rs | 21 |
15 files changed, 3013 insertions, 0 deletions
diff --git a/third_party/rust/serde_with_macros/.cargo-checksum.json b/third_party/rust/serde_with_macros/.cargo-checksum.json new file mode 100644 index 0000000000..5637cb9953 --- /dev/null +++ b/third_party/rust/serde_with_macros/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CHANGELOG.md":"63e4a849e730096c2c3817b3fe800abced26280c3a5f82021def0d927abdcce5","Cargo.toml":"61849c1007b396ccea0f8e64980c2a808df6b0cdfe25f2817b1246ee0e089ebc","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"7576269ea71f767b99297934c0b2367532690f8c4badc695edf8e04ab6a1e545","README.md":"e678bfd60449ac137d780b29fcae8d3ecb706f43e8060369f7269151a0844c1a","src/apply.rs":"027092a97792cd40d5caf8919241009d352f20a064916c0410e0b669b6d6ffe2","src/lib.rs":"267d3ecd05f1009737b7810526b9a3325fb28190ce54f3602980f6e57b9a2128","src/utils.rs":"89588e77aff8d902cb733081453d1aef4a3566b378ec4e78b2f5c5a17ca83db2","tests/apply.rs":"7875d812f2a0342f2f7cbd9dfc0c5d2ca9a1d39290b79f727b454251c521454e","tests/compiler-messages.rs":"8dca4660b6d84d5c6a401a136b66f1fd98b2cccbab4a32f1c35590d21ce67970","tests/serde_as_issue_267.rs":"caaa744bd0fa854d58f8bd28214b7394e8bdd09cadc864a5d87db0c6b1ab491d","tests/serde_as_issue_538.rs":"50b131baf53812ae458b31b42f3b440d0b2508803f83d94204f7bdf600400c27","tests/skip_serializing_null.rs":"779f5478ed2ba80c312ae9e88c9ea0d0e53ebfd13ebb8c1d42f866e619521404","tests/version_numbers.rs":"63386f8e98a17ab28c15c21b1cb5357b351bee9c513b7b1693c9d9bb482cf15a"},"package":"edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070"}
\ No newline at end of file diff --git a/third_party/rust/serde_with_macros/CHANGELOG.md b/third_party/rust/serde_with_macros/CHANGELOG.md new file mode 100644 index 0000000000..07ed2102e9 --- /dev/null +++ b/third_party/rust/serde_with_macros/CHANGELOG.md @@ -0,0 +1,296 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [3.0.0] - 2023-05-01 + +No changes. + +## [2.3.3] - 2023-04-27 + +### Changed + +* Update `syn` to v2 and `darling` to v0.20 (#578) + Update proc-macro dependencies. + This change should have no impact on users, but now uses the same dependency as `serde_derive`. + +## [2.3.2] - 2023-04-05 + +No changes. + +## [2.3.1] - 2023-03-10 + +No changes. + +## [2.3.0] - 2023-03-09 + +No changes. + +## [2.2.0] - 2023-01-09 + +### Fixed + +* `serde_with::apply` had an issue matching types when invisible token groups where in use (#538) + The token groups can stem from macro_rules expansion, but should be treated mostly transparent. + The old code required a group to match a group, while now groups are silently removed when checking for type patterns. + +## [2.1.0] - 2022-11-16 + +### Added + +* Add new `apply` attribute to simplify repetitive attributes over many fields. + Multiple rules and multiple attributes can be provided each. + + ```rust + #[serde_with::apply( + Option => #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")], + Option<bool> => #[serde(rename = "bool")], + )] + #[derive(serde::Serialize)] + struct Data { + a: Option<String>, + b: Option<u64>, + c: Option<String>, + d: Option<bool>, + } + ``` + + The `apply` attribute will expand into this, applying the attributs to the matching fields: + + ```rust + #[derive(serde::Serialize)] + struct Data { + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + a: Option<String>, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + b: Option<u64>, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + c: Option<String>, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "bool")] + d: Option<bool>, + } + ``` + + The attribute supports field matching using many rules, such as `_` to apply to all fields and partial generics like `Option` to match any `Option` be it `Option<String>`, `Option<bool>`, or `Option<T>`. + +### Fixed + +* The derive macros `SerializeDisplay` and `DeserializeFromStr` now take better care not to use conflicting names for generic values. (#526) + All used generics now start with `__` to make conflicts with manually written code unlikely. + + Thanks to @Elrendio for submitting a PR fixing the issue. + +## [2.0.1] - 2022-09-09 + +### Changed + +* Warn if `serde_as` is used on an enum variant. + Attributes on enum variants were never supported. + But `#[serde(with = "...")]` can be added on variants, such that some confusion can occur when migration ([#499](https://github.com/jonasbb/serde_with/issues/499)). + +## [2.0.0] - 2022-07-17 + +No changes compared to v2.0.0-rc.0. + +### Changed + +* Make `#[serde_as]` behave more intuitive on `Option<T>` fields. + + The `#[serde_as]` macro now detects if a `#[serde_as(as = "Option<S>")]` is used on a field of type `Option<T>` and applies `#[serde(default)]` to the field. + This restores the ability to deserialize with missing fields and fixes a common annoyance (#183, #185, #311, #417). + This is a breaking change, since now deserialization will pass where it did not before and this might be undesired. + + The `Option` field and transformation are detected by directly matching on the type name. + These variants are detected as `Option`. + * `Option` + * `std::option::Option`, with or without leading `::` + * `core::option::Option`, with or without leading `::` + + If an existing `default` attribute is detected, the attribute is not applied again. + This behavior can be supressed by using `#[serde_as(no_default)]` or `#[serde_as(as = "Option<S>", no_default)]`. + +### Fixed + +* Make the documentation clearer by stating that the `#[serde_as]` and `#[skip_serializing_none]` attributes must always be placed before `#[derive]`. + +## [2.0.0-rc.0] - 2022-06-29 + +### Changed + +* Make `#[serde_as]` behave more intuitive on `Option<T>` fields. + + The `#[serde_as]` macro now detects if a `#[serde_as(as = "Option<S>")]` is used on a field of type `Option<T>` and applies `#[serde(default)]` to the field. + This restores the ability to deserialize with missing fields and fixes a common annoyance (#183, #185, #311, #417). + This is a breaking change, since now deserialization will pass where it did not before and this might be undesired. + + The `Option` field and transformation are detected by directly matching on the type name. + These variants are detected as `Option`. + * `Option` + * `std::option::Option`, with or without leading `::` + * `core::option::Option`, with or without leading `::` + + If an existing `default` attribute is detected, the attribute is not applied again. + This behavior can be supressed by using `#[serde_as(no_default)]` or `#[serde_as(as = "Option<S>", no_default)]`. + +### Fixed + +* Make the documentation clearer by stating that the `#[serde_as]` and `#[skip_serializing_none]` attributes must always be placed before `#[derive]`. + +## [1.5.2] - 2022-04-07 + +### Fixed + +* Account for generics when deriving implementations with `SerializeDisplay` and `DeserializeFromStr` #413 +* Provide better error messages when parsing types fails #423 + +## [1.5.1] - 2021-10-18 + +### Added + +* The minimal supported Rust version (MSRV) is now specified in the `Cargo.toml` via the `rust-version` field. The field is supported in Rust 1.56 and has no effect on versions before. + + More details: https://doc.rust-lang.org/nightly/cargo/reference/manifest.html#the-rust-version-field + +## [1.5.0] - 2021-09-04 + +### Added + +* Add the attribute `#[serde(borrow)]` on a field if `serde_as` is used in combination with the `BorrowCow` type. + +## [1.4.2] - 2021-06-07 + +### Fixed + +* Describe how the `serde_as` macro works on a high level. +* The derive macros `SerializeDisplay` and `DeserializeFromStr` were relying on the prelude where they were used. + Properly name all types and traits required for the expanded code to work. + The tests were improved to be better able to catch such problems. + +## [1.4.2] - 2021-02-16 + +### Fixed + +* Fix compiling when having a struct field without the `serde_as` annotation. + This broke in 1.4.0 [#267](https://github.com/jonasbb/serde_with/issues/267) + +## [1.4.0] - 2021-02-15 + +### Changed + +* Improve error messages when `#[serde_as(..)]` is misused as a field attribute. + Thanks to @Lehona for reporting the bug in #233. +* Internal cleanup for assembling and parsing attributes during `serde_as` processing. +* Change processing on `#[serde_as(...)]` attributes on fields. + + The attributes will no longer be stripped during proc-macro processing. + Instead, a private derive macro is applied to the struct/enum which captures them and makes them inert, thus allowing compilation. + + This should have no effect on the generated code and on the runtime behavior. + It eases integration of third-party crates with `serde_with`, since they can now process the `#[serde_as(...)]` field attributes reliably. + Before this was impossible for derive macros and lead to awkward ordering constraints on the attribute macros. + + Thanks to @Lehona for reporting this problem and to @dtolnay for suggesting the dummy derive macro. + +## [1.3.0] - 2020-11-22 + +### Added + +* Support specifying a path to the `serde_with` crate for the `serde_as` and derive macros. + This is useful when using crate renaming in Cargo.toml or while re-exporting the macros. + + Many thanks to @tobz1000 for raising the issue and contributing fixes. + +### Changed + +* Bump minimum supported rust version to 1.40.0 + +## [1.2.2] - 2020-10-06 + +### Fixed + +* @adwhit contributed an improvement to `DeserializeFromStr` which allows it to deserialize from bytes (#186). + This makes the derived implementation applicable in more situations. + +## [1.2.1] - 2020-10-04 + +### Fixed + +* The derive macros `SerializeDisplay` and `DeserializeFromStr` now use the properly namespaced types and traits. + This solves conflicts with `Result` if `Result` is not `std::result::Result`, e.g., a type alias. + Additionally, the code assumed that `FromStr` was in scope, which is now also not required. + + Thanks goes to @adwhit for reporting and fixing the problem in #186. + +## [1.2.0] - 2020-10-01 + +### Added + +* Add `serde_as` macro. Refer to the `serde_with` crate for details. +* Add two derive macros, `SerializeDisplay` and `DeserializeFromStr`, which implement the `Serialize`/`Deserialize` traits based on `Display` and `FromStr`. + This is in addition to the already existing methods like `DisplayFromStr`, which act locally, whereas the derive macros provide the traits expected by the rest of the ecosystem. + +### Changed + +* Convert the code to use 2018 edition. + +### Fixed + +* The `serde_as` macro now supports serde attributes and no longer panic on unrecognized values in the attribute. + +## [1.2.0-alpha.3] - 2020-08-16 + +### Added + +* Add two derive macros, `SerializeDisplay` and `DeserializeFromStr`, which implement the `Serialize`/`Deserialize` traits based on `Display` and `FromStr`. + This is in addition to the already existing methods like `DisplayFromStr`, which act locally, whereas the derive macros provide the traits expected by the rest of the ecosystem. + +## [1.2.0-alpha.2] - 2020-08-08 + +### Fixed + +* The `serde_as` macro now supports serde attributes and no longer panic on unrecognized values in the attribute. + +## [1.2.0-alpha.1] - 2020-06-27 + +### Added + +* Add `serde_as` macro. Refer to the `serde_with` crate for details. + +### Changed + +* Convert the code to use 2018 edition. + +## [1.1.0] - 2020-01-16 + +### Changed + +* Bump minimal Rust version to 1.36.0 to support Rust Edition 2018 +* Improved CI pipeline by running `cargo audit` and `tarpaulin` in all configurations now. + +## [1.0.1] - 2019-04-09 + +### Fixed + +* Features for the `syn` dependency were missing. + This was hidden due to the dev-dependencies whose features leaked into the normal build. + +## [1.0.0] - 2019-04-02 + +Initial Release + +### Added + +* Add `skip_serializing_none` attribute, which adds `#[serde(skip_serializing_if = "Option::is_none")]` for each Option in a struct. + This is helpful for APIs which have many optional fields. + The effect of can be negated by adding `serialize_always` on those fields, which should always be serialized. + Existing `skip_serializing_if` will never be modified and those fields keep their behavior. diff --git a/third_party/rust/serde_with_macros/Cargo.toml b/third_party/rust/serde_with_macros/Cargo.toml new file mode 100644 index 0000000000..e833eb60e0 --- /dev/null +++ b/third_party/rust/serde_with_macros/Cargo.toml @@ -0,0 +1,105 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.60" +name = "serde_with_macros" +version = "3.0.0" +authors = ["Jonas Bushart"] +include = [ + "src/**/*", + "tests/**/*", + "!tests/compile-fail/**", + "LICENSE-*", + "README.md", + "CHANGELOG.md", +] +description = "proc-macro library for serde_with" +documentation = "https://docs.rs/serde_with_macros/" +readme = "README.md" +keywords = [ + "serde", + "utilities", + "serialization", + "deserialization", +] +categories = ["encoding"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/jonasbb/serde_with/" + +[package.metadata.docs.rs] +all-features = true + +[package.metadata.release] +tag = false + +[[package.metadata.release.pre-release-replacements]] +file = "CHANGELOG.md" +replace = """ +[Unreleased] + +## [{{version}}] - {{date}}""" +search = '\[Unreleased\]' + +[[package.metadata.release.pre-release-replacements]] +file = "src/lib.rs" +replace = "https://docs.rs/serde_with/{{version}}/" +search = 'https://docs\.rs/serde_with/[\d.]+/' + +[[package.metadata.release.pre-release-replacements]] +file = "src/lib.rs" +replace = "https://docs.rs/serde_with_macros/{{version}}/" +search = 'https://docs\.rs/serde_with_macros/[\d.]+/' + +[lib] +proc-macro = true + +[dependencies.darling] +version = "0.20.0" + +[dependencies.proc-macro2] +version = "1.0.1" + +[dependencies.quote] +version = "1.0.0" + +[dependencies.syn] +version = "2.0.0" +features = [ + "full", + "parsing", +] + +[dev-dependencies.expect-test] +version = "1.4.0" + +[dev-dependencies.pretty_assertions] +version = "1.0.0" + +[dev-dependencies.rustversion] +version = "1.0.0" + +[dev-dependencies.serde] +version = "1.0.157" +features = ["derive"] + +[dev-dependencies.serde_json] +version = "1.0.25" + +[dev-dependencies.trybuild] +version = "1.0.80" + +[dev-dependencies.version-sync] +version = "0.9.1" + +[badges.maintenance] +status = "actively-developed" diff --git a/third_party/rust/serde_with_macros/LICENSE-APACHE b/third_party/rust/serde_with_macros/LICENSE-APACHE new file mode 100644 index 0000000000..16fe87b06e --- /dev/null +++ b/third_party/rust/serde_with_macros/LICENSE-APACHE @@ -0,0 +1,201 @@ + 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/third_party/rust/serde_with_macros/LICENSE-MIT b/third_party/rust/serde_with_macros/LICENSE-MIT new file mode 100644 index 0000000000..9203baa055 --- /dev/null +++ b/third_party/rust/serde_with_macros/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2015 + +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/third_party/rust/serde_with_macros/README.md b/third_party/rust/serde_with_macros/README.md new file mode 100644 index 0000000000..fd5c446f37 --- /dev/null +++ b/third_party/rust/serde_with_macros/README.md @@ -0,0 +1,212 @@ +# Custom de/serialization functions for Rust's [serde](https://serde.rs) + +[![crates.io badge](https://img.shields.io/crates/v/serde_with.svg)](https://crates.io/crates/serde_with/) +[![Build Status](https://github.com/jonasbb/serde_with/workflows/Rust%20CI/badge.svg)](https://github.com/jonasbb/serde_with) +[![codecov](https://codecov.io/gh/jonasbb/serde_with/branch/master/graph/badge.svg)](https://codecov.io/gh/jonasbb/serde_with) +[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4322/badge)](https://bestpractices.coreinfrastructure.org/projects/4322) +[![Rustexplorer](https://img.shields.io/badge/Try%20on-rustexplorer-lightgrey?logo=rust&logoColor=orange)](https://www.rustexplorer.com/b/py7ida) + +--- + +This crate provides custom de/serialization helpers to use in combination with [serde's with-annotation][with-annotation] and with the improved [`serde_as`][as-annotation]-annotation. +Some common use cases are: + +* De/Serializing a type using the `Display` and `FromStr` traits, e.g., for `u8`, `url::Url`, or `mime::Mime`. + Check [`DisplayFromStr`] for details. +* Support for arrays larger than 32 elements or using const generics. + With `serde_as` large arrays are supported, even if they are nested in other types. + `[bool; 64]`, `Option<[u8; M]>`, and `Box<[[u8; 64]; N]>` are all supported, as [this examples shows](#large-and-const-generic-arrays). +* Skip serializing all empty `Option` types with [`#[skip_serializing_none]`][skip_serializing_none]. +* Apply a prefix to each field name of a struct, without changing the de/serialize implementations of the struct using [`with_prefix!`][]. +* Deserialize a comma separated list like `#hash,#tags,#are,#great` into a `Vec<String>`. + Check the documentation for [`serde_with::StringWithSeparator::<CommaSeparator, T>`][StringWithSeparator]. + +### Getting Help + +**Check out the [user guide][user guide] to find out more tips and tricks about this crate.** + +For further help using this crate you can [open a new discussion](https://github.com/jonasbb/serde_with/discussions/new) or ask on [users.rust-lang.org](https://users.rust-lang.org/). +For bugs, please open a [new issue](https://github.com/jonasbb/serde_with/issues/new) on GitHub. + +## Use `serde_with` in your Project + +```bash +# Add the current version to your Cargo.toml +cargo add serde_with +``` + +The crate contains different features for integration with other common crates. +Check the [feature flags][] section for information about all available features. + +## Examples + +Annotate your struct or enum to enable the custom de/serializer. +The `#[serde_as]` attribute must be placed *before* the `#[derive]`. + +The `as` is analogous to the `with` attribute of serde. +You mirror the type structure of the field you want to de/serialize. +You can specify converters for the inner types of a field, e.g., `Vec<DisplayFromStr>`. +The default de/serialization behavior can be restored by using `_` as a placeholder, e.g., `BTreeMap<_, DisplayFromStr>`. + +### `DisplayFromStr` + +[![Rustexplorer](https://img.shields.io/badge/Try%20on-rustexplorer-lightgrey?logo=rust&logoColor=orange)](https://www.rustexplorer.com/b/py7ida) +```rust +#[serde_as] +#[derive(Deserialize, Serialize)] +struct Foo { + // Serialize with Display, deserialize with FromStr + #[serde_as(as = "DisplayFromStr")] + bar: u8, +} + +// This will serialize +Foo {bar: 12} + +// into this JSON +{"bar": "12"} +``` + +### Large and const-generic arrays + +serde does not support arrays with more than 32 elements or using const-generics. +The `serde_as` attribute allows circumventing this restriction, even for nested types and nested arrays. + +On top of it, `[u8; N]` (aka, bytes) can use the specialized `"Bytes"` for efficiency much like the `serde_bytes` crate. + +[![Rustexplorer](https://img.shields.io/badge/Try%20on-rustexplorer-lightgrey?logo=rust&logoColor=orange)](https://www.rustexplorer.com/b/um0xyi) +```rust +#[serde_as] +#[derive(Deserialize, Serialize)] +struct Arrays<const N: usize, const M: usize> { + #[serde_as(as = "[_; N]")] + constgeneric: [bool; N], + + #[serde_as(as = "Box<[[_; 64]; N]>")] + nested: Box<[[u8; 64]; N]>, + + #[serde_as(as = "Option<[_; M]>")] + optional: Option<[u8; M]>, + + #[serde_as(as = "Bytes")] + bytes: [u8; M], +} + +// This allows us to serialize a struct like this +let arrays: Arrays<100, 128> = Arrays { + constgeneric: [true; 100], + nested: Box::new([[111; 64]; 100]), + optional: Some([222; 128]), + bytes: [0x42; 128], +}; +assert!(serde_json::to_string(&arrays).is_ok()); +``` + +### `skip_serializing_none` + +This situation often occurs with JSON, but other formats also support optional fields. +If many fields are optional, putting the annotations on the structs can become tedious. +The `#[skip_serializing_none]` attribute must be placed *before* the `#[derive]`. + +[![Rustexplorer](https://img.shields.io/badge/Try%20on-rustexplorer-lightgrey?logo=rust&logoColor=orange)](https://www.rustexplorer.com/b/xr1tm0) +```rust +#[skip_serializing_none] +#[derive(Deserialize, Serialize)] +struct Foo { + a: Option<usize>, + b: Option<usize>, + c: Option<usize>, + d: Option<usize>, + e: Option<usize>, + f: Option<usize>, + g: Option<usize>, +} + +// This will serialize +Foo {a: None, b: None, c: None, d: Some(4), e: None, f: None, g: Some(7)} + +// into this JSON +{"d": 4, "g": 7} +``` + +### Advanced `serde_as` usage + +This example is mainly supposed to highlight the flexibility of the `serde_as`-annotation compared to [serde's with-annotation][with-annotation]. +More details about `serde_as` can be found in the [user guide]. + +```rust +use std::time::Duration; + +#[serde_as] +#[derive(Deserialize, Serialize)] +enum Foo { + Durations( + // Serialize them into a list of number as seconds + #[serde_as(as = "Vec<DurationSeconds>")] + Vec<Duration>, + ), + Bytes { + // We can treat a Vec like a map with duplicates. + // JSON only allows string keys, so convert i32 to strings + // The bytes will be hex encoded + #[serde_as(as = "Map<DisplayFromStr, Hex>")] + bytes: Vec<(i32, Vec<u8>)>, + } +} + +// This will serialize +Foo::Durations( + vec![Duration::new(5, 0), Duration::new(3600, 0), Duration::new(0, 0)] +) +// into this JSON +{ + "Durations": [5, 3600, 0] +} + +// and serializes +Foo::Bytes { + bytes: vec![ + (1, vec![0, 1, 2]), + (-100, vec![100, 200, 255]), + (1, vec![0, 111, 222]), + ], +} +// into this JSON +{ + "Bytes": { + "bytes": { + "1": "000102", + "-100": "64c8ff", + "1": "006fde" + } + } +} +``` + +[`DisplayFromStr`]: https://docs.rs/serde_with/3.0.0/serde_with/struct.DisplayFromStr.html +[`with_prefix!`]: https://docs.rs/serde_with/3.0.0/serde_with/macro.with_prefix.html +[feature flags]: https://docs.rs/serde_with/3.0.0/serde_with/guide/feature_flags/index.html +[skip_serializing_none]: https://docs.rs/serde_with/3.0.0/serde_with/attr.skip_serializing_none.html +[StringWithSeparator]: https://docs.rs/serde_with/3.0.0/serde_with/struct.StringWithSeparator.html +[user guide]: https://docs.rs/serde_with/3.0.0/serde_with/guide/index.html +[with-annotation]: https://serde.rs/field-attrs.html#with +[as-annotation]: https://docs.rs/serde_with/3.0.0/serde_with/guide/serde_as/index.html + +## License + +Licensed under either of + +* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +## Contribution + +For detailed contribution instructions please read [`CONTRIBUTING.md`]. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. + +[`CONTRIBUTING.md`]: https://github.com/jonasbb/serde_with/blob/master/CONTRIBUTING.md diff --git a/third_party/rust/serde_with_macros/src/apply.rs b/third_party/rust/serde_with_macros/src/apply.rs new file mode 100644 index 0000000000..9cab898482 --- /dev/null +++ b/third_party/rust/serde_with_macros/src/apply.rs @@ -0,0 +1,313 @@ +use darling::{ast::NestedMeta, Error as DarlingError, FromMeta}; +use proc_macro::TokenStream; +use quote::ToTokens as _; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + Attribute, Error, Field, Path, Token, Type, TypeArray, TypeGroup, TypeParen, TypePath, TypePtr, + TypeReference, TypeSlice, TypeTuple, +}; + +/// Parsed form of a single rule in the `#[apply(...)]` attribute. +/// +/// This parses tokens in the shape of `Type => Attribute`. +/// For example, `Option<String> => #[serde(default)]`. +struct AddAttributesRule { + /// A type pattern determining the fields to which the attributes are applied. + ty: Type, + /// The attributes to apply. + /// + /// All attributes are appended to the list of existing field attributes. + attrs: Vec<Attribute>, +} + +impl Parse for AddAttributesRule { + fn parse(input: ParseStream<'_>) -> Result<Self, Error> { + let ty: Type = input.parse()?; + input.parse::<Token![=>]>()?; + let attr = Attribute::parse_outer(input)?; + Ok(AddAttributesRule { ty, attrs: attr }) + } +} + +/// Parsed form of the `#[apply(...)]` attribute. +/// +/// The `apply` attribute takes a comma separated list of rules in the shape of `Type => Attribute`. +/// Each rule is stored as a [`AddAttributesRule`]. +struct ApplyInput { + metas: Vec<NestedMeta>, + rules: Punctuated<AddAttributesRule, Token![,]>, +} + +impl Parse for ApplyInput { + fn parse(input: ParseStream<'_>) -> Result<Self, Error> { + let mut metas: Vec<NestedMeta> = Vec::new(); + + while input.peek2(Token![=]) && !input.peek2(Token![=>]) { + let value = NestedMeta::parse(input)?; + metas.push(value); + if !input.peek(Token![,]) { + break; + } + input.parse::<Token![,]>()?; + } + + let rules: Punctuated<AddAttributesRule, Token![,]> = + input.parse_terminated(AddAttributesRule::parse, Token![,])?; + Ok(Self { metas, rules }) + } +} + +pub fn apply(args: TokenStream, input: TokenStream) -> TokenStream { + let args = syn::parse_macro_input!(args as ApplyInput); + + #[derive(FromMeta)] + struct SerdeContainerOptions { + #[darling(rename = "crate")] + alt_crate_path: Option<Path>, + } + + let container_options = match SerdeContainerOptions::from_list(&args.metas) { + Ok(v) => v, + Err(e) => { + return TokenStream::from(e.write_errors()); + } + }; + let serde_with_crate_path = container_options + .alt_crate_path + .unwrap_or_else(|| syn::parse_quote!(::serde_with)); + + let res = match super::apply_function_to_struct_and_enum_fields_darling( + input, + &serde_with_crate_path, + &prepare_apply_attribute_to_field(args), + ) { + Ok(res) => res, + Err(err) => err.write_errors(), + }; + TokenStream::from(res) +} + +/// Create a function compatible with [`super::apply_function_to_struct_and_enum_fields`] based on [`ApplyInput`]. +/// +/// A single [`ApplyInput`] can apply to multiple field types. +/// To account for this a new function must be created to stay compatible with the function signature or [`super::apply_function_to_struct_and_enum_fields`]. +fn prepare_apply_attribute_to_field( + input: ApplyInput, +) -> impl Fn(&mut Field) -> Result<(), DarlingError> { + move |field: &mut Field| { + let has_skip_attr = super::field_has_attribute(field, "serde_with", "skip_apply"); + if has_skip_attr { + return Ok(()); + } + + for matcher in input.rules.iter() { + if ty_pattern_matches_ty(&matcher.ty, &field.ty) { + field.attrs.extend(matcher.attrs.clone()); + } + } + Ok(()) + } +} + +fn ty_pattern_matches_ty(ty_pattern: &Type, ty: &Type) -> bool { + match (ty_pattern, ty) { + // Groups are invisible groupings which can for example come from macro_rules expansion. + // This can lead to a mismatch where the `ty` is "Group { Option<String> }" and the `ty_pattern` is "Option<String>". + // To account for this we unwrap the group and compare the inner types. + ( + Type::Group(TypeGroup { + elem: ty_pattern, .. + }), + ty, + ) => ty_pattern_matches_ty(ty_pattern, ty), + (ty_pattern, Type::Group(TypeGroup { elem: ty, .. })) => { + ty_pattern_matches_ty(ty_pattern, ty) + } + + // Processing of the other types + ( + Type::Array(TypeArray { + elem: ty_pattern, + len: len_pattern, + .. + }), + Type::Array(TypeArray { elem: ty, len, .. }), + ) => { + let ty_match = ty_pattern_matches_ty(ty_pattern, ty); + dbg!(len_pattern); + let len_match = len_pattern == len || len_pattern.to_token_stream().to_string() == "_"; + ty_match && len_match + } + (Type::BareFn(ty_pattern), Type::BareFn(ty)) => ty_pattern == ty, + (Type::ImplTrait(ty_pattern), Type::ImplTrait(ty)) => ty_pattern == ty, + (Type::Infer(_), _) => true, + (Type::Macro(ty_pattern), Type::Macro(ty)) => ty_pattern == ty, + (Type::Never(_), Type::Never(_)) => true, + ( + Type::Paren(TypeParen { + elem: ty_pattern, .. + }), + Type::Paren(TypeParen { elem: ty, .. }), + ) => ty_pattern_matches_ty(ty_pattern, ty), + ( + Type::Path(TypePath { + qself: qself_pattern, + path: path_pattern, + }), + Type::Path(TypePath { qself, path }), + ) => { + /// Compare two paths for relaxed equality. + /// + /// Two paths match if they are equal except for the path arguments. + /// Path arguments are generics on types or functions. + /// If the pattern has no argument, it can match with everthing. + /// If the pattern does have an argument, the other side must be equal. + fn path_pattern_matches_path(path_pattern: &Path, path: &Path) -> bool { + if path_pattern.leading_colon != path.leading_colon + || path_pattern.segments.len() != path.segments.len() + { + return false; + } + // Boths parts are equal length + std::iter::zip(&path_pattern.segments, &path.segments).all( + |(path_pattern_segment, path_segment)| { + let ident_equal = path_pattern_segment.ident == path_segment.ident; + let args_match = + match (&path_pattern_segment.arguments, &path_segment.arguments) { + (syn::PathArguments::None, _) => true, + ( + syn::PathArguments::AngleBracketed( + syn::AngleBracketedGenericArguments { + args: args_pattern, + .. + }, + ), + syn::PathArguments::AngleBracketed( + syn::AngleBracketedGenericArguments { args, .. }, + ), + ) => { + args_pattern.len() == args.len() + && std::iter::zip(args_pattern, args).all(|(a, b)| { + match (a, b) { + ( + syn::GenericArgument::Type(ty_pattern), + syn::GenericArgument::Type(ty), + ) => ty_pattern_matches_ty(ty_pattern, ty), + (a, b) => a == b, + } + }) + } + (args_pattern, args) => args_pattern == args, + }; + ident_equal && args_match + }, + ) + } + qself_pattern == qself && path_pattern_matches_path(path_pattern, path) + } + ( + Type::Ptr(TypePtr { + const_token: const_token_pattern, + mutability: mutability_pattern, + elem: ty_pattern, + .. + }), + Type::Ptr(TypePtr { + const_token, + mutability, + elem: ty, + .. + }), + ) => { + const_token_pattern == const_token + && mutability_pattern == mutability + && ty_pattern_matches_ty(ty_pattern, ty) + } + ( + Type::Reference(TypeReference { + lifetime: lifetime_pattern, + elem: ty_pattern, + .. + }), + Type::Reference(TypeReference { + lifetime, elem: ty, .. + }), + ) => { + (lifetime_pattern.is_none() || lifetime_pattern == lifetime) + && ty_pattern_matches_ty(ty_pattern, ty) + } + ( + Type::Slice(TypeSlice { + elem: ty_pattern, .. + }), + Type::Slice(TypeSlice { elem: ty, .. }), + ) => ty_pattern_matches_ty(ty_pattern, ty), + (Type::TraitObject(ty_pattern), Type::TraitObject(ty)) => ty_pattern == ty, + ( + Type::Tuple(TypeTuple { + elems: ty_pattern, .. + }), + Type::Tuple(TypeTuple { elems: ty, .. }), + ) => { + ty_pattern.len() == ty.len() + && std::iter::zip(ty_pattern, ty) + .all(|(ty_pattern, ty)| ty_pattern_matches_ty(ty_pattern, ty)) + } + (Type::Verbatim(_), Type::Verbatim(_)) => false, + _ => false, + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[track_caller] + fn matches(ty_pattern: &str, ty: &str) -> bool { + let ty_pattern = syn::parse_str(ty_pattern).unwrap(); + let ty = syn::parse_str(ty).unwrap(); + ty_pattern_matches_ty(&ty_pattern, &ty) + } + + #[test] + fn test_ty_generic() { + assert!(matches("Option<u8>", "Option<u8>")); + assert!(matches("Option", "Option<u8>")); + assert!(!matches("Option<u8>", "Option<String>")); + + assert!(matches("BTreeMap<u8, u8>", "BTreeMap<u8, u8>")); + assert!(matches("BTreeMap", "BTreeMap<u8, u8>")); + assert!(!matches("BTreeMap<String, String>", "BTreeMap<u8, u8>")); + assert!(matches("BTreeMap<_, _>", "BTreeMap<u8, u8>")); + assert!(matches("BTreeMap<_, u8>", "BTreeMap<u8, u8>")); + assert!(!matches("BTreeMap<String, _>", "BTreeMap<u8, u8>")); + } + + #[test] + fn test_array() { + assert!(matches("[u8; 1]", "[u8; 1]")); + assert!(matches("[_; 1]", "[u8; 1]")); + assert!(matches("[u8; _]", "[u8; 1]")); + assert!(matches("[u8; _]", "[u8; N]")); + + assert!(!matches("[u8; 1]", "[u8; 2]")); + assert!(!matches("[u8; 1]", "[u8; _]")); + assert!(!matches("[u8; 1]", "[String; 1]")); + } + + #[test] + fn test_reference() { + assert!(matches("&str", "&str")); + assert!(matches("&mut str", "&str")); + assert!(matches("&str", "&mut str")); + assert!(matches("&str", "&'a str")); + assert!(matches("&str", "&'static str")); + assert!(matches("&str", "&'static mut str")); + + assert!(matches("&'a str", "&'a str")); + assert!(matches("&'a mut str", "&'a str")); + + assert!(!matches("&'b str", "&'a str")); + } +} diff --git a/third_party/rust/serde_with_macros/src/lib.rs b/third_party/rust/serde_with_macros/src/lib.rs new file mode 100644 index 0000000000..7f6d46ee37 --- /dev/null +++ b/third_party/rust/serde_with_macros/src/lib.rs @@ -0,0 +1,1318 @@ +#![forbid(unsafe_code)] +#![warn( + clippy::semicolon_if_nothing_returned, + missing_copy_implementations, + missing_debug_implementations, + missing_docs, + rust_2018_idioms, + rustdoc::missing_crate_level_docs, + trivial_casts, + trivial_numeric_casts, + unused_extern_crates, + unused_import_braces, + unused_qualifications, + variant_size_differences +)] +#![doc(test(attr(forbid(unsafe_code))))] +#![doc(test(attr(deny( + missing_copy_implementations, + missing_debug_implementations, + trivial_casts, + trivial_numeric_casts, + unused_extern_crates, + unused_import_braces, + unused_qualifications, +))))] +#![doc(test(attr(warn(rust_2018_idioms))))] +// Not needed for 2018 edition and conflicts with `rust_2018_idioms` +#![doc(test(no_crate_inject))] +#![doc(html_root_url = "https://docs.rs/serde_with_macros/3.0.0/")] +// Necessary to silence the warning about clippy::unknown_clippy_lints on nightly +#![allow(renamed_and_removed_lints)] +// Necessary for nightly clippy lints +#![allow(clippy::unknown_clippy_lints)] +// Tarpaulin does not work well with proc macros and marks most of the lines as uncovered. +#![cfg(not(tarpaulin_include))] + +//! proc-macro extensions for [`serde_with`]. +//! +//! This crate should **NEVER** be used alone. +//! All macros **MUST** be used via the re-exports in the [`serde_with`] crate. +//! +//! [`serde_with`]: https://crates.io/crates/serde_with/ + +#[allow(unused_extern_crates)] +extern crate proc_macro; + +mod apply; +mod utils; + +use crate::utils::{split_with_de_lifetime, DeriveOptions, IteratorExt as _}; +use darling::{ + ast::NestedMeta, + util::{Flag, Override}, + Error as DarlingError, FromField, FromMeta, +}; +use proc_macro::TokenStream; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::quote; +use syn::{ + parse::Parser, + parse_macro_input, parse_quote, + punctuated::{Pair, Punctuated}, + spanned::Spanned, + DeriveInput, Error, Field, Fields, GenericArgument, ItemEnum, ItemStruct, Meta, Path, + PathArguments, ReturnType, Token, Type, +}; + +/// Apply function on every field of structs or enums +fn apply_function_to_struct_and_enum_fields<F>( + input: TokenStream, + function: F, +) -> Result<TokenStream2, Error> +where + F: Copy, + F: Fn(&mut Field) -> Result<(), String>, +{ + /// Handle a single struct or a single enum variant + fn apply_on_fields<F>(fields: &mut Fields, function: F) -> Result<(), Error> + where + F: Fn(&mut Field) -> Result<(), String>, + { + match fields { + // simple, no fields, do nothing + Fields::Unit => Ok(()), + Fields::Named(ref mut fields) => fields + .named + .iter_mut() + .map(|field| function(field).map_err(|err| Error::new(field.span(), err))) + .collect_error(), + Fields::Unnamed(ref mut fields) => fields + .unnamed + .iter_mut() + .map(|field| function(field).map_err(|err| Error::new(field.span(), err))) + .collect_error(), + } + } + + // For each field in the struct given by `input`, add the `skip_serializing_if` attribute, + // if and only if, it is of type `Option` + if let Ok(mut input) = syn::parse::<ItemStruct>(input.clone()) { + apply_on_fields(&mut input.fields, function)?; + Ok(quote!(#input)) + } else if let Ok(mut input) = syn::parse::<ItemEnum>(input) { + input + .variants + .iter_mut() + .map(|variant| apply_on_fields(&mut variant.fields, function)) + .collect_error()?; + Ok(quote!(#input)) + } else { + Err(Error::new( + Span::call_site(), + "The attribute can only be applied to struct or enum definitions.", + )) + } +} + +/// Like [apply_function_to_struct_and_enum_fields] but for darling errors +fn apply_function_to_struct_and_enum_fields_darling<F>( + input: TokenStream, + serde_with_crate_path: &Path, + function: F, +) -> Result<TokenStream2, DarlingError> +where + F: Copy, + F: Fn(&mut Field) -> Result<(), DarlingError>, +{ + /// Handle a single struct or a single enum variant + fn apply_on_fields<F>(fields: &mut Fields, function: F) -> Result<(), DarlingError> + where + F: Fn(&mut Field) -> Result<(), DarlingError>, + { + match fields { + // simple, no fields, do nothing + Fields::Unit => Ok(()), + Fields::Named(ref mut fields) => { + let errors: Vec<DarlingError> = fields + .named + .iter_mut() + .map(|field| function(field).map_err(|err| err.with_span(&field))) + // turn the Err variant into the Some, such that we only collect errors + .filter_map(|res| match res { + Err(e) => Some(e), + Ok(()) => None, + }) + .collect(); + if errors.is_empty() { + Ok(()) + } else { + Err(DarlingError::multiple(errors)) + } + } + Fields::Unnamed(ref mut fields) => { + let errors: Vec<DarlingError> = fields + .unnamed + .iter_mut() + .map(|field| function(field).map_err(|err| err.with_span(&field))) + // turn the Err variant into the Some, such that we only collect errors + .filter_map(|res| match res { + Err(e) => Some(e), + Ok(()) => None, + }) + .collect(); + if errors.is_empty() { + Ok(()) + } else { + Err(DarlingError::multiple(errors)) + } + } + } + } + + // Add a dummy derive macro which consumes (makes inert) all field attributes + let consume_serde_as_attribute = parse_quote!( + #[derive(#serde_with_crate_path::__private_consume_serde_as_attributes)] + ); + + // For each field in the struct given by `input`, add the `skip_serializing_if` attribute, + // if and only if, it is of type `Option` + if let Ok(mut input) = syn::parse::<ItemStruct>(input.clone()) { + apply_on_fields(&mut input.fields, function)?; + input.attrs.push(consume_serde_as_attribute); + Ok(quote!(#input)) + } else if let Ok(mut input) = syn::parse::<ItemEnum>(input) { + // Prevent serde_as on enum variants + let mut errors: Vec<DarlingError> = input + .variants + .iter() + .flat_map(|variant| { + variant.attrs.iter().filter_map(|attr| { + if attr.path().is_ident("serde_as") { + Some( + DarlingError::custom( + "serde_as attribute is not allowed on enum variants", + ) + .with_span(&attr), + ) + } else { + None + } + }) + }) + .collect(); + // Process serde_as on all fields + errors.extend( + input + .variants + .iter_mut() + .map(|variant| apply_on_fields(&mut variant.fields, function)) + // turn the Err variant into the Some, such that we only collect errors + .filter_map(|res| match res { + Err(e) => Some(e), + Ok(()) => None, + }), + ); + + if errors.is_empty() { + input.attrs.push(consume_serde_as_attribute); + Ok(quote!(#input)) + } else { + Err(DarlingError::multiple(errors)) + } + } else { + Err(DarlingError::custom( + "The attribute can only be applied to struct or enum definitions.", + ) + .with_span(&Span::call_site())) + } +} + +/// Add `skip_serializing_if` annotations to [`Option`] fields. +/// +/// The attribute can be added to structs and enums. +/// The `#[skip_serializing_none]` attribute must be placed *before* the `#[derive]` attribute. +/// +/// # Example +/// +/// JSON APIs sometimes have many optional values. +/// Missing values should not be serialized, to keep the serialized format smaller. +/// Such a data type might look like: +/// +/// ```rust +/// # use serde::Serialize; +/// # +/// #[derive(Serialize)] +/// struct Data { +/// #[serde(skip_serializing_if = "Option::is_none")] +/// a: Option<String>, +/// #[serde(skip_serializing_if = "Option::is_none")] +/// b: Option<u64>, +/// #[serde(skip_serializing_if = "Option::is_none")] +/// c: Option<String>, +/// #[serde(skip_serializing_if = "Option::is_none")] +/// d: Option<bool>, +/// } +/// ``` +/// +/// The `skip_serializing_if` annotation is repetitive and harms readability. +/// Instead, the same struct can be written as: +/// +/// ```rust +/// # use serde::Serialize; +/// # use serde_with_macros::skip_serializing_none; +/// #[skip_serializing_none] +/// #[derive(Serialize)] +/// struct Data { +/// a: Option<String>, +/// b: Option<u64>, +/// c: Option<String>, +/// // Always serialize field d even if None +/// #[serialize_always] +/// d: Option<bool>, +/// } +/// ``` +/// +/// Existing `skip_serializing_if` annotations will not be altered. +/// +/// If some values should always be serialized, then `serialize_always` can be used. +/// +/// # Limitations +/// +/// The `serialize_always` cannot be used together with a manual `skip_serializing_if` annotations, +/// as these conflict in their meaning. A compile error will be generated if this occurs. +/// +/// The `skip_serializing_none` only works if the type is called [`Option`], +/// [`std::option::Option`], or [`core::option::Option`]. Type aliasing an [`Option`] and giving it +/// another name, will cause this field to be ignored. This cannot be supported, as proc-macros run +/// before type checking, thus it is not possible to determine if a type alias refers to an +/// [`Option`]. +/// +/// ```rust +/// # use serde::Serialize; +/// # use serde_with_macros::skip_serializing_none; +/// type MyOption<T> = Option<T>; +/// +/// #[skip_serializing_none] +/// #[derive(Serialize)] +/// struct Data { +/// a: MyOption<String>, // This field will not be skipped +/// } +/// ``` +/// +/// Likewise, if you import a type and name it `Option`, the `skip_serializing_if` attributes will +/// be added and compile errors will occur, if `Option::is_none` is not a valid function. +/// Here the function `Vec::is_none` does not exist, and therefore the example fails to compile. +/// +/// ```rust,compile_fail +/// # use serde::Serialize; +/// # use serde_with_macros::skip_serializing_none; +/// use std::vec::Vec as Option; +/// +/// #[skip_serializing_none] +/// #[derive(Serialize)] +/// struct Data { +/// a: Option<String>, +/// } +/// ``` +#[proc_macro_attribute] +pub fn skip_serializing_none(_args: TokenStream, input: TokenStream) -> TokenStream { + let res = match apply_function_to_struct_and_enum_fields( + input, + skip_serializing_none_add_attr_to_field, + ) { + Ok(res) => res, + Err(err) => err.to_compile_error(), + }; + TokenStream::from(res) +} + +/// Add the skip_serializing_if annotation to each field of the struct +fn skip_serializing_none_add_attr_to_field(field: &mut Field) -> Result<(), String> { + if is_std_option(&field.ty) { + let has_skip_serializing_if = field_has_attribute(field, "serde", "skip_serializing_if"); + + // Remove the `serialize_always` attribute + let mut has_always_attr = false; + field.attrs.retain(|attr| { + let has_attr = attr.path().is_ident("serialize_always"); + has_always_attr |= has_attr; + !has_attr + }); + + // Error on conflicting attributes + if has_always_attr && has_skip_serializing_if { + let mut msg = r#"The attributes `serialize_always` and `serde(skip_serializing_if = "...")` cannot be used on the same field"#.to_string(); + if let Some(ident) = &field.ident { + msg += ": `"; + msg += &ident.to_string(); + msg += "`"; + } + msg += "."; + return Err(msg); + } + + // Do nothing if `skip_serializing_if` or `serialize_always` is already present + if has_skip_serializing_if || has_always_attr { + return Ok(()); + } + + // Add the `skip_serializing_if` attribute + let attr = parse_quote!( + #[serde(skip_serializing_if = "Option::is_none")] + ); + field.attrs.push(attr); + } else { + // Warn on use of `serialize_always` on non-Option fields + let has_attr = field + .attrs + .iter() + .any(|attr| attr.path().is_ident("serialize_always")); + if has_attr { + return Err("`serialize_always` may only be used on fields of type `Option`.".into()); + } + } + Ok(()) +} + +/// Return `true`, if the type path refers to `std::option::Option` +/// +/// Accepts +/// +/// * `Option` +/// * `std::option::Option`, with or without leading `::` +/// * `core::option::Option`, with or without leading `::` +fn is_std_option(type_: &Type) -> bool { + match type_ { + Type::Array(_) + | Type::BareFn(_) + | Type::ImplTrait(_) + | Type::Infer(_) + | Type::Macro(_) + | Type::Never(_) + | Type::Ptr(_) + | Type::Reference(_) + | Type::Slice(_) + | Type::TraitObject(_) + | Type::Tuple(_) + | Type::Verbatim(_) => false, + + Type::Group(syn::TypeGroup { elem, .. }) + | Type::Paren(syn::TypeParen { elem, .. }) + | Type::Path(syn::TypePath { + qself: Some(syn::QSelf { ty: elem, .. }), + .. + }) => is_std_option(elem), + + Type::Path(syn::TypePath { qself: None, path }) => { + (path.leading_colon.is_none() + && path.segments.len() == 1 + && path.segments[0].ident == "Option") + || (path.segments.len() == 3 + && (path.segments[0].ident == "std" || path.segments[0].ident == "core") + && path.segments[1].ident == "option" + && path.segments[2].ident == "Option") + } + _ => false, + } +} + +/// Determine if the `field` has an attribute with given `namespace` and `name` +/// +/// On the example of +/// `#[serde(skip_serializing_if = "Option::is_none")]` +/// +/// * `serde` is the outermost path, here namespace +/// * it contains a Meta::List +/// * which contains in another Meta a Meta::NameValue +/// * with the name being `skip_serializing_if` +fn field_has_attribute(field: &Field, namespace: &str, name: &str) -> bool { + for attr in &field.attrs { + if attr.path().is_ident(namespace) { + // Ignore non parsable attributes, as these are not important for us + if let Meta::List(expr) = &attr.meta { + let nested = match Punctuated::<Meta, Token![,]>::parse_terminated + .parse2(expr.tokens.clone()) + { + Ok(nested) => nested, + Err(_) => continue, + }; + for expr in nested { + match expr { + Meta::NameValue(expr) => { + if let Some(ident) = expr.path.get_ident() { + if *ident == name { + return true; + } + } + } + Meta::Path(expr) => { + if let Some(ident) = expr.get_ident() { + if *ident == name { + return true; + } + } + } + _ => (), + } + } + } + } + } + false +} + +/// Convenience macro to use the [`serde_as`] system. +/// +/// The [`serde_as`] system is designed as a more flexible alternative to serde's with-annotation. +/// The `#[serde_as]` attribute must be placed *before* the `#[derive]` attribute. +/// Each field of a struct or enum can be annotated with `#[serde_as(...)]` to specify which +/// transformations should be applied. `serde_as` is *not* supported on enum variants. +/// This is in contrast to `#[serde(with = "...")]`. +/// +/// # Example +/// +/// ```rust,ignore +/// use serde_with::{serde_as, DisplayFromStr, Map}; +/// +/// #[serde_as] +/// #[derive(Serialize, Deserialize)] +/// struct Data { +/// /// Serialize into number +/// #[serde_as(as = "_")] +/// a: u32, +/// +/// /// Serialize into String +/// #[serde_as(as = "DisplayFromStr")] +/// b: u32, +/// +/// /// Serialize into a map from String to String +/// #[serde_as(as = "Map<DisplayFromStr, _>")] +/// c: Vec<(u32, String)>, +/// } +/// ``` +/// +/// # Alternative path to `serde_with` crate +/// +/// If `serde_with` is not available at the default path, its path should be specified with the +/// `crate` argument. See [re-exporting `serde_as`] for more use case information. +/// +/// ```rust,ignore +/// #[serde_as(crate = "::some_other_lib::serde_with")] +/// #[derive(Deserialize)] +/// struct Data { +/// #[serde_as(as = "_")] +/// a: u32, +/// } +/// ``` +/// +/// # What this macro does +/// +/// The `serde_as` macro only serves a convenience function. +/// All the steps it performs, can easily be done manually, in case the cost of an attribute macro +/// is deemed too high. The functionality can best be described with an example. +/// +/// ```rust,ignore +/// #[serde_as] +/// #[derive(serde::Serialize)] +/// struct Foo { +/// #[serde_as(as = "Vec<_>")] +/// bar: Vec<u32>, +/// +/// #[serde_as(as = "Option<DisplayFromStr>")] +/// baz: Option<u32>, +/// } +/// ``` +/// +/// 1. All the placeholder type `_` will be replaced with `::serde_with::Same`. +/// The placeholder type `_` marks all the places where the type's `Serialize` implementation +/// should be used. In the example, it means that the `u32` values will serialize with the +/// `Serialize` implementation of `u32`. The `Same` type implements `SerializeAs` whenever the +/// underlying type implements `Serialize` and is used to make the two traits compatible. +/// +/// If you specify a custom path for `serde_with` via the `crate` attribute, the path to the +/// `Same` type will be altered accordingly. +/// +/// 2. Wrap the type from the annotation inside a `::serde_with::As`. +/// In the above example we now have something like `::serde_with::As::<Vec<::serde_with::Same>>`. +/// The `As` type acts as the opposite of the `Same` type. +/// It allows using a `SerializeAs` type whenever a `Serialize` is required. +/// +/// 3. Translate the `*as` attributes into the serde equivalent ones. +/// `#[serde_as(as = ...)]` will become `#[serde(with = ...)]`. +/// Similarly, `serialize_as` is translated to `serialize_with`. +/// +/// The field attributes will be kept on the struct/enum such that other macros can use them +/// too. +/// +/// 4. It searches `#[serde_as(as = ...)]` if there is a type named `BorrowCow` under any path. +/// If `BorrowCow` is found, the attribute `#[serde(borrow)]` is added to the field. +/// If `#[serde(borrow)]` or `#[serde(borrow = "...")]` is already present, this step will be +/// skipped. +/// +/// 5. Restore the ability of accepting missing fields if both the field and the +/// transformation are `Option`. +/// +/// An `Option` is detected by an exact text match. +/// Renaming an import or type aliases can cause confusion here. +/// The following variants are supported. +/// * `Option` +/// * `std::option::Option`, with or without leading `::` +/// * `core::option::Option`, with or without leading `::` +/// +/// If the field is of type `Option<T>` and the attribute `#[serde_as(as = "Option<S>")]` (also +/// `deserialize_as`; for any `T`/`S`) then `#[serde(default)]` is applied to the field. +/// +/// This restores the ability of accepting missing fields, which otherwise often leads to confusing [serde_with#185](https://github.com/jonasbb/serde_with/issues/185). +/// `#[serde(default)]` is not applied, if it already exists. +/// It only triggers if both field and transformation are `Option`s. +/// For example, using `#[serde_as(as = "NoneAsEmptyString")]` on `Option<String>` will not see +/// any change. +/// +/// If the automatically applied attribute is undesired, the behavior can be supressed by adding +/// `#[serde_as(no_default)]`. + +/// This can be combined like `#[serde_as(as = "Option<S>", no_default)]`. +/// +/// After all these steps, the code snippet will have transformed into roughly this. +/// +/// ```rust,ignore +/// #[derive(serde::Serialize)] +/// struct Foo { +/// #[serde_as(as = "Vec<_>")] +/// #[serde(with = "::serde_with::As::<Vec<::serde_with::Same>>")] +/// bar: Vec<u32>, +/// +/// #[serde_as(as = "Option<DisplayFromStr>")] +/// #[serde(default)] +/// #[serde(with = "::serde_with::As::<Option<DisplayFromStr>>")] +/// baz: Option<u32>, +/// } +/// ``` +/// +/// [`serde_as`]: https://docs.rs/serde_with/3.0.0/serde_with/guide/index.html +/// [re-exporting `serde_as`]: https://docs.rs/serde_with/3.0.0/serde_with/guide/serde_as/index.html#re-exporting-serde_as +#[proc_macro_attribute] +pub fn serde_as(args: TokenStream, input: TokenStream) -> TokenStream { + #[derive(FromMeta)] + struct SerdeContainerOptions { + #[darling(rename = "crate")] + alt_crate_path: Option<Path>, + } + + match NestedMeta::parse_meta_list(args.into()) { + Ok(list) => { + let container_options = match SerdeContainerOptions::from_list(&list) { + Ok(v) => v, + Err(e) => { + return TokenStream::from(e.write_errors()); + } + }; + + let serde_with_crate_path = container_options + .alt_crate_path + .unwrap_or_else(|| syn::parse_quote!(::serde_with)); + + // Convert any error message into a nice compiler error + let res = match apply_function_to_struct_and_enum_fields_darling( + input, + &serde_with_crate_path, + |field| serde_as_add_attr_to_field(field, &serde_with_crate_path), + ) { + Ok(res) => res, + Err(err) => err.write_errors(), + }; + TokenStream::from(res) + } + Err(e) => TokenStream::from(DarlingError::from(e).write_errors()), + } +} + +/// Inspect the field and convert the `serde_as` attribute into the classical `serde` +fn serde_as_add_attr_to_field( + field: &mut Field, + serde_with_crate_path: &Path, +) -> Result<(), DarlingError> { + #[derive(FromField)] + #[darling(attributes(serde_as))] + struct SerdeAsOptions { + r#as: Option<Type>, + deserialize_as: Option<Type>, + serialize_as: Option<Type>, + no_default: Flag, + } + + impl SerdeAsOptions { + fn has_any_set(&self) -> bool { + self.r#as.is_some() || self.deserialize_as.is_some() || self.serialize_as.is_some() + } + } + + #[derive(FromField)] + #[darling(attributes(serde), allow_unknown_fields)] + struct SerdeOptions { + with: Option<String>, + deserialize_with: Option<String>, + serialize_with: Option<String>, + + borrow: Option<Override<String>>, + default: Option<Override<String>>, + } + + impl SerdeOptions { + fn has_any_set(&self) -> bool { + self.with.is_some() || self.deserialize_with.is_some() || self.serialize_with.is_some() + } + } + + /// Emit a `borrow` annotation, if the replacement type requires borrowing. + fn emit_borrow_annotation(serde_options: &SerdeOptions, as_type: &Type, field: &mut Field) { + let type_borrowcow = &syn::parse_quote!(BorrowCow); + // If the field is not borrowed yet, check if we need to borrow it. + if serde_options.borrow.is_none() && has_type_embedded(as_type, type_borrowcow) { + let attr_borrow = parse_quote!(#[serde(borrow)]); + field.attrs.push(attr_borrow); + } + } + + /// Emit a `default` annotation, if `as_type` and `field` are both `Option`. + fn emit_default_annotation( + serde_as_options: &SerdeAsOptions, + serde_options: &SerdeOptions, + as_type: &Type, + field: &mut Field, + ) { + if !serde_as_options.no_default.is_present() + && serde_options.default.is_none() + && is_std_option(as_type) + && is_std_option(&field.ty) + { + let attr_borrow = parse_quote!(#[serde(default)]); + field.attrs.push(attr_borrow); + } + } + + // syn v2 no longer supports keywords in the path position of an attribute. + // That breaks #[serde_as(as = "FooBar")], since `as` is a keyword. + // For each attribute, that is named `serde_as`, we replace the `as` keyword with `r#as`. + let mut has_serde_as = false; + field.attrs.iter_mut().for_each(|attr| { + if attr.path().is_ident("serde_as") { + // We found a `serde_as` attribute. + // Remember that such that we can quick exit otherwise + has_serde_as = true; + + if let Meta::List(metalist) = &mut attr.meta { + metalist.tokens = std::mem::take(&mut metalist.tokens) + .into_iter() + .map(|token| { + use proc_macro2::{Ident, TokenTree}; + + // Replace `as` with `r#as`. + match token { + TokenTree::Ident(ident) if ident == "as" => { + TokenTree::Ident(Ident::new_raw("as", ident.span())) + } + _ => token, + } + }) + .collect(); + } + } + }); + // If there is no `serde_as` attribute, we can exit early. + if !has_serde_as { + return Ok(()); + } + let serde_as_options = SerdeAsOptions::from_field(field)?; + let serde_options = SerdeOptions::from_field(field)?; + + let mut errors = Vec::new(); + if !serde_as_options.has_any_set() { + errors.push(DarlingError::custom("An empty `serde_as` attribute on a field has no effect. You are missing an `as`, `serialize_as`, or `deserialize_as` parameter.")); + } + + // Check if there are any conflicting attributes + if serde_as_options.has_any_set() && serde_options.has_any_set() { + errors.push(DarlingError::custom("Cannot combine `serde_as` with serde's `with`, `deserialize_with`, or `serialize_with`.")); + } + + if serde_as_options.r#as.is_some() && serde_as_options.deserialize_as.is_some() { + errors.push(DarlingError::custom("Cannot combine `as` with `deserialize_as`. Use `serialize_as` to specify different serialization code.")); + } else if serde_as_options.r#as.is_some() && serde_as_options.serialize_as.is_some() { + errors.push(DarlingError::custom("Cannot combine `as` with `serialize_as`. Use `deserialize_as` to specify different deserialization code.")); + } + + if !errors.is_empty() { + return Err(DarlingError::multiple(errors)); + } + + let type_same = &syn::parse_quote!(#serde_with_crate_path::Same); + if let Some(type_) = &serde_as_options.r#as { + emit_borrow_annotation(&serde_options, type_, field); + emit_default_annotation(&serde_as_options, &serde_options, type_, field); + + let replacement_type = replace_infer_type_with_type(type_.clone(), type_same); + let attr_inner_tokens = quote!(#serde_with_crate_path::As::<#replacement_type>).to_string(); + let attr = parse_quote!(#[serde(with = #attr_inner_tokens)]); + field.attrs.push(attr); + } + if let Some(type_) = &serde_as_options.deserialize_as { + emit_borrow_annotation(&serde_options, type_, field); + emit_default_annotation(&serde_as_options, &serde_options, type_, field); + + let replacement_type = replace_infer_type_with_type(type_.clone(), type_same); + let attr_inner_tokens = + quote!(#serde_with_crate_path::As::<#replacement_type>::deserialize).to_string(); + let attr = parse_quote!(#[serde(deserialize_with = #attr_inner_tokens)]); + field.attrs.push(attr); + } + if let Some(type_) = serde_as_options.serialize_as { + let replacement_type = replace_infer_type_with_type(type_, type_same); + let attr_inner_tokens = + quote!(#serde_with_crate_path::As::<#replacement_type>::serialize).to_string(); + let attr = parse_quote!(#[serde(serialize_with = #attr_inner_tokens)]); + field.attrs.push(attr); + } + + Ok(()) +} + +/// Recursively replace all occurrences of `_` with `replacement` in a [Type][] +/// +/// The [serde_as][macro@serde_as] macro allows to use the infer type, i.e., `_`, as shortcut for +/// `serde_with::As`. This function replaces all occurrences of the infer type with another type. +fn replace_infer_type_with_type(to_replace: Type, replacement: &Type) -> Type { + match to_replace { + // Base case + // Replace the infer type with the actual replacement type + Type::Infer(_) => replacement.clone(), + + // Recursive cases + // Iterate through all positions where a type could occur and recursively call this function + Type::Array(mut inner) => { + *inner.elem = replace_infer_type_with_type(*inner.elem, replacement); + Type::Array(inner) + } + Type::Group(mut inner) => { + *inner.elem = replace_infer_type_with_type(*inner.elem, replacement); + Type::Group(inner) + } + Type::Never(inner) => Type::Never(inner), + Type::Paren(mut inner) => { + *inner.elem = replace_infer_type_with_type(*inner.elem, replacement); + Type::Paren(inner) + } + Type::Path(mut inner) => { + match inner.path.segments.pop() { + Some(Pair::End(mut t)) | Some(Pair::Punctuated(mut t, _)) => { + t.arguments = match t.arguments { + PathArguments::None => PathArguments::None, + PathArguments::AngleBracketed(mut inner) => { + // Iterate over the args between the angle brackets + inner.args = inner + .args + .into_iter() + .map(|generic_argument| match generic_argument { + // replace types within the generics list, but leave other stuff + // like lifetimes untouched + GenericArgument::Type(type_) => GenericArgument::Type( + replace_infer_type_with_type(type_, replacement), + ), + ga => ga, + }) + .collect(); + PathArguments::AngleBracketed(inner) + } + PathArguments::Parenthesized(mut inner) => { + inner.inputs = inner + .inputs + .into_iter() + .map(|type_| replace_infer_type_with_type(type_, replacement)) + .collect(); + inner.output = match inner.output { + ReturnType::Type(arrow, mut type_) => { + *type_ = replace_infer_type_with_type(*type_, replacement); + ReturnType::Type(arrow, type_) + } + default => default, + }; + PathArguments::Parenthesized(inner) + } + }; + inner.path.segments.push(t); + } + None => {} + } + Type::Path(inner) + } + Type::Ptr(mut inner) => { + *inner.elem = replace_infer_type_with_type(*inner.elem, replacement); + Type::Ptr(inner) + } + Type::Reference(mut inner) => { + *inner.elem = replace_infer_type_with_type(*inner.elem, replacement); + Type::Reference(inner) + } + Type::Slice(mut inner) => { + *inner.elem = replace_infer_type_with_type(*inner.elem, replacement); + Type::Slice(inner) + } + Type::Tuple(mut inner) => { + inner.elems = inner + .elems + .into_pairs() + .map(|pair| match pair { + Pair::Punctuated(type_, p) => { + Pair::Punctuated(replace_infer_type_with_type(type_, replacement), p) + } + Pair::End(type_) => Pair::End(replace_infer_type_with_type(type_, replacement)), + }) + .collect(); + Type::Tuple(inner) + } + + // Pass unknown types or non-handleable types (e.g., bare Fn) without performing any + // replacements + type_ => type_, + } +} + +/// Check if a type ending in the `syn::Ident` `embedded_type` is contained in `type_`. +fn has_type_embedded(type_: &Type, embedded_type: &syn::Ident) -> bool { + match type_ { + // Base cases + Type::Infer(_) => false, + Type::Never(_inner) => false, + + // Recursive cases + // Iterate through all positions where a type could occur and recursively call this function + Type::Array(inner) => has_type_embedded(&inner.elem, embedded_type), + Type::Group(inner) => has_type_embedded(&inner.elem, embedded_type), + Type::Paren(inner) => has_type_embedded(&inner.elem, embedded_type), + Type::Path(inner) => { + match inner.path.segments.last() { + Some(t) => { + if t.ident == *embedded_type { + return true; + } + + match &t.arguments { + PathArguments::None => false, + PathArguments::AngleBracketed(inner) => { + // Iterate over the args between the angle brackets + inner + .args + .iter() + .any(|generic_argument| match generic_argument { + // replace types within the generics list, but leave other stuff + // like lifetimes untouched + GenericArgument::Type(type_) => { + has_type_embedded(type_, embedded_type) + } + _ga => false, + }) + } + PathArguments::Parenthesized(inner) => { + inner + .inputs + .iter() + .any(|type_| has_type_embedded(type_, embedded_type)) + || match &inner.output { + ReturnType::Type(_arrow, type_) => { + has_type_embedded(type_, embedded_type) + } + _default => false, + } + } + } + } + None => false, + } + } + Type::Ptr(inner) => has_type_embedded(&inner.elem, embedded_type), + Type::Reference(inner) => has_type_embedded(&inner.elem, embedded_type), + Type::Slice(inner) => has_type_embedded(&inner.elem, embedded_type), + Type::Tuple(inner) => inner.elems.pairs().any(|pair| match pair { + Pair::Punctuated(type_, _) | Pair::End(type_) => { + has_type_embedded(type_, embedded_type) + } + }), + + // Pass unknown types or non-handleable types (e.g., bare Fn) without performing any + // replacements + _type_ => false, + } +} + +/// Deserialize value by using its [`FromStr`] implementation +/// +/// This is an alternative way to implement `Deserialize` for types, which also implement +/// [`FromStr`] by deserializing the type from string. Ensure that the struct/enum also implements +/// [`FromStr`]. If the implementation is missing, you will get an error message like +/// ```text +/// error[E0277]: the trait bound `Struct: std::str::FromStr` is not satisfied +/// ``` +/// Additionally, `FromStr::Err` **must** implement [`Display`] as otherwise you will see a rather +/// unhelpful error message +/// +/// Serialization with [`Display`] is available with the matching [`SerializeDisplay`] derive. +/// +/// # Attributes +/// +/// Attributes for the derive can be specified via the `#[serde_with(...)]` attribute on the struct +/// or enum. Currently, these arguments to the attribute are possible: +/// +/// * **`#[serde_with(crate = "...")]`**: This allows using `DeserializeFromStr` when `serde_with` +/// is not available from the crate root. This happens while [renaming dependencies in +/// Cargo.toml][cargo-toml-rename] or when re-exporting the macro from a different crate. +/// +/// This argument is analogue to [serde's crate argument][serde-crate] and the [crate argument +/// to `serde_as`][serde-as-crate]. +/// +/// # Example +/// +/// ```rust,ignore +/// use std::str::FromStr; +/// +/// #[derive(DeserializeFromStr)] +/// struct A { +/// a: u32, +/// b: bool, +/// } +/// +/// impl FromStr for A { +/// type Err = String; +/// +/// /// Parse a value like `123<>true` +/// fn from_str(s: &str) -> Result<Self, Self::Err> { +/// let mut parts = s.split("<>"); +/// let number = parts +/// .next() +/// .ok_or_else(|| "Missing first value".to_string())? +/// .parse() +/// .map_err(|err: ParseIntError| err.to_string())?; +/// let bool = parts +/// .next() +/// .ok_or_else(|| "Missing second value".to_string())? +/// .parse() +/// .map_err(|err: ParseBoolError| err.to_string())?; +/// Ok(Self { a: number, b: bool }) +/// } +/// } +/// +/// let a: A = serde_json::from_str("\"159<>true\"").unwrap(); +/// assert_eq!(A { a: 159, b: true }, a); +/// ``` +/// +/// [`Display`]: std::fmt::Display +/// [`FromStr`]: std::str::FromStr +/// [cargo-toml-rename]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#renaming-dependencies-in-cargotoml +/// [serde-as-crate]: https://docs.rs/serde_with/3.0.0/serde_with/guide/serde_as/index.html#re-exporting-serde_as +/// [serde-crate]: https://serde.rs/container-attrs.html#crate +#[proc_macro_derive(DeserializeFromStr, attributes(serde_with))] +pub fn derive_deserialize_fromstr(item: TokenStream) -> TokenStream { + let input: DeriveInput = parse_macro_input!(item); + let derive_options = match DeriveOptions::from_derive_input(&input) { + Ok(opt) => opt, + Err(err) => { + return err; + } + }; + TokenStream::from(deserialize_fromstr( + input, + derive_options.get_serde_with_path(), + )) +} + +fn deserialize_fromstr(mut input: DeriveInput, serde_with_crate_path: Path) -> TokenStream2 { + let ident = input.ident; + let where_clause = &mut input.generics.make_where_clause().predicates; + where_clause.push(parse_quote!(Self: #serde_with_crate_path::__private__::FromStr)); + where_clause.push(parse_quote!( + <Self as #serde_with_crate_path::__private__::FromStr>::Err: #serde_with_crate_path::__private__::Display + )); + let (de_impl_generics, ty_generics, where_clause) = split_with_de_lifetime(&input.generics); + quote! { + #[automatically_derived] + impl #de_impl_generics #serde_with_crate_path::serde::Deserialize<'de> for #ident #ty_generics #where_clause { + fn deserialize<__D>(deserializer: __D) -> #serde_with_crate_path::__private__::Result<Self, __D::Error> + where + __D: #serde_with_crate_path::serde::Deserializer<'de>, + { + struct Helper<__S>(#serde_with_crate_path::__private__::PhantomData<__S>); + + impl<'de, __S> #serde_with_crate_path::serde::de::Visitor<'de> for Helper<__S> + where + __S: #serde_with_crate_path::__private__::FromStr, + <__S as #serde_with_crate_path::__private__::FromStr>::Err: #serde_with_crate_path::__private__::Display, + { + type Value = __S; + + fn expecting(&self, formatter: &mut #serde_with_crate_path::core::fmt::Formatter<'_>) -> #serde_with_crate_path::core::fmt::Result { + #serde_with_crate_path::__private__::Display::fmt("a string", formatter) + } + + fn visit_str<__E>( + self, + value: &str + ) -> #serde_with_crate_path::__private__::Result<Self::Value, __E> + where + __E: #serde_with_crate_path::serde::de::Error, + { + value.parse::<Self::Value>().map_err(#serde_with_crate_path::serde::de::Error::custom) + } + + fn visit_bytes<__E>( + self, + value: &[u8] + ) -> #serde_with_crate_path::__private__::Result<Self::Value, __E> + where + __E: #serde_with_crate_path::serde::de::Error, + { + let utf8 = #serde_with_crate_path::core::str::from_utf8(value).map_err(#serde_with_crate_path::serde::de::Error::custom)?; + self.visit_str(utf8) + } + } + + deserializer.deserialize_str(Helper(#serde_with_crate_path::__private__::PhantomData)) + } + } + } +} + +/// Serialize value by using it's [`Display`] implementation +/// +/// This is an alternative way to implement `Serialize` for types, which also implement [`Display`] +/// by serializing the type as string. Ensure that the struct/enum also implements [`Display`]. +/// If the implementation is missing, you will get an error message like +/// ```text +/// error[E0277]: `Struct` doesn't implement `std::fmt::Display` +/// ``` +/// +/// Deserialization with [`FromStr`] is available with the matching [`DeserializeFromStr`] derive. +/// +/// # Attributes +/// +/// Attributes for the derive can be specified via the `#[serde_with(...)]` attribute on the struct +/// or enum. Currently, these arguments to the attribute are possible: +/// +/// * **`#[serde_with(crate = "...")]`**: This allows using `SerializeDisplay` when `serde_with` is +/// not available from the crate root. This happens while [renaming dependencies in +/// Cargo.toml][cargo-toml-rename] or when re-exporting the macro from a different crate. +/// +/// This argument is analogue to [serde's crate argument][serde-crate] and the [crate argument +/// to `serde_as`][serde-as-crate]. +/// +/// # Example +/// +/// ```rust,ignore +/// use std::fmt; +/// +/// #[derive(SerializeDisplay)] +/// struct A { +/// a: u32, +/// b: bool, +/// } +/// +/// impl fmt::Display for A { +/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +/// write!(f, "{}<>{}", self.a, self.b) +/// } +/// } +/// +/// let a = A { a: 123, b: false }; +/// assert_eq!(r#""123<>false""#, serde_json::to_string(&a).unwrap()); +/// ``` +/// +/// [`Display`]: std::fmt::Display +/// [`FromStr`]: std::str::FromStr +/// [cargo-toml-rename]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#renaming-dependencies-in-cargotoml +/// [serde-as-crate]: https://docs.rs/serde_with/3.0.0/serde_with/guide/serde_as/index.html#re-exporting-serde_as +/// [serde-crate]: https://serde.rs/container-attrs.html#crate +#[proc_macro_derive(SerializeDisplay, attributes(serde_with))] +pub fn derive_serialize_display(item: TokenStream) -> TokenStream { + let input: DeriveInput = parse_macro_input!(item); + let derive_options = match DeriveOptions::from_derive_input(&input) { + Ok(opt) => opt, + Err(err) => { + return err; + } + }; + TokenStream::from(serialize_display( + input, + derive_options.get_serde_with_path(), + )) +} + +fn serialize_display(mut input: DeriveInput, serde_with_crate_path: Path) -> TokenStream2 { + let ident = input.ident; + input + .generics + .make_where_clause() + .predicates + .push(parse_quote!(Self: #serde_with_crate_path::__private__::Display)); + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + quote! { + #[automatically_derived] + impl #impl_generics #serde_with_crate_path::serde::Serialize for #ident #ty_generics #where_clause { + fn serialize<__S>( + &self, + serializer: __S + ) -> #serde_with_crate_path::__private__::Result<__S::Ok, __S::Error> + where + __S: #serde_with_crate_path::serde::Serializer, + { + serializer.collect_str(&self) + } + } + } +} + +#[doc(hidden)] +/// Private function. Not part of the public API +/// +/// The only task of this derive macro is to consume any `serde_as` attributes and turn them into +/// inert attributes. This allows the serde_as macro to keep the field attributes without causing +/// compiler errors. The intend is that keeping the field attributes allows downstream crates to +/// consume and act on them without causing an ordering dependency to the serde_as macro. +/// +/// Otherwise, downstream proc-macros would need to be placed *in front of* the main `#[serde_as]` +/// attribute, since otherwise the field attributes would already be stripped off. +/// +/// More details about the use-cases in the GitHub discussion: <https://github.com/jonasbb/serde_with/discussions/260>. +#[proc_macro_derive( + __private_consume_serde_as_attributes, + attributes(serde_as, serde_with) +)] +pub fn __private_consume_serde_as_attributes(_: TokenStream) -> TokenStream { + TokenStream::new() +} + +/// Apply attributes to all fields with matching types +/// +/// Whenever you experience the need to apply the same attributes to multiple fields, you can use +/// this macro. It allows you to specify a list of types and a list of attributes. +/// Each field with a "matching" type will then get the attributes applied. +/// The `apply` attribute must be place *before* any consuming attributes, such as `derive`, because +/// Rust expands all attributes in order. +/// +/// For example, if your struct or enum contains many `Option<T>` fields, but you do not want to +/// serialize `None` values, you can use this macro to apply the `#[serde(skip_serializing_if = +/// "Option::is_none")]` attribute to all fields of type `Option<T>`. +/// +/// ```rust +/// # use serde_with_macros as serde_with; +/// #[serde_with::apply( +/// # crate="serde_with", +/// Option => #[serde(skip_serializing_if = "Option::is_none")], +/// )] +/// #[derive(serde::Serialize)] +/// # #[derive(Default)] +/// struct Data { +/// a: Option<String>, +/// b: Option<u64>, +/// c: Option<String>, +/// d: Option<bool>, +/// } +/// # +/// # assert_eq!("{}", serde_json::to_string(&Data::default()).unwrap()); +/// ``` +/// +/// Each rule starts with a type pattern, specifying which fields to match and a list of attributes +/// to apply. Multiple rules can be provided in a single `apply` attribute. +/// +/// ```rust +/// # use serde_with_macros as serde_with; +/// #[serde_with::apply( +/// # crate="serde_with", +/// Option => #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")], +/// Option<bool> => #[serde(rename = "bool")], +/// )] +/// # #[derive(serde::Serialize)] +/// # #[derive(Default)] +/// # struct Data { +/// # a: Option<String>, +/// # b: Option<u64>, +/// # c: Option<String>, +/// # d: Option<bool>, +/// # } +/// # +/// # assert_eq!("{}", serde_json::to_string(&Data::default()).unwrap()); +/// ``` +/// +/// ## Type Patterns +/// +/// The type pattern left of the `=>` specifies which fields to match. +/// +/// | Type Pattern | Matching Types | Notes | +/// | :---------------------- | ---------------------------------------------------: | :------------------------------------------------------------------------------ | +/// | `_` | `Option<bool>`<br>`BTreeMap<&'static str, Vec<u32>>` | `_` matches all fields. | +/// | `Option` | `Option<bool>`<br>`Option<String>` | A missing generic is compatible with any generic arguments. | +/// | `Option<bool>` | `Option<bool>` | A fully specified type only matches exactly. | +/// | `BTreeMap<String, u32>` | `BTreeMap<String, u32>` | A fully specified type only matches exactly. | +/// | `BTreeMap<String, _>` | `BTreeMap<String, u32>`<br>`BTreeMap<String, bool>` | Any `String` key `BTreeMap` matches, as the value is using the `_` placeholder. | +/// | `[u8; _]` | `[u8; 1]`<br>`[u8; N]` | `_` also works as a placeholder for any array length. | +/// +/// ## Opt-out for Individual Fields +/// +/// The `apply` attribute will find all fields with a compatible type. +/// This can be overly eager and a different set of attributes might be required for a specific +/// field. You can opt-out of the `apply` attribute by adding the `#[serde_with(skip_apply)]` +/// attribute to the field. This will prevent any `apply` to apply to this field. +/// If two rules apply to the same field, it is impossible to opt-out of only a single one. +/// In this case the attributes must be applied to the field manually. +/// +/// ```rust +/// # use serde_json::json; +/// # use serde_with_macros as serde_with; +/// #[serde_with::apply( +/// # crate="serde_with", +/// Option => #[serde(skip_serializing_if = "Option::is_none")], +/// )] +/// #[derive(serde::Serialize)] +/// struct Data { +/// a: Option<String>, +/// #[serde_with(skip_apply)] +/// always_serialize_this_field: Option<u64>, +/// c: Option<String>, +/// d: Option<bool>, +/// } +/// +/// let data = Data { +/// a: None, +/// always_serialize_this_field: None, +/// c: None, +/// d: None, +/// }; +/// +/// // serializes into this JSON: +/// # assert_eq!(json!( +/// { +/// "always_serialize_this_field": null +/// } +/// # ), serde_json::to_value(&data).unwrap()); +/// ``` +/// +/// # Alternative path to `serde_with` crate +/// +/// If `serde_with` is not available at the default path, its path should be specified with the +/// `crate` argument. See [re-exporting `serde_as`] for more use case information. +/// +/// ```rust,ignore +/// #[serde_with::apply( +/// crate = "::some_other_lib::serde_with" +/// Option => #[serde(skip_serializing_if = "Option::is_none")], +/// )] +/// #[derive(serde::Serialize)] +/// struct Data { +/// a: Option<String>, +/// b: Option<u64>, +/// c: Option<String>, +/// d: Option<bool>, +/// } +/// ``` +#[proc_macro_attribute] +pub fn apply(args: TokenStream, input: TokenStream) -> TokenStream { + apply::apply(args, input) +} diff --git a/third_party/rust/serde_with_macros/src/utils.rs b/third_party/rust/serde_with_macros/src/utils.rs new file mode 100644 index 0000000000..068cbce6ed --- /dev/null +++ b/third_party/rust/serde_with_macros/src/utils.rs @@ -0,0 +1,77 @@ +use core::iter::Iterator; +use darling::FromDeriveInput; +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::ToTokens; +use syn::{parse_quote, Error, Generics, Path, TypeGenerics}; + +/// Merge multiple [`syn::Error`] into one. +pub(crate) trait IteratorExt { + fn collect_error(self) -> Result<(), Error> + where + Self: Iterator<Item = Result<(), Error>> + Sized, + { + let accu = Ok(()); + self.fold(accu, |accu, error| match (accu, error) { + (Ok(()), error) => error, + (accu, Ok(())) => accu, + (Err(mut err), Err(error)) => { + err.combine(error); + Err(err) + } + }) + } +} +impl<I> IteratorExt for I where I: Iterator<Item = Result<(), Error>> + Sized {} + +/// Attributes usable for derive macros +#[derive(FromDeriveInput)] +#[darling(attributes(serde_with))] +pub(crate) struct DeriveOptions { + /// Path to the crate + #[darling(rename = "crate", default)] + pub(crate) alt_crate_path: Option<Path>, +} + +impl DeriveOptions { + pub(crate) fn from_derive_input(input: &syn::DeriveInput) -> Result<Self, TokenStream> { + match <Self as FromDeriveInput>::from_derive_input(input) { + Ok(v) => Ok(v), + Err(e) => Err(TokenStream::from(e.write_errors())), + } + } + + pub(crate) fn get_serde_with_path(&self) -> Path { + self.alt_crate_path + .clone() + .unwrap_or_else(|| syn::parse_str("::serde_with").unwrap()) + } +} + +// Inspired by https://github.com/serde-rs/serde/blob/fb2fe409c8f7ad6c95e3096e5e9ede865c8cfb49/serde_derive/src/de.rs#L3120 +// Serde is also licensed Apache 2 + MIT +pub(crate) fn split_with_de_lifetime( + generics: &Generics, +) -> ( + DeImplGenerics<'_>, + TypeGenerics<'_>, + Option<&syn::WhereClause>, +) { + let de_impl_generics = DeImplGenerics(generics); + let (_, ty_generics, where_clause) = generics.split_for_impl(); + (de_impl_generics, ty_generics, where_clause) +} + +pub(crate) struct DeImplGenerics<'a>(&'a Generics); + +impl<'a> ToTokens for DeImplGenerics<'a> { + fn to_tokens(&self, tokens: &mut TokenStream2) { + let mut generics = self.0.clone(); + generics.params = Some(parse_quote!('de)) + .into_iter() + .chain(generics.params) + .collect(); + let (impl_generics, _, _) = generics.split_for_impl(); + impl_generics.to_tokens(tokens); + } +} diff --git a/third_party/rust/serde_with_macros/tests/apply.rs b/third_party/rust/serde_with_macros/tests/apply.rs new file mode 100644 index 0000000000..33b721325d --- /dev/null +++ b/third_party/rust/serde_with_macros/tests/apply.rs @@ -0,0 +1,247 @@ +#![allow(dead_code)] + +use expect_test::expect; +use serde_with_macros::apply; +use std::collections::BTreeMap; + +/// Fields `a` and `c` match, as each has a fully specified pattern. +#[test] +fn test_apply_fully_specified() { + #[apply( + crate="serde_with_macros", + + Option<String> => #[serde(skip)], + BTreeMap<String, String> => #[serde(skip)], +)] + #[derive(Default, serde::Serialize)] + struct FooBar<'a> { + a: Option<String>, + b: Option<i32>, + c: BTreeMap<String, String>, + d: BTreeMap<String, i32>, + e: BTreeMap<i32, String>, + f: &'a str, + g: &'static str, + + #[serde_with(skip_apply)] + skip: Option<String>, + } + + expect![[r#" + { + "b": null, + "d": {}, + "e": {}, + "f": "", + "g": "", + "skip": null + }"#]] + .assert_eq(&serde_json::to_string_pretty(&FooBar::<'static>::default()).unwrap()); +} + +/// All fields match as `_` matches any type. +/// +/// The `skip` field is ignored because of the `#[serde_with(skip_apply)]` attribute. +#[test] +fn test_apply_all() { + #[apply( + crate="serde_with_macros", + + _ => #[serde(skip)], +)] + #[derive(Default, serde::Serialize)] + struct FooBar<'a> { + a: Option<String>, + b: Option<i32>, + c: BTreeMap<String, String>, + d: BTreeMap<String, i32>, + e: BTreeMap<i32, String>, + f: &'a str, + g: &'static str, + + #[serde_with(skip_apply)] + skip: Option<String>, + } + + expect![[r#" + { + "skip": null + }"#]] + .assert_eq(&serde_json::to_string_pretty(&FooBar::<'static>::default()).unwrap()); +} + +/// Fields `a` and `b` match, since both are variants of `Option`. +/// +/// No generic in the pattern allows matching with any number of generics on the fields. +/// The `skip` field is ignored because of the `#[serde_with(skip_apply)]` attribute. +#[test] +fn test_apply_partial_no_generic() { + #[apply( + crate="serde_with_macros", + + Option => #[serde(skip)], +)] + #[derive(Default, serde::Serialize)] + struct FooBar<'a> { + a: Option<String>, + b: Option<i32>, + c: BTreeMap<String, String>, + d: BTreeMap<String, i32>, + e: BTreeMap<i32, String>, + f: &'a str, + g: &'static str, + + #[serde_with(skip_apply)] + skip: Option<String>, + } + + expect![[r#" + { + "c": {}, + "d": {}, + "e": {}, + "f": "", + "g": "", + "skip": null + }"#]] + .assert_eq(&serde_json::to_string_pretty(&FooBar::<'static>::default()).unwrap()); +} + +/// Fields `c` and `d` match, as both have a `String` key and `_` matches any type. +#[test] +fn test_apply_partial_generic() { + #[apply( + crate="serde_with_macros", + + BTreeMap<String, _> => #[serde(skip)], +)] + #[derive(Default, serde::Serialize)] + struct FooBar<'a> { + a: Option<String>, + b: Option<i32>, + c: BTreeMap<String, String>, + d: BTreeMap<String, i32>, + e: BTreeMap<i32, String>, + f: &'a str, + g: &'static str, + + #[serde_with(skip_apply)] + skip: Option<String>, + } + + expect![[r#" + { + "a": null, + "b": null, + "e": {}, + "f": "", + "g": "", + "skip": null + }"#]] + .assert_eq(&serde_json::to_string_pretty(&FooBar::<'static>::default()).unwrap()); +} + +/// Fields `f` and `g` match, since no lifetime matches any reference. +#[test] +fn test_apply_no_lifetime() { + #[apply( + crate="serde_with_macros", + + &str => #[serde(skip)], +)] + #[derive(Default, serde::Serialize)] + struct FooBar<'a> { + a: Option<String>, + b: Option<i32>, + c: BTreeMap<String, String>, + d: BTreeMap<String, i32>, + e: BTreeMap<i32, String>, + f: &'a str, + g: &'static str, + + #[serde_with(skip_apply)] + skip: Option<String>, + } + + expect![[r#" + { + "a": null, + "b": null, + "c": {}, + "d": {}, + "e": {}, + "skip": null + }"#]] + .assert_eq(&serde_json::to_string_pretty(&FooBar::<'static>::default()).unwrap()); +} + +/// Field `f` matches as the lifetime is identical and `mut` is ignored. +#[test] +fn test_apply_lifetime() { + #[apply( + crate="serde_with_macros", + + &'a mut str => #[serde(skip)], +)] + #[derive(Default, serde::Serialize)] + struct FooBar<'a> { + a: Option<String>, + b: Option<i32>, + c: BTreeMap<String, String>, + d: BTreeMap<String, i32>, + e: BTreeMap<i32, String>, + f: &'a str, + g: &'static str, + + #[serde_with(skip_apply)] + skip: Option<String>, + } + + expect![[r#" + { + "a": null, + "b": null, + "c": {}, + "d": {}, + "e": {}, + "g": "", + "skip": null + }"#]] + .assert_eq(&serde_json::to_string_pretty(&FooBar::<'static>::default()).unwrap()); +} + +/// No field matches as the explicit lifetimes are different +#[test] +fn test_apply_mismatched_lifetime() { + #[apply( + crate="serde_with_macros", + + &'b str => #[serde(skip)], +)] + #[derive(Default, serde::Serialize)] + struct FooBar<'a> { + a: Option<String>, + b: Option<i32>, + c: BTreeMap<String, String>, + d: BTreeMap<String, i32>, + e: BTreeMap<i32, String>, + f: &'a str, + g: &'static str, + + #[serde_with(skip_apply)] + skip: Option<String>, + } + + expect![[r#" + { + "a": null, + "b": null, + "c": {}, + "d": {}, + "e": {}, + "f": "", + "g": "", + "skip": null + }"#]] + .assert_eq(&serde_json::to_string_pretty(&FooBar::<'static>::default()).unwrap()); +} diff --git a/third_party/rust/serde_with_macros/tests/compiler-messages.rs b/third_party/rust/serde_with_macros/tests/compiler-messages.rs new file mode 100644 index 0000000000..bbb16c32d6 --- /dev/null +++ b/third_party/rust/serde_with_macros/tests/compiler-messages.rs @@ -0,0 +1,9 @@ +#[cfg_attr(tarpaulin, ignore)] +// The error messages are different on beta and nightly, thus breaking the test. +#[rustversion::attr(beta, ignore)] +#[rustversion::attr(nightly, ignore)] +#[test] +fn compile_test() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/compile-fail/*.rs"); +} diff --git a/third_party/rust/serde_with_macros/tests/serde_as_issue_267.rs b/third_party/rust/serde_with_macros/tests/serde_as_issue_267.rs new file mode 100644 index 0000000000..2db1bbea00 --- /dev/null +++ b/third_party/rust/serde_with_macros/tests/serde_as_issue_267.rs @@ -0,0 +1,9 @@ +use serde::Serialize; +use serde_with_macros::serde_as; + +// The field has no serde_as annotation and should not trigger any error +#[serde_as(crate = "serde_with_macros")] +#[derive(Serialize)] +pub struct Thing { + pub id: u8, +} diff --git a/third_party/rust/serde_with_macros/tests/serde_as_issue_538.rs b/third_party/rust/serde_with_macros/tests/serde_as_issue_538.rs new file mode 100644 index 0000000000..2ac2f08cc8 --- /dev/null +++ b/third_party/rust/serde_with_macros/tests/serde_as_issue_538.rs @@ -0,0 +1,30 @@ +//! Check for the correct processing of ExprGroups in types +//! +//! They occur when the types is passed in as a macro_rules argument like here. +//! https://github.com/jonasbb/serde_with/issues/538 + +macro_rules! t { + ($($param:ident : $ty:ty),*) => { + #[derive(Default)] + #[serde_with_macros::apply( + crate = "serde_with_macros", + Option => #[serde(skip_serializing_if = "Option::is_none")], + )] + #[derive(serde::Serialize)] + struct Data { + a: Option<String>, + b: Option<u64>, + c: Option<String>, + $(pub $param: $ty),* + } + } +} + +t!(d: Option<bool>); + +#[test] +fn t() { + let expected = r#"{}"#; + let data: Data = Default::default(); + assert_eq!(expected, serde_json::to_string(&data).unwrap()); +} diff --git a/third_party/rust/serde_with_macros/tests/skip_serializing_null.rs b/third_party/rust/serde_with_macros/tests/skip_serializing_null.rs new file mode 100644 index 0000000000..53412ef1c7 --- /dev/null +++ b/third_party/rust/serde_with_macros/tests/skip_serializing_null.rs @@ -0,0 +1,149 @@ +use pretty_assertions::assert_eq; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use serde_with_macros::skip_serializing_none; + +macro_rules! test { + ($fn:ident, $struct:ident) => { + #[test] + fn $fn() { + let expected = json!({}); + let data = $struct { + a: None, + b: None, + c: None, + d: None, + }; + let res = serde_json::to_value(&data).unwrap(); + assert_eq!(expected, res); + assert_eq!(data, serde_json::from_value(res).unwrap()); + } + }; +} + +macro_rules! test_tuple { + ($fn:ident, $struct:ident) => { + #[test] + fn $fn() { + let expected = json!([]); + let data = $struct(None, None); + let res = serde_json::to_value(&data).unwrap(); + assert_eq!(expected, res); + } + }; +} + +#[skip_serializing_none] +#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] +struct DataBasic { + a: Option<String>, + b: Option<String>, + c: Option<String>, + d: Option<String>, +} +test!(test_basic, DataBasic); + +#[skip_serializing_none] +#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] +struct DataFullyQualified { + a: ::std::option::Option<String>, + b: std::option::Option<String>, + c: ::std::option::Option<i64>, + d: core::option::Option<String>, +} +test!(test_fully_qualified, DataFullyQualified); + +fn never<T>(_t: &T) -> bool { + false +} + +#[skip_serializing_none] +#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] +struct DataExistingAnnotation { + #[serde(skip_serializing_if = "Option::is_none")] + a: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none", rename = "abc")] + b: Option<String>, + #[serde(default)] + c: Option<String>, + #[serde(skip_serializing_if = "never")] + #[serde(rename = "name")] + d: Option<String>, +} + +#[test] +fn test_existing_annotation() { + let expected = json!({ "name": null }); + let data = DataExistingAnnotation { + a: None, + b: None, + c: None, + d: None, + }; + let res = serde_json::to_value(&data).unwrap(); + assert_eq!(expected, res); + assert_eq!(data, serde_json::from_value(res).unwrap()); +} + +#[skip_serializing_none] +#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] +struct DataSerializeAlways { + #[serialize_always] + a: Option<String>, + #[serialize_always] + b: Option<String>, + c: i64, + #[serialize_always] + d: Option<String>, +} + +#[test] +fn test_serialize_always() { + let expected = json!({ + "a": null, + "b": null, + "c": 0, + "d": null + }); + let data = DataSerializeAlways { + a: None, + b: None, + c: 0, + d: None, + }; + let res = serde_json::to_value(&data).unwrap(); + assert_eq!(expected, res); + assert_eq!(data, serde_json::from_value(res).unwrap()); +} + +#[skip_serializing_none] +#[derive(Debug, Eq, PartialEq, Serialize)] +struct DataTuple(Option<String>, std::option::Option<String>); +test_tuple!(test_tuple, DataTuple); + +#[skip_serializing_none] +#[derive(Debug, Eq, PartialEq, Serialize)] +enum DataEnum { + Tuple(Option<i64>, std::option::Option<bool>), + Struct { + a: Option<String>, + b: Option<String>, + }, +} + +#[test] +fn test_enum() { + let expected = json!({ + "Tuple": [] + }); + let data = DataEnum::Tuple(None, None); + let res = serde_json::to_value(data).unwrap(); + assert_eq!(expected, res); + + let expected = json!({ + "Struct": {} + }); + let data = DataEnum::Struct { a: None, b: None }; + let res = serde_json::to_value(data).unwrap(); + assert_eq!(expected, res); +} diff --git a/third_party/rust/serde_with_macros/tests/version_numbers.rs b/third_party/rust/serde_with_macros/tests/version_numbers.rs new file mode 100644 index 0000000000..35560b024b --- /dev/null +++ b/third_party/rust/serde_with_macros/tests/version_numbers.rs @@ -0,0 +1,21 @@ +// Needed to supress a 2021 incompatability warning in the macro generated code +// The non_fmt_panic lint is not yet available on most Rust versions +#![allow(unknown_lints, non_fmt_panics)] + +#[test] +fn test_html_root_url() { + version_sync::assert_html_root_url_updated!("src/lib.rs"); +} + +#[test] +fn test_changelog() { + version_sync::assert_contains_regex!("CHANGELOG.md", r#"## \[{version}\]"#); +} + +#[test] +fn test_serde_with_dependency() { + version_sync::assert_contains_regex!( + "../serde_with/Cargo.toml", + r#"^serde_with_macros = .*? version = "={version}""# + ); +} |