├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── CHANGELOG.md
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── assets
└── logo-512.png
├── ci.sh
├── source
├── postcard-derive
│ ├── Cargo.toml
│ └── src
│ │ ├── lib.rs
│ │ ├── max_size.rs
│ │ └── schema.rs
├── postcard-dyn
│ ├── .gitignore
│ ├── Cargo.toml
│ └── src
│ │ ├── de.rs
│ │ ├── lib.rs
│ │ └── ser.rs
├── postcard-schema
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ │ ├── impls
│ │ │ ├── builtins_alloc.rs
│ │ │ ├── builtins_nostd.rs
│ │ │ ├── builtins_std.rs
│ │ │ ├── chrono_v0_4.rs
│ │ │ ├── heapless_v0_7.rs
│ │ │ ├── heapless_v0_8.rs
│ │ │ ├── mod.rs
│ │ │ ├── nalgebra_v0_33.rs
│ │ │ └── uuid_v1_0.rs
│ │ ├── key
│ │ │ ├── hash.rs
│ │ │ └── mod.rs
│ │ ├── lib.rs
│ │ └── schema
│ │ │ ├── fmt.rs
│ │ │ ├── mod.rs
│ │ │ └── owned.rs
│ └── tests
│ │ └── schema.rs
└── postcard
│ ├── Cargo.toml
│ ├── README.md
│ ├── src
│ ├── accumulator.rs
│ ├── de
│ │ ├── deserializer.rs
│ │ ├── flavors.rs
│ │ └── mod.rs
│ ├── eio.rs
│ ├── error.rs
│ ├── fixint.rs
│ ├── lib.rs
│ ├── max_size.rs
│ ├── ser
│ │ ├── flavors.rs
│ │ ├── mod.rs
│ │ └── serializer.rs
│ └── varint.rs
│ └── tests
│ ├── accumulator.rs
│ ├── crc.rs
│ ├── loopback.rs
│ └── max_size.rs
└── spec
├── .gitignore
├── LICENSE-CC-BY-SA
├── book.toml
└── src
├── SUMMARY.md
├── glossary.md
├── intro.md
├── serde-data-model.md
└── wire-format.md
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | # Cancel old workflows for PRs (only the most recent workflow can run).
12 | concurrency:
13 | group: ci-${{ github.ref }}
14 | cancel-in-progress: ${{ github.event_name == 'pull_request' }}
15 |
16 | # Avoid workflow-level permissions, instead use job-level permissions.
17 | permissions: {}
18 |
19 | jobs:
20 | ubuntu:
21 | runs-on: ubuntu-latest
22 | steps:
23 | - uses: actions/checkout@v4
24 | - run: ./ci.sh
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | **/*.rs.bk
3 | Cargo.lock
4 | .vscode
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | Changes will be described here.
4 |
5 | ## 1.0.8 -> Unreleased
6 |
7 | * None yet!
8 |
9 | ## 1.0.7 -> 1.0.8
10 |
11 | * Add IO traits support by @xgroleau in https://github.com/jamesmunns/postcard/pull/91
12 |
13 | ## 1.0.6 -> 1.0.7
14 |
15 | * Fix off-by-one in varint size calculation by @Palladinium in https://github.com/jamesmunns/postcard/pull/111
16 | * Add specific error for Crc errors by @CBJamo in https://github.com/jamesmunns/postcard/pull/112
17 |
18 | ## 1.0.5 -> 1.0.6
19 |
20 | * Add cfg information to docs by @dtolnay in https://github.com/jamesmunns/postcard/pull/108
21 |
22 | ## 1.0.4 -> 1.0.5
23 |
24 | * improved documentation of the cobs decoding by @gutzchi in https://github.com/jamesmunns/postcard/pull/97
25 | * Fix strict provenance use of pointers by @jamesmunns in https://github.com/jamesmunns/postcard/pull/100
26 | * Introduces CRCs by @huntc in https://github.com/jamesmunns/postcard/pull/98
27 | * Format with rustfmt to eliminate trailing whitespaces by @dtolnay in https://github.com/jamesmunns/postcard/pull/104
28 | * Fix documentation of re-exports that have dedicated doc comment by @dtolnay in https://github.com/jamesmunns/postcard/pull/107
29 |
30 | ## New Contributors
31 |
32 | * @gutzchi made their first contribution in https://github.com/jamesmunns/postcard/pull/97
33 |
34 | **Full Changelog**: https://github.com/jamesmunns/postcard/compare/v1.0.4...v1.0.5
35 |
36 | ## 1.0.3 -> 1.0.4
37 |
38 | * fix cobs accumulator out-of-bounds index when data is 1 byte too long ([PR#90])
39 | * Move cobs accumulator tests into a `cfg(test)` module
40 |
41 | [PR#90]: https://github.com/jamesmunns/postcard/pull/90
42 |
43 | ## 1.0.2 -> 1.0.3
44 |
45 | * PhantomData's T doesn't need MaxSize impl ([PR#87])
46 | * Add function for computing the postcard serialized size of a value. ([PR#86])
47 | * Fix typos & encoding example in wire doc ([PR#83])
48 | * Optimize varint decode ([PR#62])
49 | * Bump postcard-derive version ([PR#74])
50 | * add std::string::String and alloc::string::String ([PR#76])
51 | * Make fixints usable through serde field attributes instead of wrappers ([PR#69])
52 | * Add support for 16-bit and 8-bit architectures ([PR#64])
53 | * Add feed_ref to cobs_accumulator ([PR#70])
54 | * Add a link to doc.rs documentation in README ([PR#72])
55 |
56 | [PR#87]: https://github.com/jamesmunns/postcard/pull/87
57 | [PR#86]: https://github.com/jamesmunns/postcard/pull/86
58 | [PR#83]: https://github.com/jamesmunns/postcard/pull/83
59 | [PR#62]: https://github.com/jamesmunns/postcard/pull/62
60 | [PR#74]: https://github.com/jamesmunns/postcard/pull/74
61 | [PR#76]: https://github.com/jamesmunns/postcard/pull/76
62 | [PR#69]: https://github.com/jamesmunns/postcard/pull/69
63 | [PR#64]: https://github.com/jamesmunns/postcard/pull/64
64 | [PR#70]: https://github.com/jamesmunns/postcard/pull/70
65 | [PR#72]: https://github.com/jamesmunns/postcard/pull/72
66 |
67 | ## 1.0.1 -> 1.0.2
68 |
69 | * Correct exporting of experimental Schema proc macro ([PR#73])
70 |
71 | [PR#73]: https://github.com/jamesmunns/postcard/pull/73
72 |
73 | ## 1.0.0 -> 1.0.1
74 |
75 | * [Fixed deserialization] of `i128`, which was using the "new style" varint serialization, but the incorrect, "old style" fixed deserialization.
76 | * This is considered a defect, and not a breaking change, as it brings the code back in line with the specification behavior.
77 | * Version 1.0.0 will be yanked due to this defect.
78 |
79 | [Fixed deserialization]: https://github.com/jamesmunns/postcard/commit/70ea33a1ac7f82632697f4578002267eaf9095f5
80 |
81 | ## 1.0.0-alpha.4 -> 1.0.0
82 |
83 | * Added experimental derive features
84 | * Made Flavor fields private again
85 | * Optimized varint encoding
86 | * Use crate `Result` for `Flavor`s
87 |
88 | ## 1.0.0-alpha.3 -> 1.0.0-alpha.4
89 |
90 | * Updated the signature of deserialization `Flavor` trait
91 | * Added documentation and tests
92 | * Removed the `Encoder` wrapper type to better match serialization and deserialization types
93 | * Renamed `ser_flavor::Flavor::release` to `finalize` for consistency
94 | * Re-organized some public items and modules
95 | * Made `Error` non-exhaustive
96 | * Added a `fixint` type to avoid varints
97 |
98 | ## 1.0.0-alpha.2 -> 1.0.0-alpha.3
99 |
100 | * Moved back to `cobs` from `postcard-cobs`
101 | * This fixed a number of upstream issues, including removal of panicking branches
102 | * Improved documentation and code examples
103 | * Corrected the behavior of `take_from_cobs`
104 | * Added support for serializing `Debug`/`Display` representation strings via serde's `collect_str` method (and removed the panic)
105 |
106 | ## 1.0.0-alpha.1 -> 1.0.0-alpha.2
107 |
108 | * Re-exposed fields of the Flavor constructors, made various flavors impl `Default`
109 | * No breaking changes vs `1.0.0-alpha.1`.
110 |
111 | ## 0.7.3 -> 1.0.0-alpha.1
112 |
113 | * WARNING: This includes a BREAKING wire change from postcard v0.x.y! Please ensure
114 | all devices using postcard are recompiled with the newest version!
115 | * added `#[inline]` to many functions, increasing performance
116 | * All unsigned integers u16-u128 are varint encoded
117 | * All signed integers i16-i128 are zigzag + varint encoded
118 | * Serialization flavors have been tweaked slightly, with the `Slice` flavor now faster
119 | * Introduction of Deserialization flavors
120 | * Please report any bugs upstream as we prepare for the v1.0.0 release!
121 |
122 | ## 0.7.2 -> 0.7.3
123 |
124 | * Added optional [`defmt`](https://crates.io/crates/defmt) support with the `use-defmt` feature.
125 | * Improved docs
126 |
127 | ## 0.7.1 -> 0.7.2
128 |
129 | * Changed the `CobsAccumulator::new()` into a const fn.
130 |
131 | ## 0.7.0 -> 0.7.1
132 |
133 | * Added the `CobsAccumulator` type for accumulating COBS encoded data for deserialization.
134 |
135 | ## 0.6.x -> 0.7.0
136 |
137 | * Updated `heapless` dependency to `v0.7.0`, which added support for const-generic sized buffers.
138 |
139 | ## Prior to 0.7.0
140 |
141 | * No changelog information added yet.
142 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | resolver = '2'
3 | members = [
4 | "source/postcard",
5 | "source/postcard-derive",
6 | "source/postcard-dyn",
7 | "source/postcard-schema",
8 | ]
9 |
--------------------------------------------------------------------------------
/LICENSE-APACHE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Copyright (c) 2019 Anthony James Munns
2 |
3 | Permission is hereby granted, free of charge, to any
4 | person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the
6 | Software without restriction, including without
7 | limitation the rights to use, copy, modify, merge,
8 | publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software
10 | is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice
14 | shall be included in all copies or substantial portions
15 | of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 | DEALINGS IN THE SOFTWARE.
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Postcard
2 |
3 | [](https://docs.rs/postcard)
4 |
5 |
6 |
7 | Postcard is a `#![no_std]` focused serializer and deserializer for Serde.
8 |
9 | Postcard aims to be convenient for developers in constrained environments, while
10 | allowing for flexibility to customize behavior as needed.
11 |
12 | ## Design Goals
13 |
14 | 1. Design primarily for `#![no_std]` usage, in embedded or other constrained contexts
15 | 2. Support a maximal set of `serde` features, so `postcard` can be used as a drop in replacement
16 | 3. Avoid special differences in code between communication code written for a microcontroller or a desktop/server PC
17 | 4. Be resource efficient - memory usage, code size, developer time, and CPU time; in that order
18 | 5. Allow library users to customize the serialization and deserialization behavior to fit their bespoke needs
19 |
20 | ## Format Stability
21 |
22 | As of v1.0.0, `postcard` has a documented and stable wire format. More information about this
23 | wire format can be found in the `spec/` folder of the Postcard repository, or viewed online
24 | at .
25 |
26 | Work towards the Postcard Specification and portions of the Postcard 1.0 Release
27 | were sponsored by Mozilla Corporation.
28 |
29 | ## Variable Length Data
30 |
31 | All signed and unsigned integers larger than eight bits are encoded using a [Varint].
32 | This includes the length of array slices, as well as the discriminant of `enums`.
33 |
34 | For more information, see the [Varint] chapter of the wire specification.
35 |
36 | [Varint]: https://postcard.jamesmunns.com/wire-format.html#varint-encoded-integers
37 |
38 | ## Example - Serialization/Deserialization
39 |
40 | Postcard can serialize and deserialize messages similar to other `serde` formats.
41 |
42 | Using the default `heapless` feature to serialize to a `heapless::Vec`:
43 |
44 | ```rust
45 | use core::ops::Deref;
46 | use serde::{Serialize, Deserialize};
47 | use postcard::{from_bytes, to_vec};
48 | use heapless::Vec;
49 |
50 | #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
51 | struct RefStruct<'a> {
52 | bytes: &'a [u8],
53 | str_s: &'a str,
54 | }
55 | let message = "hElLo";
56 | let bytes = [0x01, 0x10, 0x02, 0x20];
57 | let output: Vec = to_vec(&RefStruct {
58 | bytes: &bytes,
59 | str_s: message,
60 | }).unwrap();
61 |
62 | assert_eq!(
63 | &[0x04, 0x01, 0x10, 0x02, 0x20, 0x05, b'h', b'E', b'l', b'L', b'o',],
64 | output.deref()
65 | );
66 |
67 | let out: RefStruct = from_bytes(output.deref()).unwrap();
68 | assert_eq!(
69 | out,
70 | RefStruct {
71 | bytes: &bytes,
72 | str_s: message,
73 | }
74 | );
75 | ```
76 |
77 | Or the optional `alloc` feature to serialize to an `alloc::vec::Vec`:
78 |
79 | ```rust
80 | use core::ops::Deref;
81 | use serde::{Serialize, Deserialize};
82 | use postcard::{from_bytes, to_allocvec};
83 | extern crate alloc;
84 | use alloc::vec::Vec;
85 |
86 | #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
87 | struct RefStruct<'a> {
88 | bytes: &'a [u8],
89 | str_s: &'a str,
90 | }
91 | let message = "hElLo";
92 | let bytes = [0x01, 0x10, 0x02, 0x20];
93 | let output: Vec = to_allocvec(&RefStruct {
94 | bytes: &bytes,
95 | str_s: message,
96 | }).unwrap();
97 |
98 | assert_eq!(
99 | &[0x04, 0x01, 0x10, 0x02, 0x20, 0x05, b'h', b'E', b'l', b'L', b'o',],
100 | output.deref()
101 | );
102 |
103 | let out: RefStruct = from_bytes(output.deref()).unwrap();
104 | assert_eq!(
105 | out,
106 | RefStruct {
107 | bytes: &bytes,
108 | str_s: message,
109 | }
110 | );
111 | ```
112 |
113 | ## Flavors
114 |
115 | `postcard` supports a system called `Flavors`, which are used to modify the way
116 | postcard serializes or processes serialized data. These flavors act as "plugins" or "middlewares"
117 | during the serialization or deserialization process, and can be combined to obtain complex protocol formats.
118 |
119 | See the documentation of the `ser_flavors` or `de_flavors` modules for more information on usage.
120 |
121 | ## Setup - `Cargo.toml`
122 |
123 | Don't forget to add [the `no-std` subset](https://serde.rs/no-std.html) of `serde` along with `postcard` to the `[dependencies]` section of your `Cargo.toml`!
124 |
125 | ```toml
126 | [dependencies]
127 | postcard = "1.0.0"
128 |
129 | # By default, `serde` has the `std` feature enabled, which makes it unsuitable for embedded targets
130 | # disabling default-features fixes this
131 | serde = { version = "1.0.*", default-features = false }
132 | ```
133 |
134 | ## Unsupported serde attributes
135 |
136 | In the case `serde` attributes are used with `postcard`, the serialization and deserialization
137 | process should be tested extensively as multiple attributes can cause problems.
138 |
139 | > See the [tracking issue](https://github.com/jamesmunns/postcard/issues/125)
140 |
141 | Some serde attributes break serialization/deserialization like:
142 |
143 | - `serde(flatten)`
144 | - `serde(skip_serializing_if($COND))`
145 |
146 | Some attributes can cause silent issues:
147 |
148 | - `serde(skip)` Can break deserialization. If used for enum variants, make sure that
149 | the skipped fields are the last variants, otherwise `postcard` will attempt to deserialize
150 | based on an offsetted discriminant, causing failure, or silent mismatch on C-like enums.
151 |
152 | ## License
153 |
154 | Licensed under either of
155 |
156 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
157 | )
158 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or )
159 |
160 | at your option.
161 |
162 | ### Contribution
163 |
164 | Unless you explicitly state otherwise, any contribution intentionally submitted
165 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
166 | dual licensed as above, without any additional terms or conditions.
167 |
--------------------------------------------------------------------------------
/assets/logo-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesmunns/postcard/c198b9733518a8d7f64fb142c47f98aa2d2194dc/assets/logo-512.png
--------------------------------------------------------------------------------
/ci.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -ex
3 |
4 | has_target() {
5 | rustup target list --installed | grep -q "$1"
6 | }
7 | ensure_target() {
8 | has_target "$1" || rustup target add "$1"
9 | }
10 |
11 | ensure_target thumbv7em-none-eabi
12 | ensure_target riscv32i-unknown-none-elf
13 |
14 | has_toolchain() {
15 | rustup toolchain list | grep -q "$1"
16 | }
17 | ensure_toolchain() {
18 | has_toolchain "$1" || rustup toolchain install "$1"
19 | }
20 |
21 | ensure_toolchain nightly
22 |
23 | cargo_check() {
24 | cargo check --all "$@"
25 | cargo clippy --all "$@" -- --deny=warnings
26 | }
27 | cargo_test() {
28 | cargo_check --all-targets "$@"
29 | cargo test --all "$@"
30 | }
31 |
32 | cargo_test --features=alloc,experimental-derive,use-std,use-crc,derive,nalgebra-v0_33,heapless-v0_8
33 |
34 | # NOTE: we exclude postcard-dyn for these checks because it is std-only
35 |
36 | cargo_check --target=thumbv7em-none-eabi --no-default-features --exclude postcard-dyn
37 | cargo_check --target=thumbv7em-none-eabi --features=alloc,experimental-derive --exclude postcard-dyn
38 |
39 | # CC https://github.com/jamesmunns/postcard/issues/167 - don't accidentally use atomics
40 | # on non-atomic systems
41 | cargo_check --target=riscv32i-unknown-none-elf --features=alloc,experimental-derive --exclude postcard-dyn
42 |
43 | cargo fmt --all -- --check
44 |
45 | # Check docs.rs build
46 | #
47 | # TODO: We SHOULDN'T exclude postcard-dyn but it does weird things with feature unification and
48 | # makes the embedded-io stuff break
49 | env RUSTDOCFLAGS='--cfg=docsrs --deny=warnings' cargo +nightly doc --all --no-deps --all-features --exclude postcard-dyn
50 |
--------------------------------------------------------------------------------
/source/postcard-derive/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "postcard-derive"
3 | version = "0.2.1"
4 | authors = [
5 | "Lachlan Sneff ",
6 | "James Munns ",
7 | ]
8 | edition = "2021"
9 | readme = "../../README.md"
10 | repository = "https://github.com/jamesmunns/postcard"
11 | description = "A no_std + serde compatible message library for Rust - Derive Crate"
12 | license = "MIT OR Apache-2.0"
13 | categories = [
14 | "embedded",
15 | "no-std",
16 | ]
17 | keywords = [
18 | "serde",
19 | "cobs",
20 | "framing",
21 | ]
22 | documentation = "https://docs.rs/postcard-derive/"
23 |
24 | [lib]
25 | proc-macro = true
26 |
27 | [package.metadata.docs.rs]
28 | all-features = true
29 |
30 | [dependencies]
31 | syn = "2.0"
32 | quote = "1.0"
33 | proc-macro2 = "1.0"
34 |
--------------------------------------------------------------------------------
/source/postcard-derive/src/lib.rs:
--------------------------------------------------------------------------------
1 | use syn::{parse_macro_input, DeriveInput};
2 |
3 | mod max_size;
4 | mod schema;
5 |
6 | /// Derive the `postcard::MaxSize` trait for a struct or enum.
7 | #[proc_macro_derive(MaxSize)]
8 | pub fn derive_max_size(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
9 | max_size::do_derive_max_size(item)
10 | }
11 |
12 | /// Derive the `postcard_schema::Schema` trait for a struct or enum.
13 | #[proc_macro_derive(Schema, attributes(postcard))]
14 | pub fn derive_schema(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
15 | let input = parse_macro_input!(item as DeriveInput);
16 | schema::do_derive_schema(input)
17 | .unwrap_or_else(syn::Error::into_compile_error)
18 | .into()
19 | }
20 |
--------------------------------------------------------------------------------
/source/postcard-derive/src/max_size.rs:
--------------------------------------------------------------------------------
1 | use proc_macro2::{Span, TokenStream};
2 | use quote::{quote, quote_spanned};
3 | use syn::{
4 | parse_macro_input, parse_quote, spanned::Spanned, Data, DeriveInput, Fields, GenericParam,
5 | Generics,
6 | };
7 |
8 | pub fn do_derive_max_size(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
9 | let input = parse_macro_input!(item as DeriveInput);
10 |
11 | let span = input.span();
12 | let name = input.ident;
13 |
14 | // Add a bound `T: MaxSize` to every type parameter T.
15 | let generics = add_trait_bounds(input.generics);
16 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
17 |
18 | let sum = max_size_sum(&input.data, span).unwrap_or_else(syn::Error::into_compile_error);
19 |
20 | let expanded = quote! {
21 | impl #impl_generics ::postcard::experimental::max_size::MaxSize for #name #ty_generics #where_clause {
22 | const POSTCARD_MAX_SIZE: usize = #sum;
23 | }
24 | };
25 |
26 | expanded.into()
27 | }
28 |
29 | /// Add a bound `T: MaxSize` to every type parameter T.
30 | fn add_trait_bounds(mut generics: Generics) -> Generics {
31 | for param in &mut generics.params {
32 | if let GenericParam::Type(ref mut type_param) = *param {
33 | type_param
34 | .bounds
35 | .push(parse_quote!(::postcard::experimental::max_size::MaxSize));
36 | }
37 | }
38 | generics
39 | }
40 |
41 | /// Generate a constant expression that sums up the maximum size of the type.
42 | fn max_size_sum(data: &Data, span: Span) -> Result {
43 | match data {
44 | Data::Struct(data) => Ok(sum_fields(&data.fields)),
45 | Data::Enum(data) => {
46 | let variant_count = data.variants.len();
47 |
48 | let recurse = data.variants.iter().map(|v| sum_fields(&v.fields));
49 |
50 | let discriminant_size = varint_size_discriminant(variant_count as u32) as usize;
51 |
52 | // Generate a tree of max expressions.
53 | let max = recurse.fold(quote!(0), |acc, x| {
54 | quote! {
55 | {
56 | let lhs = #acc;
57 | let rhs = #x;
58 | if lhs > rhs {
59 | lhs
60 | } else {
61 | rhs
62 | }
63 | }
64 | }
65 | });
66 |
67 | Ok(quote! {
68 | #discriminant_size + #max
69 | })
70 | }
71 | Data::Union(_) => Err(syn::Error::new(
72 | span,
73 | "unions are not supported by `postcard::MaxSize`",
74 | )),
75 | }
76 | }
77 |
78 | fn sum_fields(fields: &Fields) -> TokenStream {
79 | match fields {
80 | syn::Fields::Named(fields) => {
81 | // Expands to an expression like
82 | //
83 | // 0 + ::POSTCARD_MAX_SIZE + ::POSTCARD_MAX_SIZE + ...
84 | //
85 | // but using fully qualified syntax.
86 |
87 | let recurse = fields.named.iter().map(|f| {
88 | let ty = &f.ty;
89 | quote_spanned! { f.span() => <#ty as ::postcard::experimental::max_size::MaxSize>::POSTCARD_MAX_SIZE }
90 | });
91 |
92 | quote! {
93 | 0 #(+ #recurse)*
94 | }
95 | }
96 | syn::Fields::Unnamed(fields) => {
97 | let recurse = fields.unnamed.iter().map(|f| {
98 | let ty = &f.ty;
99 | quote_spanned! { f.span() => <#ty as ::postcard::experimental::max_size::MaxSize>::POSTCARD_MAX_SIZE }
100 | });
101 |
102 | quote! {
103 | 0 #(+ #recurse)*
104 | }
105 | }
106 | syn::Fields::Unit => quote!(0),
107 | }
108 | }
109 |
110 | fn varint_size_discriminant(max_n: u32) -> u32 {
111 | const BITS_PER_BYTE: u32 = 8;
112 | const BITS_PER_VARINT_BYTE: u32 = 7;
113 |
114 | // How many data bits do we need for `max_n`.
115 | let bits = core::mem::size_of::() as u32 * BITS_PER_BYTE - max_n.leading_zeros();
116 |
117 | let roundup_bits = bits + (BITS_PER_VARINT_BYTE - 1);
118 |
119 | // Apply division, using normal "round down" integer division
120 | roundup_bits / BITS_PER_VARINT_BYTE
121 | }
122 |
--------------------------------------------------------------------------------
/source/postcard-derive/src/schema.rs:
--------------------------------------------------------------------------------
1 | use proc_macro2::{Span, TokenStream};
2 | use quote::{quote, quote_spanned};
3 | use syn::{
4 | parse_quote, punctuated::Punctuated, spanned::Spanned, Data, DeriveInput, Fields, GenericParam,
5 | Generics, Path, Token,
6 | };
7 |
8 | pub fn do_derive_schema(input: DeriveInput) -> syn::Result {
9 | let span = input.span();
10 | let name = &input.ident;
11 |
12 | let mut generator = Generator::new(&input)?;
13 |
14 | // Add a bound `T: Schema` to every type parameter T unless overridden by `#[postcard(bound = "...")]`
15 | let generics = match generator.bound.take() {
16 | Some(bounds) => {
17 | let mut generics = input.generics;
18 | generics.make_where_clause().predicates.extend(bounds);
19 | generics
20 | }
21 | None => generator.add_trait_bounds(input.generics),
22 | };
23 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
24 |
25 | let ty = generator.generate_type(&input.data, span, name.to_string())?;
26 |
27 | let postcard_schema = &generator.postcard_schema;
28 | let expanded = quote! {
29 | impl #impl_generics #postcard_schema::Schema for #name #ty_generics #where_clause {
30 | const SCHEMA: &'static #postcard_schema::schema::DataModelType = #ty;
31 | }
32 | };
33 |
34 | Ok(expanded)
35 | }
36 |
37 | struct Generator {
38 | postcard_schema: Path,
39 | bound: Option>,
40 | }
41 |
42 | impl Generator {
43 | fn new(input: &DeriveInput) -> syn::Result {
44 | let mut generator = Self {
45 | postcard_schema: parse_quote!(::postcard_schema),
46 | bound: None,
47 | };
48 | for attr in &input.attrs {
49 | if attr.path().is_ident("postcard") {
50 | attr.parse_nested_meta(|meta| {
51 | // #[postcard(crate = path::to::postcard)]
52 | if meta.path.is_ident("crate") {
53 | generator.postcard_schema = meta.value()?.parse()?;
54 | return Ok(());
55 | }
56 |
57 | // #[postcard(bound = "T: Schema")]
58 | if meta.path.is_ident("bound") {
59 | let bound = meta.value()?.parse::()?;
60 | let bound = bound.parse_with(
61 | Punctuated::::parse_terminated,
62 | )?;
63 | if generator.bound.is_some() {
64 | return Err(meta.error("duplicate #[postcard(bound = \"...\")]"));
65 | }
66 | generator.bound = Some(bound);
67 | return Ok(());
68 | }
69 |
70 | Err(meta.error("unsupported #[postcard] attribute"))
71 | })?;
72 | }
73 | }
74 | Ok(generator)
75 | }
76 |
77 | fn generate_type(
78 | &self,
79 | data: &Data,
80 | span: Span,
81 | name: String,
82 | ) -> Result {
83 | let postcard_schema = &self.postcard_schema;
84 | match data {
85 | Data::Struct(data) => {
86 | let data = self.generate_struct(&data.fields);
87 | Ok(quote! {
88 | postcard_schema::schema::DataModelType::Struct{
89 | name: #name,
90 | data: #data,
91 | }
92 | })
93 | }
94 | Data::Enum(data) => {
95 | let variants = data.variants.iter().map(|v| {
96 | let (name, data) = (v.ident.to_string(), self.generate_variants(&v.fields));
97 | quote! { #postcard_schema::schema::Variant { name: #name, data: #data } }
98 | });
99 |
100 | Ok(quote! {
101 | postcard_schema::schema::DataModelType::Enum {
102 | name: #name,
103 | variants: &[#(variants),*],
104 | }
105 | })
106 | }
107 | Data::Union(_) => Err(syn::Error::new(
108 | span,
109 | "#[derive(Schema)] does not support unions",
110 | )),
111 | }
112 | }
113 |
114 | fn generate_struct(&self, fields: &Fields) -> TokenStream {
115 | let postcard_schema = &self.postcard_schema;
116 | match fields {
117 | syn::Fields::Named(fields) => {
118 | let fields = fields.named.iter().map(|f| {
119 | let ty = &f.ty;
120 | let name = f.ident.as_ref().unwrap().to_string();
121 | quote_spanned!(f.span() => postcard_schema::schema::NamedField { name: #name, ty: <#ty as #postcard_schema::Schema>::SCHEMA })
122 | });
123 | quote! { #postcard_schema::schema::Data::Struct(&[
124 | #( #fields ),*
125 | ]) }
126 | }
127 | syn::Fields::Unnamed(fields) => {
128 | if fields.unnamed.len() == 1 {
129 | let f = fields.unnamed[0].clone();
130 | let ty = &f.ty;
131 | let qs = quote_spanned!(f.span() => <#ty as #postcard_schema::Schema>::SCHEMA);
132 |
133 | quote! { #postcard_schema::schema::Data::Newtype(#qs) }
134 | } else {
135 | let fields = fields.unnamed.iter().map(|f| {
136 | let ty = &f.ty;
137 | quote_spanned!(f.span() => <#ty as #postcard_schema::Schema>::SCHEMA)
138 | });
139 | quote! { #postcard_schema::schema::Data::Tuple(&[
140 | #( #fields ),*
141 | ]) }
142 | }
143 | }
144 | syn::Fields::Unit => {
145 | quote! { #postcard_schema::schema::Data::Unit }
146 | }
147 | }
148 | }
149 |
150 | fn generate_variants(&self, fields: &Fields) -> TokenStream {
151 | let postcard_schema = &self.postcard_schema;
152 | match fields {
153 | syn::Fields::Named(fields) => {
154 | let fields = fields.named.iter().map(|f| {
155 | let ty = &f.ty;
156 | let name = f.ident.as_ref().unwrap().to_string();
157 | quote_spanned!(f.span() => postcard_schema::schema::NamedField { name: #name, ty: <#ty as #postcard_schema::Schema>::SCHEMA })
158 | });
159 | quote! { #postcard_schema::schema::Data::Struct(&[
160 | #( #fields ),*
161 | ]) }
162 | }
163 | syn::Fields::Unnamed(fields) => {
164 | if fields.unnamed.len() == 1 {
165 | let f = fields.unnamed[0].clone();
166 | let ty = &f.ty;
167 | let qs = quote_spanned!(f.span() => <#ty as #postcard_schema::Schema>::SCHEMA);
168 |
169 | quote! { #postcard_schema::schema::Data::Newtype(#qs) }
170 | } else {
171 | let fields = fields.unnamed.iter().map(|f| {
172 | let ty = &f.ty;
173 | quote_spanned!(f.span() => <#ty as #postcard_schema::Schema>::SCHEMA)
174 | });
175 | quote! { #postcard_schema::schema::Data::Tuple(&[
176 | #( #fields ),*
177 | ]) }
178 | }
179 | }
180 | syn::Fields::Unit => {
181 | quote! { #postcard_schema::schema::Data::Unit }
182 | }
183 | }
184 | }
185 |
186 | /// Add a bound `T: Schema` to every type parameter T.
187 | fn add_trait_bounds(&self, mut generics: Generics) -> Generics {
188 | let postcard_schema = &self.postcard_schema;
189 | for param in &mut generics.params {
190 | if let GenericParam::Type(ref mut type_param) = *param {
191 | type_param
192 | .bounds
193 | .push(parse_quote!(#postcard_schema::Schema));
194 | }
195 | }
196 | generics
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/source/postcard-dyn/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/source/postcard-dyn/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "postcard-dyn"
3 | version = "0.2.0"
4 | authors = ["James Munns "]
5 | edition = "2021"
6 | repository = "https://github.com/jamesmunns/postcard"
7 | description = "Dynamic ser/de for postcard"
8 | license = "MIT OR Apache-2.0"
9 | categories = [
10 | "embedded",
11 | "no-std",
12 | ]
13 | keywords = [
14 | "serde",
15 | "cobs",
16 | "framing",
17 | ]
18 | documentation = "https://docs.rs/postcard-dyn/"
19 |
20 |
21 | [dependencies]
22 | serde = { version = "1.0.202", features = ["derive"] }
23 | serde_json = "1.0.117"
24 |
25 | [dependencies.postcard]
26 | version = "1.0.10"
27 | features = ["use-std"]
28 | path = "../postcard"
29 |
30 | [dependencies.postcard-schema]
31 | version = "0.2"
32 | features = ["use-std", "derive"]
33 | path = "../postcard-schema"
34 |
--------------------------------------------------------------------------------
/source/postcard-dyn/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod de;
2 | mod ser;
3 |
4 | pub use de::from_slice_dyn;
5 | pub use ser::to_stdvec_dyn;
6 | pub use serde_json::Value;
7 |
--------------------------------------------------------------------------------
/source/postcard-schema/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "postcard-schema"
3 | version = "0.2.1"
4 | authors = ["James Munns "]
5 | edition = "2021"
6 | readme = "README.md"
7 | repository = "https://github.com/jamesmunns/postcard"
8 | description = "Reflection-like schemas for postcard types"
9 | license = "MIT OR Apache-2.0"
10 | categories = [
11 | "embedded",
12 | "no-std",
13 | ]
14 | keywords = [
15 | "serde",
16 | ]
17 | documentation = "https://docs.rs/postcard-schema/"
18 |
19 | [package.metadata.docs.rs]
20 | all-features = true
21 | rustdoc-args = ["--cfg", "docsrs"]
22 |
23 | [dependencies]
24 |
25 | [dependencies.serde]
26 | version = "1.0"
27 | default-features = false
28 | # todo: remove this?
29 | features = ["derive"]
30 |
31 | [dependencies.uuid_v1_0]
32 | package = "uuid"
33 | version = "1.0"
34 | default-features = false
35 | optional = true
36 |
37 | [dependencies.chrono_v0_4]
38 | package = "chrono"
39 | version = "0.4"
40 | default-features = false
41 | optional = true
42 |
43 | [dependencies.heapless_v0_7]
44 | package = "heapless"
45 | version = "0.7"
46 | default-features = false
47 | optional = true
48 |
49 | [dependencies.heapless_v0_8]
50 | package = "heapless"
51 | version = "0.8"
52 | default-features = false
53 | optional = true
54 |
55 | [dependencies.nalgebra_v0_33]
56 | package = "nalgebra"
57 | version = "0.33.0"
58 | optional = true
59 | default-features = false
60 |
61 | [dependencies.postcard-derive]
62 | path = "../postcard-derive"
63 | version = "0.2.0"
64 | optional = true
65 |
66 | [dependencies.defmt_v0_3]
67 | package = "defmt"
68 | version = "0.3.5"
69 | optional = true
70 |
71 | [dev-dependencies.postcard]
72 | path = "../postcard"
73 | version = "1.0"
74 | features = ["use-std"]
75 |
76 | [dev-dependencies.nalgebra_v0_33]
77 | package = "nalgebra"
78 | version = "0.33.0"
79 | default-features = false
80 | features = ["serde-serialize-no-std"]
81 |
82 | [features]
83 | default = []
84 | use-std = ["serde/std"]
85 | alloc = ["serde/alloc"]
86 | derive = ["postcard-derive"]
87 |
88 | defmt-v0_3 = ["defmt_v0_3"]
89 | uuid-v1_0 = ["uuid_v1_0"]
90 | chrono-v0_4 = ["chrono_v0_4"]
91 | heapless-v0_7 = ["heapless_v0_7"]
92 | heapless-v0_8 = ["heapless_v0_8"]
93 | nalgebra-v0_33 = ["nalgebra_v0_33"]
94 |
--------------------------------------------------------------------------------
/source/postcard-schema/README.md:
--------------------------------------------------------------------------------
1 | # Postcard Schema
2 |
--------------------------------------------------------------------------------
/source/postcard-schema/src/impls/builtins_alloc.rs:
--------------------------------------------------------------------------------
1 | //! Implementations of the [`Schema`] trait for `alloc` types
2 |
3 | use crate::{schema::DataModelType, Schema};
4 |
5 | extern crate alloc;
6 |
7 | impl Schema for alloc::vec::Vec {
8 | const SCHEMA: &'static DataModelType = &DataModelType::Seq(T::SCHEMA);
9 | }
10 |
11 | impl Schema for alloc::string::String {
12 | const SCHEMA: &'static DataModelType = &DataModelType::String;
13 | }
14 |
15 | impl Schema for alloc::collections::BTreeMap {
16 | const SCHEMA: &'static DataModelType = &DataModelType::Map {
17 | key: K::SCHEMA,
18 | val: V::SCHEMA,
19 | };
20 | }
21 |
22 | impl Schema for alloc::collections::BTreeSet {
23 | const SCHEMA: &'static DataModelType = &DataModelType::Seq(K::SCHEMA);
24 | }
25 |
--------------------------------------------------------------------------------
/source/postcard-schema/src/impls/builtins_nostd.rs:
--------------------------------------------------------------------------------
1 | //! Implementations of the [`Schema`] trait for `core` types
2 | //!
3 | //! These implementations are always available
4 |
5 | use crate::{
6 | schema::{Data, DataModelType, NamedField, Variant},
7 | Schema,
8 | };
9 | use core::{
10 | num::{
11 | NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroU128, NonZeroU16,
12 | NonZeroU32, NonZeroU64, NonZeroU8,
13 | },
14 | ops::{Range, RangeFrom, RangeInclusive, RangeTo},
15 | };
16 |
17 | macro_rules! impl_schema {
18 | ($($t:ty: $sdm:expr),*) => {
19 | $(
20 | impl Schema for $t {
21 | const SCHEMA: &'static DataModelType = &$sdm;
22 | }
23 | )*
24 | };
25 | (tuple => [$(($($generic:ident),*)),*]) => {
26 | $(
27 | impl<$($generic: Schema),*> Schema for ($($generic,)*) {
28 | const SCHEMA: &'static DataModelType = &DataModelType::Tuple(&[$($generic::SCHEMA),*]);
29 | }
30 | )*
31 | };
32 | }
33 |
34 | impl_schema![
35 | u8: DataModelType::U8,
36 | NonZeroU8: DataModelType::U8,
37 | i8: DataModelType::I8,
38 | NonZeroI8: DataModelType::I8,
39 | bool: DataModelType::Bool,
40 | f32: DataModelType::F32,
41 | f64: DataModelType::F64,
42 | char: DataModelType::Char,
43 | str: DataModelType::String,
44 | (): DataModelType::Unit,
45 | i16: DataModelType::I16, i32: DataModelType::I32, i64: DataModelType::I64, i128: DataModelType::I128,
46 | u16: DataModelType::U16, u32: DataModelType::U32, u64: DataModelType::U64, u128: DataModelType::U128,
47 | NonZeroI16: DataModelType::I16, NonZeroI32: DataModelType::I32,
48 | NonZeroI64: DataModelType::I64, NonZeroI128: DataModelType::I128,
49 | NonZeroU16: DataModelType::U16, NonZeroU32: DataModelType::U32,
50 | NonZeroU64: DataModelType::U64, NonZeroU128: DataModelType::U128
51 | ];
52 |
53 | impl_schema!(tuple => [
54 | (A),
55 | (A, B),
56 | (A, B, C),
57 | (A, B, C, D),
58 | (A, B, C, D, E),
59 | (A, B, C, D, E, F)
60 | ]);
61 |
62 | impl Schema for Option {
63 | const SCHEMA: &'static DataModelType = &DataModelType::Option(T::SCHEMA);
64 | }
65 |
66 | impl Schema for Result {
67 | const SCHEMA: &'static DataModelType = &DataModelType::Enum {
68 | name: "Result",
69 | variants: &[
70 | &Variant {
71 | name: "Ok",
72 | data: Data::Newtype(T::SCHEMA),
73 | },
74 | &Variant {
75 | name: "Err",
76 | data: Data::Newtype(E::SCHEMA),
77 | },
78 | ],
79 | };
80 | }
81 |
82 | impl Schema for &'_ T {
83 | const SCHEMA: &'static DataModelType = T::SCHEMA;
84 | }
85 |
86 | impl Schema for [T] {
87 | const SCHEMA: &'static DataModelType = &DataModelType::Seq(T::SCHEMA);
88 | }
89 |
90 | impl Schema for [T; N] {
91 | const SCHEMA: &'static DataModelType = &DataModelType::Tuple(&[T::SCHEMA; N]);
92 | }
93 |
94 | impl Schema for Range {
95 | const SCHEMA: &'static DataModelType = &DataModelType::Struct {
96 | name: "Range",
97 | data: Data::Struct(&[
98 | &NamedField {
99 | name: "start",
100 | ty: T::SCHEMA,
101 | },
102 | &NamedField {
103 | name: "end",
104 | ty: T::SCHEMA,
105 | },
106 | ]),
107 | };
108 | }
109 |
110 | impl Schema for RangeInclusive {
111 | const SCHEMA: &'static DataModelType = &DataModelType::Struct {
112 | name: "RangeInclusive",
113 | data: Data::Struct(&[
114 | &NamedField {
115 | name: "start",
116 | ty: T::SCHEMA,
117 | },
118 | &NamedField {
119 | name: "end",
120 | ty: T::SCHEMA,
121 | },
122 | ]),
123 | };
124 | }
125 |
126 | impl Schema for RangeFrom {
127 | const SCHEMA: &'static DataModelType = &DataModelType::Struct {
128 | name: "RangeFrom",
129 | data: Data::Struct(&[&NamedField {
130 | name: "start",
131 | ty: T::SCHEMA,
132 | }]),
133 | };
134 | }
135 |
136 | impl Schema for RangeTo {
137 | const SCHEMA: &'static DataModelType = &DataModelType::Struct {
138 | name: "RangeTo",
139 | data: Data::Struct(&[&NamedField {
140 | name: "end",
141 | ty: T::SCHEMA,
142 | }]),
143 | };
144 | }
145 |
--------------------------------------------------------------------------------
/source/postcard-schema/src/impls/builtins_std.rs:
--------------------------------------------------------------------------------
1 | //! Implementations of the [`Schema`] trait for `std` types
2 |
3 | use crate::{schema::DataModelType, Schema};
4 |
5 | #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "use-std"))))]
6 | impl Schema for std::vec::Vec {
7 | const SCHEMA: &'static DataModelType = &DataModelType::Seq(T::SCHEMA);
8 | }
9 |
10 | #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "use-std"))))]
11 | impl Schema for std::string::String {
12 | const SCHEMA: &'static DataModelType = &DataModelType::String;
13 | }
14 |
15 | #[cfg_attr(docsrs, doc(cfg(feature = "use-std")))]
16 | impl Schema for std::path::PathBuf {
17 | const SCHEMA: &'static DataModelType = &DataModelType::String;
18 | }
19 |
20 | #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "use-std"))))]
21 | impl Schema for std::collections::HashMap {
22 | const SCHEMA: &'static DataModelType = &DataModelType::Map {
23 | key: K::SCHEMA,
24 | val: V::SCHEMA,
25 | };
26 | }
27 |
28 | #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "use-std"))))]
29 | impl Schema for std::collections::BTreeMap {
30 | const SCHEMA: &'static DataModelType = &DataModelType::Map {
31 | key: K::SCHEMA,
32 | val: V::SCHEMA,
33 | };
34 | }
35 |
36 | #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "use-std"))))]
37 | impl Schema for std::collections::HashSet {
38 | const SCHEMA: &'static DataModelType = &DataModelType::Seq(K::SCHEMA);
39 | }
40 |
41 | #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "use-std"))))]
42 | impl Schema for std::collections::BTreeSet {
43 | const SCHEMA: &'static DataModelType = &DataModelType::Seq(K::SCHEMA);
44 | }
45 |
--------------------------------------------------------------------------------
/source/postcard-schema/src/impls/chrono_v0_4.rs:
--------------------------------------------------------------------------------
1 | //! Implementations of the [`Schema`] trait for the `chrono` crate v0.4
2 |
3 | use crate::{schema::DataModelType, Schema};
4 |
5 | #[cfg_attr(docsrs, doc(cfg(feature = "chrono-v0_4")))]
6 | impl Schema for chrono_v0_4::DateTime {
7 | const SCHEMA: &'static DataModelType = &DataModelType::String;
8 | }
9 |
--------------------------------------------------------------------------------
/source/postcard-schema/src/impls/heapless_v0_7.rs:
--------------------------------------------------------------------------------
1 | //! Implementations of the [`Schema`] trait for the `heapless` crate v0.7
2 |
3 | use crate::{schema::DataModelType, Schema};
4 |
5 | #[cfg_attr(docsrs, doc(cfg(feature = "heapless-v0_7")))]
6 | impl Schema for heapless_v0_7::Vec {
7 | const SCHEMA: &'static DataModelType = &DataModelType::Seq(T::SCHEMA);
8 | }
9 |
10 | #[cfg_attr(docsrs, doc(cfg(feature = "heapless-v0_7")))]
11 | impl Schema for heapless_v0_7::String {
12 | const SCHEMA: &'static DataModelType = &DataModelType::String;
13 | }
14 |
--------------------------------------------------------------------------------
/source/postcard-schema/src/impls/heapless_v0_8.rs:
--------------------------------------------------------------------------------
1 | //! Implementations of the [`Schema`] trait for the `heapless` crate v0.8
2 |
3 | use crate::{schema::DataModelType, Schema};
4 |
5 | #[cfg_attr(docsrs, doc(cfg(feature = "heapless-v0_8")))]
6 | impl Schema for heapless_v0_8::Vec {
7 | const SCHEMA: &'static DataModelType = &DataModelType::Seq(T::SCHEMA);
8 | }
9 |
10 | #[cfg_attr(docsrs, doc(cfg(feature = "heapless-v0_8")))]
11 | impl Schema for heapless_v0_8::String {
12 | const SCHEMA: &'static DataModelType = &DataModelType::String;
13 | }
14 |
--------------------------------------------------------------------------------
/source/postcard-schema/src/impls/mod.rs:
--------------------------------------------------------------------------------
1 | //! Implementations of the [`Schema`] trait for foreign crates
2 | //!
3 | //! Each module requires the matching feature flag to be enabled.
4 |
5 | use crate::{schema::DataModelType, Schema};
6 |
7 | pub mod builtins_nostd;
8 |
9 | #[cfg(all(not(feature = "use-std"), feature = "alloc"))]
10 | #[cfg_attr(docsrs, doc(cfg(all(not(feature = "use-std"), feature = "alloc"))))]
11 | pub mod builtins_alloc;
12 |
13 | #[cfg(feature = "use-std")]
14 | #[cfg_attr(docsrs, doc(cfg(feature = "use-std")))]
15 | pub mod builtins_std;
16 |
17 | #[cfg(feature = "chrono-v0_4")]
18 | #[cfg_attr(docsrs, doc(cfg(feature = "chrono-v0_4")))]
19 | pub mod chrono_v0_4;
20 |
21 | #[cfg(feature = "uuid-v1_0")]
22 | #[cfg_attr(docsrs, doc(cfg(feature = "uuid-v1_0")))]
23 | pub mod uuid_v1_0;
24 |
25 | #[cfg(feature = "heapless-v0_7")]
26 | #[cfg_attr(docsrs, doc(cfg(feature = "heapless-v0_7")))]
27 | pub mod heapless_v0_7;
28 |
29 | #[cfg(feature = "heapless-v0_8")]
30 | #[cfg_attr(docsrs, doc(cfg(feature = "heapless-v0_8")))]
31 | pub mod heapless_v0_8;
32 |
33 | #[cfg(feature = "nalgebra-v0_33")]
34 | #[cfg_attr(docsrs, doc(cfg(feature = "nalgebra-v0_33")))]
35 | pub mod nalgebra_v0_33;
36 |
37 | impl Schema for DataModelType {
38 | const SCHEMA: &'static DataModelType = &DataModelType::Schema;
39 | }
40 |
--------------------------------------------------------------------------------
/source/postcard-schema/src/impls/nalgebra_v0_33.rs:
--------------------------------------------------------------------------------
1 | //! Implementations of the [`Schema`] trait for the `nalgebra` crate v0.33
2 |
3 | use crate::{schema::DataModelType, Schema};
4 |
5 | #[cfg_attr(docsrs, doc(cfg(feature = "nalgebra-v0_33")))]
6 | impl Schema
7 | for nalgebra_v0_33::Matrix<
8 | T,
9 | nalgebra_v0_33::Const,
10 | nalgebra_v0_33::Const,
11 | nalgebra_v0_33::ArrayStorage,
12 | >
13 | where
14 | T: Schema + nalgebra_v0_33::Scalar,
15 | {
16 | const SCHEMA: &'static DataModelType = &DataModelType::Tuple(flatten(&[[T::SCHEMA; R]; C]));
17 | }
18 |
19 | /// Const version of the const-unstable [`<[[T; N]]>::as_flattened()`]
20 | const fn flatten(slice: &[[T; N]]) -> &[T] {
21 | const {
22 | assert!(size_of::() != 0);
23 | }
24 | // SAFETY: `self.len() * N` cannot overflow because `self` is
25 | // already in the address space.
26 | let len = unsafe { slice.len().unchecked_mul(N) };
27 | // SAFETY: `[T]` is layout-identical to `[T; N]`
28 | unsafe { core::slice::from_raw_parts(slice.as_ptr().cast(), len) }
29 | }
30 |
31 | #[test]
32 | fn flattened() {
33 | type T = nalgebra_v0_33::SMatrix;
34 | assert_eq!(T::SCHEMA, <[u8; 9]>::SCHEMA);
35 | }
36 |
37 | #[test]
38 | fn smoke() {
39 | let x = nalgebra_v0_33::SMatrix::::new(1, 2, 3, 4, 5, 6, 7, 8, 9);
40 | let y = postcard::to_stdvec(&x).unwrap();
41 | assert_eq!(&[1, 4, 7, 2, 5, 8, 3, 6, 9], y.as_slice());
42 | }
43 |
--------------------------------------------------------------------------------
/source/postcard-schema/src/impls/uuid_v1_0.rs:
--------------------------------------------------------------------------------
1 | //! Implementations of the [`Schema`] trait for the `uuid` crate v1.0
2 |
3 | use crate::{schema::DataModelType, Schema};
4 |
5 | impl Schema for uuid_v1_0::Uuid {
6 | const SCHEMA: &'static DataModelType = &DataModelType::ByteArray;
7 | }
8 |
--------------------------------------------------------------------------------
/source/postcard-schema/src/key/mod.rs:
--------------------------------------------------------------------------------
1 | //! A hash-based "Key" tag for postcard-schema compatible types
2 | //!
3 | //! This originally was developed for use in postcard-rpc, but has more
4 | //! general use as a general purpose type + usage tag.
5 | //!
6 | //! This key should NOT be relied on for memory safety purposes, validation
7 | //! of the data should still be performed, like that done by serde. It should
8 | //! be treated as misuse **resistant**, not misuse **proof**. It is possible
9 | //! for there to be hash collisions, and as a non-cryptograpic hash, it is
10 | //! likely trivial to intentionally cause a collision.
11 |
12 | use serde::{Deserialize, Serialize};
13 |
14 | use crate::{schema::DataModelType, Schema};
15 |
16 | pub mod hash;
17 |
18 | // TODO: https://github.com/knurling-rs/defmt/issues/928
19 | #[cfg(feature = "defmt-v0_3")]
20 | use defmt_v0_3 as defmt;
21 |
22 | /// The `Key` uniquely identifies what "kind" of message this is.
23 | ///
24 | /// In order to generate it, `postcard-schema` takes two pieces of data:
25 | ///
26 | /// * a `&str` "path" URI, similar to how you would use URIs as part of an HTTP path
27 | /// * The schema of the message type itself, using the [`Schema`] trait
28 | ///
29 | /// [`Schema`]: crate::Schema
30 | ///
31 | /// Specifically, we use [`Fnv1a`](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function),
32 | /// and produce a 64-bit digest, by first hashing the path, then hashing the
33 | /// schema. Fnv1a is a non-cryptographic hash function, designed to be reasonably
34 | /// efficient to compute even on small platforms like microcontrollers.
35 | ///
36 | /// Changing **anything** about *either* of the path or the schema will produce
37 | /// a drastically different `Key` value.
38 | #[cfg_attr(feature = "defmt-v0_3", derive(defmt_v0_3::Format))]
39 | #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Serialize, Deserialize, Hash)]
40 | pub struct Key([u8; 8]);
41 |
42 | impl Schema for Key {
43 | const SCHEMA: &'static crate::schema::DataModelType = &DataModelType::Struct {
44 | name: "Key",
45 | data: crate::schema::Data::Newtype(<[u8; 8] as Schema>::SCHEMA),
46 | };
47 | }
48 |
49 | impl core::fmt::Debug for Key {
50 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
51 | f.write_str("Key(")?;
52 | for b in self.0.iter() {
53 | f.write_fmt(format_args!("{} ", b))?;
54 | }
55 | f.write_str(")")
56 | }
57 | }
58 |
59 | impl Key {
60 | /// Create a Key for the given type and path
61 | pub const fn for_path(path: &str) -> Self
62 | where
63 | T: Schema + ?Sized,
64 | {
65 | Key(hash::fnv1a64::hash_ty_path::(path))
66 | }
67 |
68 | /// Unsafely create a key from a given 8-byte value
69 | ///
70 | /// ## Safety
71 | ///
72 | /// This MUST only be used with pre-calculated values. Incorrectly
73 | /// created keys could lead to the improper deserialization of
74 | /// messages.
75 | pub const unsafe fn from_bytes(bytes: [u8; 8]) -> Self {
76 | Self(bytes)
77 | }
78 |
79 | /// Extract the bytes making up this key
80 | pub const fn to_bytes(&self) -> [u8; 8] {
81 | self.0
82 | }
83 |
84 | /// Compare 2 keys in const context.
85 | pub const fn const_cmp(&self, other: &Self) -> bool {
86 | let mut i = 0;
87 | while i < self.0.len() {
88 | if self.0[i] != other.0[i] {
89 | return false;
90 | }
91 |
92 | i += 1;
93 | }
94 |
95 | true
96 | }
97 | }
98 |
99 | #[cfg(feature = "use-std")]
100 | mod key_owned {
101 | use super::*;
102 | use crate::schema::owned::OwnedDataModelType;
103 | impl Key {
104 | /// Calculate the Key for the given path and [`OwnedDataModelType`]
105 | pub fn for_owned_schema_path(path: &str, nt: &OwnedDataModelType) -> Key {
106 | Key(hash::fnv1a64_owned::hash_ty_path_owned(path, nt))
107 | }
108 | }
109 | }
110 |
111 | #[cfg(test)]
112 | mod test {
113 | use crate::{key::Key, schema::DataModelType, Schema};
114 |
115 | #[test]
116 | fn matches_old_postcard_rpc_defn() {
117 | let old = &DataModelType::Struct {
118 | name: "Key",
119 | data: crate::schema::Data::Newtype(&DataModelType::Tuple(&[
120 | &DataModelType::U8,
121 | &DataModelType::U8,
122 | &DataModelType::U8,
123 | &DataModelType::U8,
124 | &DataModelType::U8,
125 | &DataModelType::U8,
126 | &DataModelType::U8,
127 | &DataModelType::U8,
128 | ])),
129 | };
130 |
131 | let new = ::SCHEMA;
132 |
133 | assert_eq!(old, new);
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/source/postcard-schema/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![cfg_attr(not(any(test, feature = "use-std")), no_std)]
2 | #![warn(missing_docs)]
3 | #![cfg_attr(docsrs, feature(doc_cfg))]
4 | //! # Postcard Schema
5 |
6 | pub mod impls;
7 | pub mod key;
8 | pub mod schema;
9 |
10 | /// Derive [`Schema`] for a struct or enum
11 | ///
12 | /// # Examples
13 | ///
14 | /// ```
15 | /// use postcard_schema::Schema;
16 | ///
17 | /// #[derive(Schema)]
18 | /// struct Point {
19 | /// x: i32,
20 | /// y: i32,
21 | /// }
22 | /// ```
23 | ///
24 | /// # Attributes
25 | ///
26 | /// ## `#[postcard(crate = ...)]`
27 | ///
28 | /// The `#[postcard(crate = ...)]` attribute can be used to specify a path to the `postcard_schema`
29 | /// crate instance to use when referring to [`Schema`] and [schema types](schema) from generated
30 | /// code. This is normally only applicable when invoking re-exported derives from a different crate.
31 | ///
32 | /// ```
33 | /// # use postcard_schema::Schema;
34 | /// use postcard_schema as reexported_postcard_schema;
35 | ///
36 | /// #[derive(Schema)]
37 | /// #[postcard(crate = reexported_postcard_schema)]
38 | /// struct Point {
39 | /// x: i32,
40 | /// y: i32,
41 | /// }
42 | /// ```
43 | ///
44 | /// ## `#[postcard(bound = ...)]`
45 | ///
46 | /// The `#[postcard(bound = ...)]` attribute can be used to overwrite the default bounds when
47 | /// deriving [`Schema`]. The default bounds are `T: Schema` for each type parameter `T`.
48 | ///
49 | /// ```
50 | /// # use postcard_schema::Schema;
51 | /// #[derive(Schema)]
52 | /// #[postcard(bound = "")]
53 | /// struct Foo(F::Wrap);
54 | ///
55 | /// trait Bar {
56 | /// type Wrap: Schema;
57 | /// }
58 | ///
59 | /// struct NoSchema;
60 | /// impl Bar for NoSchema {
61 | /// type Wrap = Option;
62 | /// }
63 | ///
64 | /// Foo::::SCHEMA;
65 | /// ```
66 | #[cfg(feature = "derive")]
67 | pub use postcard_derive::Schema;
68 |
69 | /// A trait that represents a compile time calculated schema
70 | pub trait Schema {
71 | /// A recursive data structure that describes the schema of the given
72 | /// type.
73 | const SCHEMA: &'static schema::DataModelType;
74 | }
75 |
76 | #[cfg(test)]
77 | mod tests {
78 | use super::*;
79 |
80 | #[test]
81 | fn crate_path() {
82 | #[allow(unused)]
83 | #[derive(Schema)]
84 | #[postcard(crate = crate)]
85 | struct Point {
86 | x: i32,
87 | y: i32,
88 | }
89 |
90 | assert_eq!(
91 | Point::SCHEMA,
92 | &schema::DataModelType::Struct {
93 | name: "Point",
94 | data: schema::Data::Struct(&[
95 | &schema::NamedField {
96 | name: "x",
97 | ty: i32::SCHEMA
98 | },
99 | &schema::NamedField {
100 | name: "y",
101 | ty: i32::SCHEMA
102 | },
103 | ])
104 | }
105 | );
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/source/postcard-schema/src/schema/fmt.rs:
--------------------------------------------------------------------------------
1 | //! Formatting helper functionality
2 | //!
3 | //! This module provides ways of turning Data Model information into a human
4 | //! readable output
5 |
6 | #[cfg(all(not(feature = "use-std"), feature = "alloc"))]
7 | extern crate alloc;
8 |
9 | use super::owned::{OwnedData, OwnedDataModelType};
10 |
11 | #[cfg(feature = "use-std")]
12 | use std::{string::String, vec::Vec};
13 |
14 | #[cfg(all(not(feature = "use-std"), feature = "alloc"))]
15 | use alloc::{format, string::String, vec::Vec};
16 |
17 | /// Is this [`OwnedDataModelType`] a primitive?
18 | pub fn is_prim(osdmty: &OwnedDataModelType) -> bool {
19 | match osdmty {
20 | OwnedDataModelType::Bool => true,
21 | OwnedDataModelType::I8 => true,
22 | OwnedDataModelType::U8 => true,
23 | OwnedDataModelType::I16 => true,
24 | OwnedDataModelType::I32 => true,
25 | OwnedDataModelType::I64 => true,
26 | OwnedDataModelType::I128 => true,
27 | OwnedDataModelType::U16 => true,
28 | OwnedDataModelType::U32 => true,
29 | OwnedDataModelType::U64 => true,
30 | OwnedDataModelType::U128 => true,
31 | OwnedDataModelType::Usize => true,
32 | OwnedDataModelType::Isize => true,
33 | OwnedDataModelType::F32 => true,
34 | OwnedDataModelType::F64 => true,
35 | OwnedDataModelType::Char => true,
36 | OwnedDataModelType::String => true,
37 | OwnedDataModelType::ByteArray => true,
38 | OwnedDataModelType::Option(ty) => is_prim(ty),
39 | OwnedDataModelType::Unit => true,
40 | OwnedDataModelType::Seq(_) => false,
41 | OwnedDataModelType::Tuple(_) => false,
42 | OwnedDataModelType::Map { key, val } => is_prim(key) && is_prim(val),
43 | OwnedDataModelType::Struct { .. } => false,
44 | OwnedDataModelType::Enum { .. } => false,
45 | OwnedDataModelType::Schema => true,
46 | }
47 | }
48 |
49 | /// Format an [`OwnedDataModelType`] to the given string.
50 | ///
51 | /// Use `top_level = true` when this is a standalone type, and `top_level = false`
52 | /// when this type is contained within another type
53 | pub fn fmt_owned_dmt_to_buf(dmt: &OwnedDataModelType, buf: &mut String, top_level: bool) {
54 | let fmt_data = |data: &OwnedData, buf: &mut String| match data {
55 | OwnedData::Unit => {}
56 | OwnedData::Newtype(inner) => {
57 | *buf += "(";
58 | fmt_owned_dmt_to_buf(inner, buf, false);
59 | *buf += ")";
60 | }
61 | OwnedData::Tuple(fields) => {
62 | *buf += "(";
63 | let mut fields = fields.iter();
64 | if let Some(first) = fields.next() {
65 | fmt_owned_dmt_to_buf(first, buf, false);
66 | }
67 | for field in fields {
68 | *buf += ", ";
69 | fmt_owned_dmt_to_buf(field, buf, false);
70 | }
71 | *buf += ")";
72 | }
73 | OwnedData::Struct(fields) => {
74 | *buf += " { ";
75 | let mut fields = fields.iter();
76 | if let Some(first) = fields.next() {
77 | *buf += &first.name;
78 | *buf += ": ";
79 | fmt_owned_dmt_to_buf(&first.ty, buf, false);
80 | }
81 | for field in fields {
82 | *buf += ", ";
83 | *buf += &field.name;
84 | *buf += ": ";
85 | fmt_owned_dmt_to_buf(&field.ty, buf, false);
86 | }
87 | *buf += " }";
88 | }
89 | };
90 |
91 | match dmt {
92 | OwnedDataModelType::Bool => *buf += "bool",
93 | OwnedDataModelType::I8 => *buf += "i8",
94 | OwnedDataModelType::U8 => *buf += "u8",
95 | OwnedDataModelType::I16 => *buf += "i16",
96 | OwnedDataModelType::I32 => *buf += "i32",
97 | OwnedDataModelType::I64 => *buf += "i64",
98 | OwnedDataModelType::I128 => *buf += "i128",
99 | OwnedDataModelType::U16 => *buf += "u16",
100 | OwnedDataModelType::U32 => *buf += "u32",
101 | OwnedDataModelType::U64 => *buf += "u64",
102 | OwnedDataModelType::U128 => *buf += "u128",
103 | OwnedDataModelType::Usize => *buf += "usize",
104 | OwnedDataModelType::Isize => *buf += "isize",
105 | OwnedDataModelType::F32 => *buf += "f32",
106 | OwnedDataModelType::F64 => *buf += "f64",
107 | OwnedDataModelType::Char => *buf += "char",
108 | OwnedDataModelType::String => *buf += "String",
109 | OwnedDataModelType::ByteArray => *buf += "[u8]",
110 | OwnedDataModelType::Option(ty) => {
111 | *buf += "Option<";
112 | fmt_owned_dmt_to_buf(ty, buf, false);
113 | *buf += ">";
114 | }
115 | OwnedDataModelType::Unit => *buf += "()",
116 | OwnedDataModelType::Seq(ty) => {
117 | *buf += "[";
118 | fmt_owned_dmt_to_buf(ty, buf, false);
119 | *buf += "]";
120 | }
121 | OwnedDataModelType::Tuple(vec) => {
122 | if !vec.is_empty() {
123 | let first = &vec[0];
124 | if vec.iter().all(|v| first == v) {
125 | // This is a fixed size array
126 | *buf += "[";
127 | fmt_owned_dmt_to_buf(first, buf, false);
128 | *buf += "; ";
129 | *buf += &format!("{}", vec.len());
130 | *buf += "]";
131 | } else {
132 | *buf += "(";
133 | let fields = vec
134 | .iter()
135 | .map(|v| {
136 | let mut buf = String::new();
137 | fmt_owned_dmt_to_buf(v, &mut buf, false);
138 | buf
139 | })
140 | .collect::>()
141 | .join(", ");
142 | *buf += &fields;
143 | *buf += ")";
144 | }
145 | } else {
146 | *buf += "()";
147 | }
148 | }
149 | OwnedDataModelType::Map { key, val } => {
150 | *buf += "Map<";
151 | fmt_owned_dmt_to_buf(key, buf, false);
152 | *buf += ", ";
153 | fmt_owned_dmt_to_buf(val, buf, false);
154 | *buf += ">";
155 | }
156 | OwnedDataModelType::Struct { name, data } => {
157 | if top_level {
158 | *buf += "struct ";
159 | *buf += name;
160 | fmt_data(data, buf);
161 | } else {
162 | *buf += name;
163 | }
164 | }
165 | OwnedDataModelType::Enum { name, variants } => {
166 | if top_level {
167 | *buf += "enum ";
168 | *buf += name;
169 | *buf += " { ";
170 |
171 | let fields = variants
172 | .iter()
173 | .map(|v| {
174 | let mut buf = String::new();
175 | buf += &v.name;
176 | fmt_data(&v.data, &mut buf);
177 | buf
178 | })
179 | .collect::>()
180 | .join(", ");
181 | *buf += &fields;
182 | *buf += " }";
183 | } else {
184 | *buf += name;
185 | }
186 | }
187 | OwnedDataModelType::Schema => *buf += "Schema",
188 | }
189 | }
190 |
191 | /// Collect unique types mentioned by this [`OwnedDataModelType`]
192 | #[cfg(feature = "use-std")]
193 | pub fn discover_tys(
194 | ty: &OwnedDataModelType,
195 | set: &mut std::collections::HashSet,
196 | ) {
197 | let discover_tys_data = |data: &OwnedData, set: &mut _| match data {
198 | OwnedData::Unit => {}
199 | OwnedData::Newtype(inner) => discover_tys(inner, set),
200 | OwnedData::Tuple(elements) => {
201 | for element in elements {
202 | discover_tys(element, set)
203 | }
204 | }
205 | OwnedData::Struct(fields) => {
206 | for field in fields {
207 | discover_tys(&field.ty, set)
208 | }
209 | }
210 | };
211 |
212 | set.insert(ty.clone());
213 | match ty {
214 | OwnedDataModelType::Bool => {}
215 | OwnedDataModelType::I8 => {}
216 | OwnedDataModelType::U8 => {}
217 | OwnedDataModelType::I16 => {}
218 | OwnedDataModelType::I32 => {}
219 | OwnedDataModelType::I64 => {}
220 | OwnedDataModelType::I128 => {}
221 | OwnedDataModelType::U16 => {}
222 | OwnedDataModelType::U32 => {}
223 | OwnedDataModelType::U64 => {}
224 | OwnedDataModelType::U128 => {}
225 |
226 | // TODO: usize and isize don't impl Schema, which, fair.
227 | OwnedDataModelType::Usize => unreachable!(),
228 | OwnedDataModelType::Isize => unreachable!(),
229 | //
230 | OwnedDataModelType::F32 => {}
231 | OwnedDataModelType::F64 => {}
232 | OwnedDataModelType::Char => {}
233 | OwnedDataModelType::String => {}
234 | OwnedDataModelType::ByteArray => {}
235 | OwnedDataModelType::Option(inner) => {
236 | discover_tys(inner, set);
237 | }
238 | OwnedDataModelType::Unit => {}
239 | OwnedDataModelType::Seq(elements) => {
240 | discover_tys(elements, set);
241 | }
242 | OwnedDataModelType::Tuple(vec) => {
243 | for v in vec.iter() {
244 | discover_tys(v, set);
245 | }
246 | }
247 | OwnedDataModelType::Map { key, val } => {
248 | discover_tys(key, set);
249 | discover_tys(val, set);
250 | }
251 | OwnedDataModelType::Struct { name: _, data } => {
252 | discover_tys_data(data, set);
253 | }
254 | OwnedDataModelType::Enum { name: _, variants } => {
255 | for variant in variants {
256 | discover_tys_data(&variant.data, set);
257 | }
258 | }
259 | OwnedDataModelType::Schema => todo!(),
260 | };
261 | }
262 |
--------------------------------------------------------------------------------
/source/postcard-schema/src/schema/mod.rs:
--------------------------------------------------------------------------------
1 | //! ## Schema types
2 | //!
3 | //! The types in this module are used to define the schema of a given data type.
4 | //!
5 | //! The **Postcard Data Model** is nearly identical to the **Serde Data Model**, however Postcard also
6 | //! allows for one additional type, `Schema`, which maps to the [`DataModelType`] type, allowing
7 | //! the schema of types to also be sent over the wire and implement the `Schema` trait.
8 | //!
9 | //! ## Borrowed vs Owned
10 | //!
11 | //! For reasons that have to do with allowing for arbitrarily sized and nested schemas that
12 | //! can be created at compile/const time, as well as being usable in `no-std` contexts, the
13 | //! schema types in this module are implemented using a LOT of `&'static` references.
14 | //!
15 | //! This is useful in those limited contexts, however it makes it difficult to do things
16 | //! like deserialize them, as you can't generally get static references at runtime without
17 | //! a lot of leaking.
18 | //!
19 | //! For cases like this, the [`owned`] module exists, which has copies of all of the "borrowed"
20 | //! versions of the Data Model types. These owned types implement `From` for their borrowed
21 | //! counterpoint, so if you need to deserialize something, you probably want the Owned variant!
22 |
23 | #[cfg(any(feature = "use-std", feature = "alloc"))]
24 | pub mod owned;
25 |
26 | #[cfg(any(feature = "use-std", feature = "alloc"))]
27 | pub mod fmt;
28 |
29 | use serde::Serialize;
30 |
31 | /// This enum lists which of the Data Model Types apply to a given type. This describes how the
32 | /// type is encoded on the wire.
33 | ///
34 | /// This enum contains all Serde Data Model types as well as a "Schema" Type,
35 | /// which corresponds to [`DataModelType`] itself.
36 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
37 | pub enum DataModelType {
38 | /// The `bool` Serde Data Model Type
39 | Bool,
40 |
41 | /// The `i8` Serde Data Model Type
42 | I8,
43 |
44 | /// The `u8` Serde Data Model Type
45 | U8,
46 |
47 | /// A variably encoded i16
48 | I16,
49 |
50 | /// A variably encoded i32
51 | I32,
52 |
53 | /// A variably encoded i64
54 | I64,
55 |
56 | /// A variably encoded i128
57 | I128,
58 |
59 | /// A variably encoded u16
60 | U16,
61 |
62 | /// A variably encoded u32
63 | U32,
64 |
65 | /// A variably encoded u64
66 | U64,
67 |
68 | /// A variably encoded u128
69 | U128,
70 |
71 | /// A variably encoded usize
72 | Usize,
73 |
74 | /// A variably encoded isize
75 | Isize,
76 |
77 | /// The `f32` Serde Data Model Type
78 | F32,
79 |
80 | /// The `f64` Serde Data Model Type
81 | F64,
82 |
83 | /// The `char` Serde Data Model Type
84 | Char,
85 |
86 | /// The `String` Serde Data Model Type
87 | String,
88 |
89 | /// The `&[u8]` Serde Data Model Type
90 | ByteArray,
91 |
92 | /// The `Option` Serde Data Model Type
93 | Option(&'static Self),
94 |
95 | /// The `()` Serde Data Model Type
96 | Unit,
97 |
98 | /// The "Sequence" Serde Data Model Type
99 | Seq(&'static Self),
100 |
101 | /// The "Tuple" Serde Data Model Type
102 | Tuple(&'static [&'static Self]),
103 |
104 | /// The "Map" Serde Data Model Type
105 | Map {
106 | /// The map "Key" type
107 | key: &'static Self,
108 | /// The map "Value" type
109 | val: &'static Self,
110 | },
111 |
112 | /// One of the struct Serde Data Model types
113 | Struct {
114 | /// The name of this struct
115 | name: &'static str,
116 | /// The data contained in this struct
117 | data: Data,
118 | },
119 |
120 | /// The "Enum" Serde Data Model Type (which contains any of the "Variant" types)
121 | Enum {
122 | /// The name of this struct
123 | name: &'static str,
124 | /// The variants contained in this enum
125 | variants: &'static [&'static Variant],
126 | },
127 |
128 | /// A [`DataModelType`]/[`OwnedDataModelType`](owned::OwnedDataModelType)
129 | Schema,
130 | }
131 |
132 | /// The contents of a struct or enum variant.
133 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
134 | pub enum Data {
135 | /// The "Unit Struct" or "Unit Variant" Serde Data Model Type
136 | Unit,
137 |
138 | /// The "Newtype Struct" or "Newtype Variant" Serde Data Model Type
139 | Newtype(&'static DataModelType),
140 |
141 | /// The "Tuple Struct" or "Tuple Variant" Serde Data Model Type
142 | Tuple(&'static [&'static DataModelType]),
143 |
144 | /// The "Struct" or "Struct Variant" Serde Data Model Type
145 | Struct(&'static [&'static NamedField]),
146 | }
147 |
148 | /// This represents a named struct field.
149 | ///
150 | /// For example, in `struct Ex { a: u32 }` the field `a` would be reflected as
151 | /// `NamedField { name: "a", ty: DataModelType::U32 }`.
152 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
153 | pub struct NamedField {
154 | /// The name of this field
155 | pub name: &'static str,
156 | /// The type of this field
157 | pub ty: &'static DataModelType,
158 | }
159 |
160 | /// An enum variant e.g. `T::Bar(...)`
161 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
162 | pub struct Variant {
163 | /// The name of this variant
164 | pub name: &'static str,
165 | /// The data contained in this variant
166 | pub data: Data,
167 | }
168 |
--------------------------------------------------------------------------------
/source/postcard-schema/src/schema/owned.rs:
--------------------------------------------------------------------------------
1 | //! Owned Schema version
2 |
3 | use super::{Data, DataModelType, NamedField, Variant};
4 | use serde::{Deserialize, Serialize};
5 |
6 | #[cfg(all(not(feature = "use-std"), feature = "alloc"))]
7 | extern crate alloc;
8 |
9 | #[cfg(feature = "use-std")]
10 | use std::{boxed::Box, collections::HashSet, string::String};
11 |
12 | #[cfg(all(not(feature = "use-std"), feature = "alloc"))]
13 | use alloc::{boxed::Box, string::String};
14 |
15 | // ---
16 |
17 | impl OwnedDataModelType {
18 | /// Convert an `[OwnedDataModelType]` to a pseudo-Rust type format
19 | pub fn to_pseudocode(&self) -> String {
20 | let mut buf = String::new();
21 | super::fmt::fmt_owned_dmt_to_buf(self, &mut buf, true);
22 | buf
23 | }
24 |
25 | /// Collect all types used recursively by this type
26 | #[cfg(feature = "use-std")]
27 | pub fn all_used_types(&self) -> HashSet {
28 | let mut buf = HashSet::new();
29 | super::fmt::discover_tys(self, &mut buf);
30 | buf
31 | }
32 | }
33 |
34 | impl core::fmt::Display for OwnedDataModelType {
35 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
36 | let pc = self.to_pseudocode();
37 | f.write_str(&pc)
38 | }
39 | }
40 |
41 | impl crate::Schema for OwnedDataModelType {
42 | const SCHEMA: &'static DataModelType = &DataModelType::Schema;
43 | }
44 |
45 | // ---
46 |
47 | /// The owned version of [`DataModelType`]
48 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
49 | pub enum OwnedDataModelType {
50 | /// The `bool` Serde Data Model Type
51 | Bool,
52 |
53 | /// The `i8` Serde Data Model Type
54 | I8,
55 |
56 | /// The `u8` Serde Data Model Type
57 | U8,
58 |
59 | /// A variably encoded i16
60 | I16,
61 |
62 | /// A variably encoded i32
63 | I32,
64 |
65 | /// A variably encoded i64
66 | I64,
67 |
68 | /// A variably encoded i128
69 | I128,
70 |
71 | /// A variably encoded u16
72 | U16,
73 |
74 | /// A variably encoded u32
75 | U32,
76 |
77 | /// A variably encoded u64
78 | U64,
79 |
80 | /// A variably encoded u128
81 | U128,
82 |
83 | /// A variably encoded usize
84 | Usize,
85 |
86 | /// A variably encoded isize
87 | Isize,
88 |
89 | /// The `f32` Serde Data Model Type
90 | F32,
91 |
92 | /// The `f64 Serde Data Model Type
93 | F64,
94 |
95 | /// The `char` Serde Data Model Type
96 | Char,
97 |
98 | /// The `String` Serde Data Model Type
99 | String,
100 |
101 | /// The `&[u8]` Serde Data Model Type
102 | ByteArray,
103 |
104 | /// The `Option` Serde Data Model Type
105 | Option(Box),
106 |
107 | /// The `()` Serde Data Model Type
108 | Unit,
109 |
110 | /// The "Sequence" Serde Data Model Type
111 | Seq(Box),
112 |
113 | /// The "Tuple" Serde Data Model Type
114 | Tuple(Box<[Self]>),
115 |
116 | /// The "Map" Serde Data Model Type
117 | Map {
118 | /// The map "Key" type
119 | key: Box,
120 | /// The map "Value" type
121 | val: Box,
122 | },
123 |
124 | /// One of the struct Serde Data Model types
125 | Struct {
126 | /// The name of this struct
127 | name: Box,
128 | /// The data contained in this struct
129 | data: OwnedData,
130 | },
131 |
132 | /// The "Enum" Serde Data Model Type (which contains any of the "Variant" types)
133 | Enum {
134 | /// The name of this struct
135 | name: Box,
136 | /// The variants contained in this enum
137 | variants: Box<[OwnedVariant]>,
138 | },
139 |
140 | /// A [`DataModelType`]/[`OwnedDataModelType`]
141 | Schema,
142 | }
143 |
144 | impl From<&DataModelType> for OwnedDataModelType {
145 | fn from(other: &DataModelType) -> Self {
146 | match other {
147 | DataModelType::Bool => Self::Bool,
148 | DataModelType::I8 => Self::I8,
149 | DataModelType::U8 => Self::U8,
150 | DataModelType::I16 => Self::I16,
151 | DataModelType::I32 => Self::I32,
152 | DataModelType::I64 => Self::I64,
153 | DataModelType::I128 => Self::I128,
154 | DataModelType::U16 => Self::U16,
155 | DataModelType::U32 => Self::U32,
156 | DataModelType::U64 => Self::U64,
157 | DataModelType::U128 => Self::U128,
158 | DataModelType::Usize => Self::Usize,
159 | DataModelType::Isize => Self::Isize,
160 | DataModelType::F32 => Self::F32,
161 | DataModelType::F64 => Self::F64,
162 | DataModelType::Char => Self::Char,
163 | DataModelType::String => Self::String,
164 | DataModelType::ByteArray => Self::ByteArray,
165 | DataModelType::Option(o) => Self::Option(Box::new((*o).into())),
166 | DataModelType::Unit => Self::Unit,
167 | DataModelType::Seq(s) => Self::Seq(Box::new((*s).into())),
168 | DataModelType::Tuple(t) => Self::Tuple(t.iter().map(|i| (*i).into()).collect()),
169 | DataModelType::Map { key, val } => Self::Map {
170 | key: Box::new((*key).into()),
171 | val: Box::new((*val).into()),
172 | },
173 | DataModelType::Struct { name, data } => Self::Struct {
174 | name: (*name).into(),
175 | data: data.into(),
176 | },
177 | DataModelType::Enum { name, variants } => Self::Enum {
178 | name: (*name).into(),
179 | variants: variants.iter().map(|i| (*i).into()).collect(),
180 | },
181 | DataModelType::Schema => Self::Schema,
182 | }
183 | }
184 | }
185 |
186 | // ---
187 |
188 | /// The owned version of [`Data`].
189 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
190 | pub enum OwnedData {
191 | /// The "Unit Struct" or "Unit Variant" Serde Data Model Type
192 | Unit,
193 |
194 | /// The "Newtype Struct" or "Newtype Variant" Serde Data Model Type
195 | Newtype(Box),
196 |
197 | /// The "Tuple Struct" or "Tuple Variant" Serde Data Model Type
198 | Tuple(Box<[OwnedDataModelType]>),
199 |
200 | /// The "Struct" or "Struct Variant" Serde Data Model Type
201 | Struct(Box<[OwnedNamedField]>),
202 | }
203 |
204 | impl From<&Data> for OwnedData {
205 | fn from(data: &Data) -> Self {
206 | match data {
207 | Data::Unit => Self::Unit,
208 | Data::Newtype(d) => Self::Newtype(Box::new((*d).into())),
209 | Data::Tuple(d) => Self::Tuple(d.iter().map(|i| (*i).into()).collect()),
210 | Data::Struct(d) => Self::Struct(d.iter().map(|i| (*i).into()).collect()),
211 | }
212 | }
213 | }
214 |
215 | // ---
216 |
217 | /// The owned version of [`NamedField`]
218 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
219 | pub struct OwnedNamedField {
220 | /// The name of this value
221 | pub name: Box,
222 | /// The type of this value
223 | pub ty: OwnedDataModelType,
224 | }
225 |
226 | impl From<&NamedField> for OwnedNamedField {
227 | fn from(value: &NamedField) -> Self {
228 | Self {
229 | name: value.name.into(),
230 | ty: value.ty.into(),
231 | }
232 | }
233 | }
234 |
235 | // ---
236 |
237 | /// The owned version of [`Variant`]
238 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
239 | pub struct OwnedVariant {
240 | /// The name of this variant
241 | pub name: Box,
242 | /// The data contained in this variant
243 | pub data: OwnedData,
244 | }
245 |
246 | impl From<&Variant> for OwnedVariant {
247 | fn from(value: &Variant) -> Self {
248 | Self {
249 | name: value.name.into(),
250 | data: (&value.data).into(),
251 | }
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/source/postcard-schema/tests/schema.rs:
--------------------------------------------------------------------------------
1 | use postcard_schema::{
2 | schema::{owned::OwnedDataModelType, Data, DataModelType, NamedField, Variant},
3 | Schema,
4 | };
5 | use std::path::PathBuf;
6 |
7 | #[allow(unused)]
8 | #[derive(Schema)]
9 | enum Inner {
10 | Alpha,
11 | Beta,
12 | Gamma,
13 | Delta(i32, i16),
14 | Epsilon { zeta: f32, eta: bool },
15 | }
16 |
17 | #[allow(unused)]
18 | #[derive(Schema)]
19 | struct Outer<'a> {
20 | a: u32,
21 | b: u64,
22 | c: u8,
23 | d: Inner,
24 | e: [u8; 10],
25 | f: &'a [u8],
26 | }
27 |
28 | #[allow(unused)]
29 | #[derive(Schema)]
30 | struct Slice<'a> {
31 | x: &'a [u8],
32 | }
33 |
34 | #[allow(unused)]
35 | #[derive(Schema)]
36 | #[postcard(bound = "")] // doesn't compile without this
37 | struct Bound {
38 | x: F::Out,
39 | }
40 |
41 | mod bound {
42 | use super::*;
43 |
44 | pub trait Fun {
45 | type Out: Schema;
46 | }
47 |
48 | pub enum Id {}
49 | impl Fun for Id {
50 | type Out = In;
51 | }
52 | }
53 |
54 | #[test]
55 | fn test_enum_serialize() {
56 | assert_eq!(
57 | &DataModelType::Enum {
58 | name: "Inner",
59 | variants: &[
60 | &Variant {
61 | name: "Alpha",
62 | data: Data::Unit
63 | },
64 | &Variant {
65 | name: "Beta",
66 | data: Data::Unit
67 | },
68 | &Variant {
69 | name: "Gamma",
70 | data: Data::Unit
71 | },
72 | &Variant {
73 | name: "Delta",
74 | data: Data::Tuple(&[i32::SCHEMA, i16::SCHEMA,])
75 | },
76 | &Variant {
77 | name: "Epsilon",
78 | data: Data::Struct(&[
79 | &NamedField {
80 | name: "zeta",
81 | ty: f32::SCHEMA,
82 | },
83 | &NamedField {
84 | name: "eta",
85 | ty: bool::SCHEMA,
86 | }
87 | ]),
88 | }
89 | ],
90 | },
91 | Inner::SCHEMA
92 | );
93 | }
94 |
95 | #[test]
96 | fn test_struct_serialize() {
97 | assert_eq!(
98 | Outer::SCHEMA,
99 | &DataModelType::Struct {
100 | name: "Outer",
101 | data: Data::Struct(&[
102 | &NamedField {
103 | name: "a",
104 | ty: u32::SCHEMA
105 | },
106 | &NamedField {
107 | name: "b",
108 | ty: u64::SCHEMA
109 | },
110 | &NamedField {
111 | name: "c",
112 | ty: u8::SCHEMA
113 | },
114 | &NamedField {
115 | name: "d",
116 | ty: Inner::SCHEMA
117 | },
118 | &NamedField {
119 | name: "e",
120 | ty: &DataModelType::Tuple(&[u8::SCHEMA; 10]),
121 | },
122 | &NamedField {
123 | name: "f",
124 | ty: &DataModelType::Seq(u8::SCHEMA),
125 | },
126 | ]),
127 | }
128 | );
129 | }
130 |
131 | #[test]
132 | fn test_slice_serialize() {
133 | assert_eq!(
134 | &DataModelType::Struct {
135 | name: "Slice",
136 | data: Data::Struct(&[&NamedField {
137 | name: "x",
138 | ty: &DataModelType::Seq(u8::SCHEMA),
139 | }]),
140 | },
141 | Slice::SCHEMA
142 | );
143 | }
144 |
145 | #[test]
146 | fn test_bound_serialize() {
147 | assert_eq!(
148 | &DataModelType::Struct {
149 | name: "Bound",
150 | data: Data::Struct(&[&NamedField {
151 | name: "x",
152 | ty: u8::SCHEMA
153 | }]),
154 | },
155 | Bound::::SCHEMA,
156 | );
157 | }
158 |
159 | #[allow(unused)]
160 | #[derive(Debug, Schema)]
161 | enum TestEnum<'a> {
162 | Alpha,
163 | Beta(u32),
164 | Gamma { a: bool, b: &'a [u8] },
165 | Delta(f32, Option<&'a str>),
166 | Epsilon(TestStruct1),
167 | }
168 |
169 | #[allow(unused)]
170 | #[derive(Debug, Schema)]
171 | struct TestStruct1 {
172 | a: i8,
173 | b: i16,
174 | c: i32,
175 | d: i64,
176 | }
177 |
178 | #[allow(unused)]
179 | #[derive(Debug, Schema)]
180 | struct TestStruct2<'a> {
181 | x: TestEnum<'a>,
182 | y: TestStruct1,
183 | z: Result,
184 | }
185 |
186 | #[test]
187 | fn owned_punning() {
188 | let borrowed_schema = TestStruct2::SCHEMA;
189 | let owned_schema: OwnedDataModelType = borrowed_schema.into();
190 |
191 | // Check that they are the same on the wire when serialized
192 | let ser_borrowed_schema = postcard::to_stdvec(borrowed_schema).unwrap();
193 | let ser_owned_schema = postcard::to_stdvec(&owned_schema).unwrap();
194 | assert_eq!(ser_borrowed_schema, ser_owned_schema);
195 |
196 | // TODO: This is wildly repetitive, and likely could benefit from interning of
197 | // repeated types, strings, etc.
198 | assert_eq!(ser_borrowed_schema.len(), 187);
199 |
200 | // Check that we round-trip correctly
201 | let deser_borrowed_schema =
202 | postcard::from_bytes::(&ser_borrowed_schema).unwrap();
203 | let deser_owned_schema = postcard::from_bytes::(&ser_owned_schema).unwrap();
204 | assert_eq!(deser_borrowed_schema, deser_owned_schema);
205 | assert_eq!(deser_borrowed_schema, owned_schema);
206 | assert_eq!(deser_owned_schema, owned_schema);
207 | }
208 |
209 | #[allow(unused)]
210 | #[derive(Debug, Schema)]
211 | struct TestStruct3(u64);
212 |
213 | #[allow(unused)]
214 | #[derive(Debug, Schema)]
215 | struct TestStruct4(u64, bool);
216 |
217 | #[allow(unused)]
218 | #[derive(Debug, Schema)]
219 | enum TestEnum2 {
220 | Nt(u64),
221 | Tup(u64, bool),
222 | }
223 |
224 | #[test]
225 | fn newtype_vs_tuple() {
226 | assert_eq!(
227 | TestStruct3::SCHEMA,
228 | &DataModelType::Struct {
229 | name: "TestStruct3",
230 | data: Data::Newtype(u64::SCHEMA)
231 | }
232 | );
233 |
234 | assert_eq!(
235 | TestStruct4::SCHEMA,
236 | &DataModelType::Struct {
237 | name: "TestStruct4",
238 | data: Data::Tuple(&[u64::SCHEMA, bool::SCHEMA]),
239 | }
240 | );
241 |
242 | assert_eq!(
243 | TestEnum2::SCHEMA,
244 | &DataModelType::Enum {
245 | name: "TestEnum2",
246 | variants: &[
247 | &Variant {
248 | name: "Nt",
249 | data: Data::Newtype(u64::SCHEMA)
250 | },
251 | &Variant {
252 | name: "Tup",
253 | data: Data::Tuple(&[u64::SCHEMA, bool::SCHEMA])
254 | },
255 | ],
256 | }
257 | );
258 | }
259 |
260 | // Formatting
261 |
262 | fn dewit() -> String {
263 | let schema: OwnedDataModelType = T::SCHEMA.into();
264 | schema.to_pseudocode()
265 | }
266 |
267 | #[allow(unused)]
268 | #[derive(Schema)]
269 | struct UnitStruct;
270 |
271 | #[allow(unused)]
272 | #[derive(Schema)]
273 | struct NewTypeStruct(String);
274 |
275 | #[allow(unused)]
276 | #[derive(Schema)]
277 | struct TupStruct(u64, String);
278 |
279 | #[allow(unused)]
280 | #[derive(Schema)]
281 | enum Enums {
282 | Unit,
283 | Nt(u64),
284 | Tup(u32, bool),
285 | }
286 |
287 | #[allow(unused)]
288 | #[derive(Schema)]
289 | struct Classic {
290 | a: u32,
291 | b: u16,
292 | c: bool,
293 | }
294 |
295 | #[allow(unused)]
296 | #[derive(Schema)]
297 | struct ClassicGen {
298 | a: u32,
299 | b: T,
300 | }
301 |
302 | #[test]
303 | fn smoke() {
304 | #[allow(clippy::type_complexity)]
305 | let tests: &[(fn() -> String, &str)] = &[
306 | (dewit::, "u8"),
307 | (dewit::, "u16"),
308 | (dewit::, "u32"),
309 | (dewit::, "u64"),
310 | (dewit::, "u128"),
311 | (dewit::, "i8"),
312 | (dewit::, "i16"),
313 | (dewit::, "i32"),
314 | (dewit::, "i64"),
315 | (dewit::, "i128"),
316 | (dewit::<()>, "()"),
317 | (dewit::, "char"),
318 | (dewit::, "bool"),
319 | (dewit::, "String"),
320 | (dewit::