serde_with_macros-3.12.0/.cargo_vcs_info.json0000644000000001570000000000100146230ustar { "git": { "sha1": "5de3400f9cd459abb91cd021ba29c5fd5716c520" }, "path_in_vcs": "serde_with_macros" }serde_with_macros-3.12.0/CHANGELOG.md000064400000000000000000000276271046102023000152370ustar 00000000000000# 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.12.0] - 2024-12-25 No changes. ## [3.11.0] - 2024-10-05 No changes. ## [3.10.0] - 2024-10-01 ### Fixed * Proper handling of `cfg_attr` in the `serde_as` macro by @sivizius (#782) This allows to parse more valid forms of the `cfg_attr` macro, including multiple values and attribute that do not follow the `key = value` schema. ## [3.9.0] - 2024-07-14 No changes. ## [3.8.3] - 2024-07-03 No changes. ## [3.8.2] - 2024-06-30 No changes. ## [3.8.1] - 2024-04-28 No changes. ## [3.8.0] - 2024-04-24 No changes. ## [3.7.0] - 2024-03-11 ### Fixed * Detect conflicting `schema_with` attributes on fields with `schemars` annotations by @swlynch99 (#715) This extends the existing avoidance mechanism to a new variant fixing #712. ## [3.6.1] - 2024-02-08 No changes. ## [3.6.0] - 2024-01-30 No changes. ## [3.5.1] - 2024-01-23 ### Fixed * The `serde_as` macro now better detects existing `schemars` attributes on fields and incorporates them (#682) This avoids errors on existing `#[schemars(with = ...)]` annotations. ## [3.5.0] - 2024-01-20 ### Added * Support for `schemars` integration added by @swlynch99 (#666) The `serde_as` macro can now detect `schemars` usage and emits matching annotations for all fields with `serde_as` attribute. ## [3.4.0] - 2023-10-17 No changes. ## [3.3.0] - 2023-08-19 No changes. ## [3.2.0] - 2023-08-04 No changes. ## [3.1.0] - 2023-07-17 No changes. ## [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 => #[serde(rename = "bool")], )] #[derive(serde::Serialize)] struct Data { a: Option, b: Option, c: Option, d: Option, } ``` The `apply` attribute will expand into this, applying the attributes to the matching fields: ```rust #[derive(serde::Serialize)] struct Data { #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] a: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] b: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] c: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "bool")] d: Option, } ``` 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`, `Option`, or `Option`. ### 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` fields. The `#[serde_as]` macro now detects if a `#[serde_as(as = "Option")]` is used on a field of type `Option` 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 suppressed by using `#[serde_as(no_default)]` or `#[serde_as(as = "Option", 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` fields. The `#[serde_as]` macro now detects if a `#[serde_as(as = "Option")]` is used on a field of type `Option` 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 suppressed by using `#[serde_as(no_default)]` or `#[serde_as(as = "Option", 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, it 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 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 pre-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 pre-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. serde_with_macros-3.12.0/Cargo.toml0000644000000070340000000000100126220ustar # 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.70" name = "serde_with_macros" version = "3.12.0" authors = ["Jonas Bushart"] build = false include = [ "src/**/*", "tests/**/*", "!tests/compile-fail/**", "LICENSE-*", "README.md", "CHANGELOG.md", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false 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] name = "serde_with_macros" path = "src/lib.rs" proc-macro = true [[test]] name = "apply" path = "tests/apply.rs" [[test]] name = "compiler-messages" path = "tests/compiler-messages.rs" [[test]] name = "serde_as_issue_267" path = "tests/serde_as_issue_267.rs" [[test]] name = "serde_as_issue_538" path = "tests/serde_as_issue_538.rs" [[test]] name = "skip_serializing_null" path = "tests/skip_serializing_null.rs" [[test]] name = "version_numbers" path = "tests/version_numbers.rs" [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 = [ "extra-traits", "full", "parsing", ] [dev-dependencies.expect-test] version = "1.5.0" [dev-dependencies.pretty_assertions] version = "1.4.0" [dev-dependencies.rustversion] version = "1.0.0" [dev-dependencies.serde] version = "1.0.152" 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" [features] schemars_0_8 = [] [badges.maintenance] status = "actively-developed" [lints.clippy] cloned_instead_of_copied = "warn" default_trait_access = "warn" doc_markdown = "warn" explicit_auto_deref = "allow" manual-unwrap-or-default = "allow" redundant_closure_for_method_calls = "warn" semicolon_if_nothing_returned = "warn" [lints.rust] missing_docs = "warn" trivial_casts = "warn" trivial_numeric_casts = "warn" unused_extern_crates = "warn" unused_import_braces = "warn" variant_size_differences = "warn" [lints.rust.rust_2018_idioms] level = "warn" priority = -1 [lints.rust.unexpected_cfgs] level = "warn" priority = 0 check-cfg = [ "cfg(tarpaulin)", "cfg(tarpaulin_include)", ] [lints.rustdoc] missing_crate_level_docs = "warn" serde_with_macros-3.12.0/Cargo.toml.orig000064400000000000000000000032161046102023000163010ustar 00000000000000lints.workspace = true [package] authors = ["Jonas Bushart"] name = "serde_with_macros" categories = ["encoding"] description = "proc-macro library for serde_with" documentation = "https://docs.rs/serde_with_macros/" keywords = ["serde", "utilities", "serialization", "deserialization"] edition.workspace = true license.workspace = true readme.workspace = true repository.workspace = true rust-version.workspace = true version.workspace = true include = [ "src/**/*", "tests/**/*", # These tests are a bit more volatile as future compiler upgrade might break them "!tests/compile-fail/**", "LICENSE-*", "README.md", "CHANGELOG.md", ] [lib] proc-macro = true [badges] maintenance = { status = "actively-developed" } [features] schemars_0_8 = [] [dependencies] darling = "0.20.0" proc-macro2 = "1.0.1" quote = "1.0.0" [dependencies.syn] features = [ "extra-traits", "full", "parsing", ] version = "2.0.0" [dev-dependencies] expect-test = "1.5.0" pretty_assertions = "1.4.0" rustversion = "1.0.0" serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.25" trybuild = "1.0.80" version-sync = "0.9.1" [package.metadata.docs.rs] all-features = true [package.metadata.release] pre-release-replacements = [ { file = "CHANGELOG.md", search = "\\[Unreleased\\]", replace = "[Unreleased]\n\n## [{{version}}] - {{date}}" }, { file = "src/lib.rs", search = "https://docs\\.rs/serde_with/[\\d.]+/", replace = "https://docs.rs/serde_with/{{version}}/" }, { file = "src/lib.rs", search = "https://docs\\.rs/serde_with_macros/[\\d.]+/", replace = "https://docs.rs/serde_with_macros/{{version}}/" }, ] tag = false serde_with_macros-3.12.0/LICENSE-APACHE000064400000000000000000000251371046102023000153440ustar 00000000000000 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. serde_with_macros-3.12.0/LICENSE-MIT000064400000000000000000000020231046102023000150410ustar 00000000000000Copyright (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. serde_with_macros-3.12.0/README.md000064400000000000000000000176141046102023000147000ustar 00000000000000# 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 / suffix to each field name of a struct, without changing the de/serialize implementations of the struct using [`with_prefix!`][] / [`with_suffix!`][]. * Deserialize a comma separated list like `#hash,#tags,#are,#great` into a `Vec`. Check the documentation for [`serde_with::StringWithSeparator::`][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`. 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 { #[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, b: Option, c: Option, d: Option, e: Option, f: Option, g: Option, } // 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")] Vec, ), 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")] bytes: Vec<(i32, Vec)>, } } // 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.12.0/serde_with/struct.DisplayFromStr.html [`with_prefix!`]: https://docs.rs/serde_with/3.12.0/serde_with/macro.with_prefix.html [`with_suffix!`]: https://docs.rs/serde_with/3.12.0/serde_with/macro.with_suffix.html [feature flags]: https://docs.rs/serde_with/3.12.0/serde_with/guide/feature_flags/index.html [skip_serializing_none]: https://docs.rs/serde_with/3.12.0/serde_with/attr.skip_serializing_none.html [StringWithSeparator]: https://docs.rs/serde_with/3.12.0/serde_with/struct.StringWithSeparator.html [user guide]: https://docs.rs/serde_with/3.12.0/serde_with/guide/index.html [with-annotation]: https://serde.rs/field-attrs.html#with [as-annotation]: https://docs.rs/serde_with/3.12.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 serde_with_macros-3.12.0/src/apply.rs000064400000000000000000000271341046102023000157010ustar 00000000000000use 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 => #[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, } impl Parse for AddAttributesRule { fn parse(input: ParseStream<'_>) -> Result { let ty: Type = input.parse()?; input.parse::]>()?; 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, rules: Punctuated, } impl Parse for ApplyInput { fn parse(input: ParseStream<'_>) -> Result { let mut metas: Vec = Vec::new(); while input.peek2(Token![=]) && !input.peek2(Token![=>]) { let value = NestedMeta::parse(input)?; metas.push(value); if !input.peek(Token![,]) { break; } input.parse::()?; } let rules: Punctuated = 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, } 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 }" and the `ty_pattern` is "Option". // 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); 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 everything. /// 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; } // Both 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", "Option")); assert!(matches("Option", "Option")); assert!(!matches("Option", "Option")); assert!(matches("BTreeMap", "BTreeMap")); assert!(matches("BTreeMap", "BTreeMap")); assert!(!matches("BTreeMap", "BTreeMap")); assert!(matches("BTreeMap<_, _>", "BTreeMap")); assert!(matches("BTreeMap<_, u8>", "BTreeMap")); assert!(!matches("BTreeMap", "BTreeMap")); } #[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")); } } serde_with_macros-3.12.0/src/lazy_bool.rs000064400000000000000000000075101046102023000165420ustar 00000000000000use core::{ mem, ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not}, }; /// Not-yet evaluated boolean value. #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum LazyBool { /// Like `false`. /// /// This is this type’s default. #[default] False, /// Like `true`. True, /// Not-yet decided. Lazy(T), } impl From for LazyBool { fn from(value: bool) -> Self { match value { false => Self::False, true => Self::True, } } } /// Helper to implement various binary operations on [`LazyBool`]. macro_rules! impl_op { ( < $trait:ident::$method:ident, $assign_trait:ident::$assign_method:ident >($matching:pat_param) { $($pattern:pat => $body:expr),+ $(,)? } $(where $($bound:tt)+)? ) => { impl $trait> for LazyBool where L: $trait, LazyBool: Into>, LazyBool: Into>, $($($bound)+)? { type Output = LazyBool; fn $method(self, rhs: LazyBool) -> Self::Output { match (self, rhs) { (LazyBool::Lazy(lhs), LazyBool::Lazy(rhs)) => LazyBool::Lazy(lhs.$method(rhs)), ($matching, rhs) => rhs.into(), (lhs, $matching) => lhs.into(), $($pattern => $body),+ } } } impl<'a, L, R, T> $trait<&'a LazyBool> for LazyBool where L: $trait<&'a R, Output = T>, LazyBool: Into>, LazyBool: Into> + Clone, $($($bound)+)? { type Output = LazyBool; fn $method(self, rhs: &'a LazyBool) -> Self::Output { match (self, rhs) { (LazyBool::Lazy(lhs), LazyBool::Lazy(rhs)) => LazyBool::Lazy(lhs.$method(rhs)), ($matching, rhs) => rhs.clone().into(), (lhs, $matching) => lhs.into(), $($pattern => $body),+ } } } impl<'a, L, R, T> $trait> for &'a LazyBool where LazyBool: $trait<&'a LazyBool, Output = LazyBool>, { type Output = LazyBool; fn $method(self, rhs: LazyBool) -> Self::Output { rhs.$method(self) } } impl $assign_trait> for LazyBool where LazyBool: $trait, Output = LazyBool>, { fn $assign_method(&mut self, rhs: LazyBool) { let lhs = mem::take(self); *self = lhs.$method(rhs); } } }; } impl_op! { (LazyBool::True){ _ => LazyBool::False } } impl_op! { (LazyBool::False) { _ => LazyBool::True } } impl_op! { (LazyBool::False) { (LazyBool::True, rhs) => (!rhs).into(), (lhs, LazyBool::True) => (!lhs).into(), } where LazyBool: Not>, LazyBool: Not>, } impl Not for LazyBool where T: Not, { type Output = Self; fn not(self) -> Self::Output { match self { Self::False => Self::True, Self::True => Self::False, Self::Lazy(this) => Self::Lazy(!this), } } } impl Not for &LazyBool where LazyBool: Not> + Clone, { type Output = LazyBool; fn not(self) -> Self::Output { !self.clone() } } serde_with_macros-3.12.0/src/lib.rs000064400000000000000000001527351046102023000153300ustar 00000000000000// Cleanup when workspace lints can be overridden // https://github.com/rust-lang/cargo/issues/13157 #![forbid(unsafe_code)] #![warn(missing_copy_implementations, missing_debug_implementations)] #![doc(test(attr( // Problematic handling for foreign From impls in tests // https://github.com/rust-lang/rust/issues/121621 allow(unknown_lints, non_local_definitions), deny( missing_debug_implementations, rust_2018_idioms, trivial_casts, trivial_numeric_casts, unused_extern_crates, unused_import_braces, unused_qualifications, warnings, ), forbid(unsafe_code), )))] // 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.12.0/")] // 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/ mod apply; mod lazy_bool; mod utils; use crate::utils::{ split_with_de_lifetime, DeriveOptions, IteratorExt as _, SchemaFieldCondition, SchemaFieldConfig, }; 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( input: TokenStream, function: F, ) -> Result where F: Copy, F: Fn(&mut Field) -> Result<(), String>, { /// Handle a single struct or a single enum variant fn apply_on_fields(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::(input.clone()) { apply_on_fields(&mut input.fields, function)?; Ok(quote!(#input)) } else if let Ok(mut input) = syn::parse::(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( input: TokenStream, serde_with_crate_path: &Path, function: F, ) -> Result where F: Copy, F: Fn(&mut Field) -> Result<(), DarlingError>, { /// Handle a single struct or a single enum variant fn apply_on_fields(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 = 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 = 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::(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::(input) { // Prevent serde_as on enum variants let mut errors: Vec = 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; /// # /// # #[allow(dead_code)] /// #[derive(Serialize)] /// struct Data { /// #[serde(skip_serializing_if = "Option::is_none")] /// a: Option, /// #[serde(skip_serializing_if = "Option::is_none")] /// b: Option, /// #[serde(skip_serializing_if = "Option::is_none")] /// c: Option, /// #[serde(skip_serializing_if = "Option::is_none")] /// d: Option, /// } /// ``` /// /// 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; /// # /// # #[allow(dead_code)] /// #[skip_serializing_none] /// #[derive(Serialize)] /// struct Data { /// a: Option, /// b: Option, /// c: Option, /// // Always serialize field d even if None /// #[serialize_always] /// d: Option, /// } /// ``` /// /// 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; /// # #[allow(dead_code)] /// type MyOption = Option; /// /// # #[allow(dead_code)] /// #[skip_serializing_none] /// #[derive(Serialize)] /// struct Data { /// a: MyOption, // 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, /// } /// ``` #[proc_macro_attribute] pub fn skip_serializing_none(_args: TokenStream, input: TokenStream) -> TokenStream { let res = apply_function_to_struct_and_enum_fields(input, skip_serializing_none_add_attr_to_field) .unwrap_or_else(|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::::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")] /// 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, /// /// #[serde_as(as = "Option")] /// baz: Option, /// } /// ``` /// /// 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::>`. /// 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` and the attribute `#[serde_as(as = "Option")]` (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` will not see /// any change. /// /// If the automatically applied attribute is undesired, the behavior can be suppressed by adding /// `#[serde_as(no_default)]`. /// /// This can be combined like `#[serde_as(as = "Option", 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::>")] /// bar: Vec, /// /// #[serde_as(as = "Option")] /// #[serde(default)] /// #[serde(with = "::serde_with::As::>")] /// baz: Option, /// } /// ``` /// /// # A note on `schemars` integration /// When the `schemars_0_8` feature is enabled this macro will scan for /// `#[derive(JsonSchema)]` attributes and, if found, will add /// `#[schemars(with = "Schema")]` annotations to any fields with a /// `#[serde_as(as = ...)]` annotation. If you wish to override the default /// behavior here you can add `#[serde_as(schemars = true)]` or /// `#[serde_as(schemars = false)]`. /// /// Note that this macro will check for any of the following derive paths: /// * `JsonSchema` /// * `schemars::JsonSchema` /// * `::schemars::JsonSchema` /// /// It will also work if the relevant derive is behind a `#[cfg_attr]` attribute /// and propagate the `#[cfg_attr]` to the various `#[schemars]` field attributes. /// /// [`serde_as`]: https://docs.rs/serde_with/3.12.0/serde_with/guide/index.html /// [re-exporting `serde_as`]: https://docs.rs/serde_with/3.12.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, #[darling(rename = "schemars")] enable_schemars_support: Option, } 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)); let schemars_config = match container_options.enable_schemars_support { _ if cfg!(not(feature = "schemars_0_8")) => SchemaFieldConfig::False, Some(condition) => condition.into(), None => utils::has_derive_jsonschema(input.clone()).unwrap_or_default(), }; // Convert any error message into a nice compiler error let res = 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, &schemars_config), ) .unwrap_or_else(darling::Error::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, schemars_config: &SchemaFieldConfig, ) -> Result<(), DarlingError> { #[derive(FromField)] #[darling(attributes(serde_as))] struct SerdeAsOptions { /// The original type of the field ty: Type, r#as: Option, deserialize_as: Option, serialize_as: Option, 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, deserialize_with: Option, serialize_with: Option, borrow: Option>, default: Option>, } 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_original = &serde_as_options.ty; 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); match schemars_config { SchemaFieldConfig::False => {} lhs => { let rhs = utils::schemars_with_attr_if( &field.attrs, &["with", "serialize_with", "deserialize_with", "schema_with"], )?; match lhs & !rhs { SchemaFieldConfig::False => {} condition => { let attr_inner_tokens = quote! { #serde_with_crate_path::Schema::<#type_original, #replacement_type> }; let attr_inner_tokens = attr_inner_tokens.to_string(); let attr = match condition { SchemaFieldConfig::False => unreachable!(), SchemaFieldConfig::True => { parse_quote! { #[schemars(with = #attr_inner_tokens)] } } SchemaFieldConfig::Lazy(SchemaFieldCondition(condition)) => { parse_quote! { #[cfg_attr( #condition, schemars(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_.clone(), 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) => { if let Some(Pair::End(mut t)) | Some(Pair::Punctuated(mut t, _)) = inner.path.segments.pop() { 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); } 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 { /// 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.12.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!( ::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 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 where __E: #serde_with_crate_path::serde::de::Error, { value.parse::().map_err(#serde_with_crate_path::serde::de::Error::custom) } fn visit_bytes<__E>( self, value: &[u8] ) -> #serde_with_crate_path::__private__::Result 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.12.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: . #[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 placed *before* any consuming attributes, such as `derive` or /// `serde_as`, because Rust expands all attributes in order. /// /// For example, if your struct or enum contains many `Option` 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`. /// /// ```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, /// b: Option, /// c: Option, /// d: Option, /// } /// # /// # 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 => #[serde(rename = "bool")], /// )] /// # #[derive(serde::Serialize)] /// # #[derive(Default)] /// # struct Data { /// # a: Option, /// # b: Option, /// # c: Option, /// # d: Option, /// # } /// # /// # 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`
`BTreeMap<&'static str, Vec>` | `_` matches all fields. | /// | `Option` | `Option`
`Option` | A missing generic is compatible with any generic arguments. | /// | `Option` | `Option` | A fully specified type only matches exactly. | /// | `BTreeMap` | `BTreeMap` | A fully specified type only matches exactly. | /// | `BTreeMap` | `BTreeMap`
`BTreeMap` | Any `String` key `BTreeMap` matches, as the value is using the `_` placeholder. | /// | `[u8; _]` | `[u8; 1]`
`[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, /// #[serde_with(skip_apply)] /// always_serialize_this_field: Option, /// c: Option, /// d: Option, /// } /// /// 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, /// b: Option, /// c: Option, /// d: Option, /// } /// ``` #[proc_macro_attribute] pub fn apply(args: TokenStream, input: TokenStream) -> TokenStream { apply::apply(args, input) } serde_with_macros-3.12.0/src/utils.rs000064400000000000000000000202011046102023000157000ustar 00000000000000use crate::lazy_bool::LazyBool; use darling::FromDeriveInput; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::ToTokens; use std::ops::{BitAnd, BitOr, Not}; use syn::{ parse::{Parse, ParseStream}, parse_quote, punctuated::Punctuated, Attribute, DeriveInput, Generics, Meta, Path, PathSegment, Token, TypeGenerics, WhereClause, }; /// Merge multiple [`syn::Error`] into one. pub(crate) trait IteratorExt { fn collect_error(self) -> syn::Result<()> where Self: Iterator> + 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 IteratorExt for I where I: Iterator> + 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, } impl DeriveOptions { pub(crate) fn from_derive_input(input: &DeriveInput) -> Result { match ::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<&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 ToTokens for DeImplGenerics<'_> { 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); } } /// Represents the macro body of a `#[cfg_attr]` attribute. /// /// ```text /// #[cfg_attr(feature = "things", derive(Macro))] /// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ /// ``` struct CfgAttr { condition: Meta, _comma: Token![,], metas: Punctuated, } impl Parse for CfgAttr { fn parse(input: ParseStream<'_>) -> syn::Result { Ok(Self { condition: input.parse()?, _comma: input.parse()?, metas: Punctuated::parse_terminated(input)?, }) } } /// Determine if there is a `#[derive(JsonSchema)]` on this struct. pub(crate) fn has_derive_jsonschema(input: TokenStream) -> syn::Result { fn parse_derive_args(input: ParseStream<'_>) -> syn::Result> { Punctuated::parse_terminated_with(input, Path::parse_mod_style) } fn eval_metas<'a>(metas: impl IntoIterator) -> syn::Result { metas .into_iter() .map(eval_meta) .try_fold( SchemaFieldConfig::False, |state, result| Ok(state | result?), ) } fn eval_meta(meta: &Meta) -> syn::Result { match meta.path() { path if path.is_ident("cfg_attr") => { let CfgAttr { condition, metas, .. } = meta.require_list()?.parse_args()?; Ok(eval_metas(&metas)? & SchemaFieldConfig::Lazy(condition.into())) } path if path.is_ident("derive") => { let config = meta .require_list()? .parse_args_with(parse_derive_args)? .into_iter() .any(|Path { segments, .. }| { // This matches `JsonSchema`, `schemars::JsonSchema` // as well as any other path ending with `JsonSchema`. // This will not match aliased `JsonSchema`s, // but might match other `JsonSchema` not `schemars::JsonSchema`! match segments.last() { Some(PathSegment { ident, .. }) => ident == "JsonSchema", _ => false, } }) .then_some(SchemaFieldConfig::True) .unwrap_or_default(); Ok(config) } _ => Ok(SchemaFieldConfig::False), } } let DeriveInput { attrs, .. } = syn::parse(input)?; let metas = attrs.iter().map(|Attribute { meta, .. }| meta); eval_metas(metas) } /// Enum controlling when we should emit a `#[schemars]` field attribute. pub(crate) type SchemaFieldConfig = LazyBool; impl From for SchemaFieldConfig { fn from(meta: Meta) -> Self { Self::Lazy(meta.into()) } } #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub(crate) struct SchemaFieldCondition(pub(crate) Meta); impl BitAnd for SchemaFieldCondition { type Output = Self; fn bitand(self, Self(rhs): Self) -> Self::Output { let Self(lhs) = self; Self(parse_quote!(all(#lhs, #rhs))) } } impl BitAnd<&SchemaFieldCondition> for SchemaFieldCondition { type Output = Self; fn bitand(self, Self(rhs): &Self) -> Self::Output { let Self(lhs) = self; Self(parse_quote!(all(#lhs, #rhs))) } } impl BitOr for SchemaFieldCondition { type Output = Self; fn bitor(self, Self(rhs): Self) -> Self::Output { let Self(lhs) = self; Self(parse_quote!(any(#lhs, #rhs))) } } impl Not for SchemaFieldCondition { type Output = Self; fn not(self) -> Self::Output { let Self(condition) = self; Self(parse_quote!(not(#condition))) } } impl From for SchemaFieldCondition { fn from(meta: Meta) -> Self { Self(meta) } } /// Get a `#[cfg]` expression under which this field has a `#[schemars]` attribute /// with a `with = ...` argument. pub(crate) fn schemars_with_attr_if( attrs: &[Attribute], filter: &[&str], ) -> syn::Result { fn eval_metas<'a>( filter: &[&str], metas: impl IntoIterator, ) -> syn::Result { metas .into_iter() .map(|meta| eval_meta(filter, meta)) .try_fold( SchemaFieldConfig::False, |state, result| Ok(state | result?), ) } fn eval_meta(filter: &[&str], meta: &Meta) -> syn::Result { match meta.path() { path if path.is_ident("cfg_attr") => { let CfgAttr { condition, metas, .. } = meta.require_list()?.parse_args()?; Ok(eval_metas(filter, &metas)? & SchemaFieldConfig::from(condition)) } path if path.is_ident("schemars") => { let config = meta .require_list()? .parse_args_with(>::parse_terminated)? .into_iter() .any(|meta| match meta.path().get_ident() { Some(ident) => filter.iter().any(|relevant| ident == relevant), _ => false, }) .then_some(SchemaFieldConfig::True) .unwrap_or_default(); Ok(config) } _ => Ok(SchemaFieldConfig::False), } } let metas = attrs.iter().map(|Attribute { meta, .. }| meta); eval_metas(filter, metas) } serde_with_macros-3.12.0/tests/apply.rs000064400000000000000000000133261046102023000162520ustar 00000000000000//! Test Cases #![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 => #[serde(skip)], BTreeMap => #[serde(skip)], )] #[derive(Default, serde::Serialize)] struct FooBar<'a> { a: Option, b: Option, c: BTreeMap, d: BTreeMap, e: BTreeMap, f: &'a str, g: &'static str, #[serde_with(skip_apply)] skip: Option, } 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, b: Option, c: BTreeMap, d: BTreeMap, e: BTreeMap, f: &'a str, g: &'static str, #[serde_with(skip_apply)] skip: Option, } 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, b: Option, c: BTreeMap, d: BTreeMap, e: BTreeMap, f: &'a str, g: &'static str, #[serde_with(skip_apply)] skip: Option, } 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 => #[serde(skip)], )] #[derive(Default, serde::Serialize)] struct FooBar<'a> { a: Option, b: Option, c: BTreeMap, d: BTreeMap, e: BTreeMap, f: &'a str, g: &'static str, #[serde_with(skip_apply)] skip: Option, } 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, b: Option, c: BTreeMap, d: BTreeMap, e: BTreeMap, f: &'a str, g: &'static str, #[serde_with(skip_apply)] skip: Option, } 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, b: Option, c: BTreeMap, d: BTreeMap, e: BTreeMap, f: &'a str, g: &'static str, #[serde_with(skip_apply)] skip: Option, } 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, b: Option, c: BTreeMap, d: BTreeMap, e: BTreeMap, f: &'a str, g: &'static str, #[serde_with(skip_apply)] skip: Option, } expect![[r#" { "a": null, "b": null, "c": {}, "d": {}, "e": {}, "f": "", "g": "", "skip": null }"#]] .assert_eq(&serde_json::to_string_pretty(&FooBar::<'static>::default()).unwrap()); } serde_with_macros-3.12.0/tests/compiler-messages.rs000064400000000000000000000004761046102023000205460ustar 00000000000000//! Test Cases #[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"); } serde_with_macros-3.12.0/tests/serde_as_issue_267.rs000064400000000000000000000003751046102023000205200ustar 00000000000000#![allow(missing_docs)] 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, } serde_with_macros-3.12.0/tests/serde_as_issue_538.rs000064400000000000000000000014551046102023000205210ustar 00000000000000//! Check for the correct processing of `ExprGroup`s in types //! //! They occur when the types is passed in as a `macro_rules` argument like here. //! 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, b: Option, c: Option, $(pub $param: $ty),* } } } t!(d: Option); #[test] fn t() { let expected = r#"{}"#; let data = Data::default(); assert_eq!(expected, serde_json::to_string(&data).unwrap()); } serde_with_macros-3.12.0/tests/skip_serializing_null.rs000064400000000000000000000076321046102023000215300ustar 00000000000000//! Test Cases 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, b: Option, c: Option, d: Option, } test!(test_basic, DataBasic); // This tests different ways of qualifying the Option type #[allow(unused_qualifications)] #[skip_serializing_none] #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] struct DataFullyQualified { a: ::std::option::Option, b: std::option::Option, c: ::std::option::Option, d: core::option::Option, } test!(test_fully_qualified, DataFullyQualified); fn never(_t: &T) -> bool { false } #[skip_serializing_none] #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] struct DataExistingAnnotation { #[serde(skip_serializing_if = "Option::is_none")] a: Option, #[serde(default, skip_serializing_if = "Option::is_none", rename = "abc")] b: Option, #[serde(default)] c: Option, #[serde(skip_serializing_if = "never")] #[serde(rename = "name")] d: Option, } #[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, #[serialize_always] b: Option, c: i64, #[serialize_always] d: Option, } #[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()); } // This tests different ways of qualifying the Option type #[allow(unused_qualifications)] #[skip_serializing_none] #[derive(Debug, Eq, PartialEq, Serialize)] struct DataTuple(Option, std::option::Option); test_tuple!(test_tuple, DataTuple); // This tests different ways of qualifying the Option type #[allow(unused_qualifications)] #[skip_serializing_none] #[derive(Debug, Eq, PartialEq, Serialize)] enum DataEnum { Tuple(Option, std::option::Option), Struct { a: Option, b: Option, }, } #[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); } serde_with_macros-3.12.0/tests/version_numbers.rs000064400000000000000000000006421046102023000203420ustar 00000000000000//! Test Cases #[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}""# ); }