├── .github ├── ISSUE_TEMPLATE │ ├── bug.yml │ ├── config.yml │ └── feature.yml └── workflows │ ├── docs.yml │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── THIRD-PARTY-NOTICES.TXT ├── config.nims ├── deser.nimble ├── nim.cfg ├── nimdoc.cfg ├── src ├── deser.nim └── deser │ ├── base_error.nim │ ├── des.nim │ ├── des │ ├── errors.nim │ ├── helpers.nim │ ├── impls.nim │ └── make.nim │ ├── errors.nim │ ├── helpers.nim │ ├── macroutils │ ├── anycase.nim │ ├── generation │ │ ├── des.nim │ │ ├── des │ │ │ ├── keys.nim │ │ │ ├── utils.nim │ │ │ └── values.nim │ │ ├── ser.nim │ │ ├── ser │ │ │ └── ser.nim │ │ └── utils.nim │ ├── matching.nim │ ├── parsing │ │ ├── field.nim │ │ ├── pragmas.nim │ │ └── struct.nim │ └── types.nim │ ├── pragmas.nim │ ├── ser.nim │ ├── ser │ ├── helpers.nim │ ├── impls.nim │ └── make.nim │ ├── test.nim │ └── test │ ├── des.nim │ ├── ser.nim │ └── token.nim └── tests ├── config.nims ├── des ├── config.nims ├── timpls.nim └── tmake.nim ├── macroutils ├── config.nims ├── tparsepragma.nim ├── tparsestruct.nim └── ttypes.nim ├── ser ├── config.nims ├── timpls.nim └── tmake.nim └── thelpers.nim /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report issues affecting the library or the documentation. 3 | labels: 4 | - bug 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: Checklist 9 | options: 10 | - label: I am sure the error is coming from deser code 11 | required: true 12 | - label: I have searched in the issue tracker for similar bug reports, including closed ones 13 | required: true 14 | 15 | - type: markdown 16 | attributes: 17 | value: | 18 | ## Context 19 | 20 | Please provide as much information as possible. This will help us to reproduce the issue and fix it. 21 | 22 | - type: textarea 23 | attributes: 24 | label: Nim version. 25 | description: Copy and paste the output of `nim -v` on the command line. 26 | render: sh 27 | validations: 28 | required: true 29 | 30 | - type: input 31 | attributes: 32 | label: deser version 33 | placeholder: E.g. 0.3.0 34 | validations: 35 | required: true 36 | 37 | - type: textarea 38 | attributes: 39 | label: Current behavior 40 | description: Please describe the behavior you are currently experiencing. 41 | validations: 42 | required: true 43 | 44 | - type: textarea 45 | attributes: 46 | label: Expected behavior 47 | description: Please describe the behavior you are expecting. 48 | validations: 49 | required: true 50 | 51 | - type: textarea 52 | attributes: 53 | label: Steps to reproduce 54 | description: Please describe the steps you took to reproduce the behavior. 55 | placeholder: | 56 | 1. step 1 57 | 2. step 2 58 | 3. ... 59 | 4. you get it... 60 | validations: 61 | required: true 62 | 63 | - type: textarea 64 | attributes: 65 | label: Code example 66 | description: Provide a [minimal, reproducible](https://stackoverflow.com/help/minimal-reproducible-example) and properly formatted example (if applicable). 67 | placeholder: | 68 | import deser 69 | ... 70 | makeDeserializable(Foo) 71 | render: nim 72 | 73 | - type: textarea 74 | attributes: 75 | label: Logs 76 | description: Provide the complete traceback (if applicable) or other kind of logs. 77 | render: sh 78 | 79 | - type: textarea 80 | attributes: 81 | label: Debug dump 82 | description: If you think the bug is related to the `makeSerializable` and/or `makeDeserializable` macros, please compile with the `-d:debugMakeSerializable` and/or `-d:debugMakeDeserializable` flag and paste the output. 83 | render: nim 84 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Report features you would like to see or improve in the library. 3 | labels: 4 | - enhancement 5 | body: 6 | - type: textarea 7 | attributes: 8 | label: Problem 9 | description: Is your feature request related to a specific problem? If not, please describe the general idea of your request. 10 | validations: 11 | required: true 12 | 13 | - type: textarea 14 | attributes: 15 | label: Possible solution 16 | description: Describe the solution you would like to see in the library. 17 | validations: 18 | required: true 19 | 20 | - type: textarea 21 | attributes: 22 | label: References 23 | description: If you have seen a similar feature in another library/framework/lanugage. 24 | 25 | - type: textarea 26 | attributes: 27 | label: Code example 28 | description: A small code example that demonstrates the behavior you would like to see. 29 | render: nim 30 | 31 | - type: textarea 32 | attributes: 33 | label: Additional information 34 | description: Any additional information you would like to provide. -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Build docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | path-ignore: 8 | - 'README.md' 9 | release: 10 | types: [published] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Set up Nim 19 | uses: jiro4989/setup-nim-action@v1 20 | with: 21 | nim-version: 1.6.12 22 | 23 | - name: Build docs 24 | run: nimble docs 25 | 26 | - name: Deploy docs 27 | uses: peaceiris/actions-gh-pages@v3 28 | with: 29 | github_token: ${{ secrets.GITHUB_TOKEN }} 30 | publish_dir: ./docs 31 | keep_files: true 32 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | path-ignore: 8 | - 'README.md' 9 | pull_request: 10 | types: [opened, synchronize, reopened, ready_for_review] 11 | branches: 12 | - master 13 | path-ignore: 14 | - 'README.md' 15 | 16 | jobs: 17 | build: 18 | if: "github.event.pull_request.draft == false && ! contains(toJSON(github.event.commits.*.message), '[skip-ci]')" 19 | strategy: 20 | matrix: 21 | nim: ['1.6.12', 'devel'] 22 | os: [ubuntu-latest] 23 | runs-on: ${{ matrix.os }} 24 | steps: 25 | - uses: actions/checkout@v2 26 | 27 | - name: Cache nimble 28 | id: cache-nimble 29 | uses: actions/cache@v1 30 | with: 31 | path: ~/.nimble 32 | key: ${{ runner.os }}-nimble-${{ hashFiles('*.nimble') }} 33 | if: runner.os != 'Windows' 34 | 35 | - name: Set up Nim 36 | uses: jiro4989/setup-nim-action@v1 37 | with: 38 | nim-version: ${{ matrix.nim }} 39 | 40 | - name: Install dependencies 41 | run: nimble install -d -y 42 | 43 | - name: Run tests 44 | run: nimble test 45 | 46 | - name: Try to build docs 47 | run: nimble docs 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache/ 2 | nimblecache/ 3 | htmldocs/ 4 | testresults/ 5 | docs/ 6 | 7 | *.o 8 | !/icons/*.o 9 | *.obj 10 | *.ilk 11 | *.exp 12 | *.pdb 13 | *.lib 14 | *.dll 15 | *.exe 16 | *.so 17 | *.dylib 18 | *.zip 19 | *.iss 20 | *.log 21 | *.pdb 22 | 23 | oc/html/ 24 | doc/*.html 25 | doc/*.pdf 26 | doc/*.idx 27 | /web/upload 28 | /build/* 29 | bin/* 30 | 31 | /*.html 32 | outputGotten.txt 33 | tests/megatest.nim 34 | outputExpected.txt 35 | 36 | .idea/ 37 | tests/megatest 38 | tests/des/timpls 39 | tests/ser/timpls 40 | tests/ser/tmake 41 | tests/des/tmake 42 | src/deser/macroutils/generation/ser/ser 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Nikita Gabbasov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deser [![nim-version-img]][nim-version] 2 | 3 | [nim-version]: https://nim-lang.org/blog/2021/10/19/version-160-released.html 4 | [nim-version-img]: https://img.shields.io/badge/Nim_-v1.6.0%2B-blue 5 | 6 | **Serde-like de/serialization library for Nim.** 7 | 8 | `nimble install deser` 9 | 10 | [Documentation](https://deser.nim.town) 11 | 12 | --- 13 | 14 |
15 | Motivation 16 | Many serializers have already been written for Nim. You can probably find at least two serializers for each format. 17 | 18 | The problem is that each library's API and customization options are different. I can't declare an object with renamed or skipped fields once and change the data format with one line. 19 | 20 | Attempts to generalize the serializer were also made. However, I found only one library that is actively under development - [nim-serialization](https://github.com/status-im/nim-serialization). When installing the library downloaded a quarter of all libraries for Nim, so I did not try it. 21 | 22 | Thus, there was no library for Nim that standardized the serialization process, so I wrote **deser**. 23 | 24 | Also read: 25 | - [Standards](https://xkcd.com/927/) 26 | - [Not invented here](https://en.wikipedia.org/wiki/Not_invented_here) 27 |
28 | 29 | 30 | ## Supported formats 31 | - JSON - [deser_json](https://github.com/gabbhack/deser_json) 32 | 33 | Also read: 34 | - [How to make bindings](https://deser.nim.town/deser.html#how-to-make-bindings) 35 | 36 | 37 | ## Example 38 | ```nim 39 | import std/[ 40 | options, 41 | times 42 | ] 43 | 44 | import 45 | deser, 46 | deser_json 47 | 48 | proc fromTimestamp(deserializer: var auto): Time = 49 | fromUnix(deserialize(int64, deserializer)) 50 | 51 | proc toTimestamp(self: Time, serializer: var auto) = 52 | serializer.serializeInt64(self.toUnix()) 53 | 54 | type 55 | ChatType = enum 56 | Private = "private" 57 | Group = "group" 58 | 59 | Chat {.renameAll(SnakeCase).} = object 60 | id: int64 61 | username {.skipSerializeIf(isNone).}: Option[string] 62 | created {.serializeWith(toTimestamp), deserializeWith(fromTimestamp).}: Time 63 | 64 | case kind {.renamed("type").}: ChatType 65 | of Private: 66 | firstName: string 67 | lastName {.skipSerializeIf(isNone).}: Option[string] 68 | bio {.skipSerializeIf(isNone).}: Option[string] 69 | of Group: 70 | title: string 71 | 72 | # Use public to export deserialize or serialize procedures 73 | # false by default 74 | makeSerializable(Chat, public=true) 75 | makeDeserializable(Chat, public=true) 76 | 77 | const 78 | json = """ 79 | { 80 | "id": 123, 81 | "username": "gabbhack", 82 | "created": 1234567890, 83 | "type": "private", 84 | "first_name": "Gabben" 85 | } 86 | """ 87 | chat = Chat( 88 | id: 123, 89 | username: some "gabbhack", 90 | created: fromUnix(1234567890), 91 | kind: Private, 92 | firstName: "Gabben" 93 | ) 94 | 95 | echo Chat.fromJson(json) 96 | echo chat.toJson() 97 | ``` 98 | 99 | Also read: 100 | - [Customize serialization process](https://deser.nim.town/deser.html#customize-serialization-process) 101 | 102 | ## License 103 | Licensed under MIT license. 104 | 105 | Deser uses third-party libraries or other resources that may be 106 | distributed under licenses different than the deser. 107 | 108 | THIRD-PARTY-NOTICES.TXT 109 | 110 | 111 | ## Acknowledgements 112 | - [serde.rs](https://serde.rs), for all the ideas I stole 113 | - [fusion/matching](https://github.com/nim-lang/fusion/blob/master/src/fusion/matching.nim), for making it easier to work with object variants 114 | - [anycase](https://github.com/epszaw/anycase) -------------------------------------------------------------------------------- /THIRD-PARTY-NOTICES.TXT: -------------------------------------------------------------------------------- 1 | deser uses third-party libraries or other resources that may be 2 | distributed under licenses different than the deser 3 | 4 | The attached notices are provided for information only. 5 | 6 | License notice for fusion/matching 7 | ------------------------------- 8 | MIT License 9 | 10 | Copyright (c) 2020 Andreas Rumpf and contributors 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy 13 | of this software and associated documentation files (the "Software"), to deal 14 | in the Software without restriction, including without limitation the rights 15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | copies of the Software, and to permit persons to whom the Software is 17 | furnished to do so, subject to the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be included in all 20 | copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | SOFTWARE. 29 | ------------------------------- 30 | 31 | License notice for anycase-fork 32 | The MIT License (MIT) 33 | 34 | Copyright 2019 Konstantin Epishev 35 | Copyright (c) 2020 Nikita "gabbhack" Gabbasov 36 | 37 | Permission is hereby granted, free of charge, to any person obtaining a copy of 38 | this software and associated documentation files (the "Software"), to deal in 39 | the Software without restriction, including without limitation the rights to 40 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 41 | the Software, and to permit persons to whom the Software is furnished to do so, 42 | subject to the following conditions: 43 | 44 | The above copyright notice and this permission notice shall be included in all 45 | copies or substantial portions of the Software. 46 | 47 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 48 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 49 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 50 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 51 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 52 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /config.nims: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabbhack/deser/efa0b92765fa84130aadb62344aec4e703f13805/config.nims -------------------------------------------------------------------------------- /deser.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.3.2" 4 | author = "gabbhack" 5 | description = "De/serialization library for Nim" 6 | license = "MIT" 7 | srcDir = "src" 8 | 9 | 10 | # Dependencies 11 | 12 | requires "nim >= 1.6.0" 13 | 14 | # Tasks 15 | import std/[os, strformat] 16 | 17 | task test, "Run tests": 18 | exec "testament all" 19 | 20 | task docs, "Generate docs": 21 | rmDir "docs" 22 | exec "nimble doc2 --outdir:docs --project --git.url:https://github.com/gabbhack/deser --git.commit:master --index:on src/deser" 23 | -------------------------------------------------------------------------------- /nim.cfg: -------------------------------------------------------------------------------- 1 | path="$config/src" -------------------------------------------------------------------------------- /nimdoc.cfg: -------------------------------------------------------------------------------- 1 | doc.body_toc_group = """ 2 |
3 |
4 |
5 | 9 |     Dark Mode 10 |
11 | 24 |
25 | Search: 27 |
28 |
29 | Group by: 30 | 34 |
35 | $tableofcontents 36 |
37 | 38 |
39 |
40 | $deprecationMsg 41 |

$moduledesc

42 | $content 43 |
44 |
45 | """ 46 | -------------------------------------------------------------------------------- /src/deser.nim: -------------------------------------------------------------------------------- 1 | ##[ 2 | Deser is a library for serializing and deserializing Nim data structures efficiently and generically. Just like [Serde](https://serde.rs/). 3 | 4 | # Explore 5 | First, install Deser via `nimble install deser`. 6 | 7 | Deser is not a parser library. You need to install some parser from [Supported formats](#supported-formats). 8 | 9 | We use [deser_json](https://github.com/gabbhack/deser_json/) for example - `nimble install deser_json`. 10 | 11 | Let's say we have an API with `Message` type that has this output: 12 | ```nim 13 | const json = """ 14 | { 15 | "id": 1, 16 | "text": "Hello!", 17 | "created": 1660848266 18 | } 19 | """ 20 | ``` 21 | 22 | Let's try to map it to the Nim object. 23 | Use [makeSerializable](deser/ser/make.html#makeSerializable.m,varargs[typedesc],static[bool]) 24 | and 25 | [makeDeserializable](deser/des/make.html#makeDeserializable.m,varargs[typedesc],static[bool]) 26 | to generate `serialize` and `deserialize` procedures for your type. 27 | 28 | .. Note:: This is a mandatory step. The generated procedures will then be used by parsers. 29 | 30 | ```nim 31 | import 32 | deser, 33 | deser_json 34 | 35 | type 36 | Message = object 37 | id: int 38 | text: string 39 | created: int 40 | 41 | makeSerializable(Message) 42 | makeDeserializable(Message) 43 | ``` 44 | 45 | Use `toJson` and `fromJson` procedures from `deser_json` to serialize and deserialize to/from JSON: 46 | 47 | ```nim 48 | let chat = Message.fromJson(json) 49 | echo chat.toJson() 50 | ``` 51 | 52 | # De/serialize Time and DateTime 53 | 54 | In previous section we created `Message` type where `created` field is the time in unix format. 55 | 56 | It is not convenient to work with it as `int`. But if we try to just use [Time](https://nim-lang.org/docs/times.html#Time), we get an error: 57 | 58 | ```nim 59 | Error: type mismatch: got 60 | but expected one of: ... 61 | ``` 62 | 63 | .. Note:: Out of the box, deser supports serialization of [many types](#supported-stdminustypes) from the standard library. However, some types, such as `Time`, cannot be unambiguously serialized. Therefore, the user must explicitly specify how to serialize such types. 64 | 65 | To fix this error, we need to use the [serializeWith](deser/pragmas.html#serializeWith.t%2Ctyped) and [deserializeWith](deser/pragmas.html#deserializeWith.t,typed) pragmas. 66 | 67 | Full code: 68 | 69 | ```nim 70 | import std/times 71 | 72 | import 73 | deser, 74 | deser_json 75 | 76 | proc toTimestamp(self: Time, serializer: var auto) = 77 | serializer.serializeInt64(self.toUnix()) 78 | 79 | proc fromTimestamp(deserializer: var auto): Time = 80 | fromUnix(deserialize(int64, deserializer)) 81 | 82 | type 83 | Message = object 84 | id: int 85 | text: string 86 | created {.serializeWith(toTimestamp), deserializeWith(fromTimestamp).}: Time 87 | 88 | makeSerializable(Message) 89 | makeDeserializable(Message) 90 | ``` 91 | 92 | .. Note:: Since deser 0.3.2 you can use the [helpers](https://deser.nim.town/deser/helpers.html) module and [deserWith](https://deser.nim.town/deser/pragmas.html#deserWith.t%2Ctyped) pragma for convenient time de/serialization. 93 | 94 | Example: 95 | 96 | ```nim 97 | import std/times 98 | 99 | import 100 | deser, 101 | deser_json 102 | 103 | type 104 | Message = object 105 | id: int 106 | text: string 107 | created {.deserWith(UnixTimeFormat).}: Time 108 | 109 | const json = """ 110 | { 111 | "id": 1, 112 | "text": "Hello!", 113 | "created": 1660848266 114 | } 115 | """ 116 | let chat = Message.fromJson(json) 117 | echo chat.toJson() 118 | ``` 119 | 120 | # Supported std-types 121 | - [bool](https://nim-lang.org/docs/system.html#bool) 122 | - [int8-64](https://nim-lang.org/docs/system.html#SomeSignedInt) (`int` serializaed as int64) 123 | - [uint8-64](https://nim-lang.org/docs/system.html#SomeUnsignedInt) (`uint` serialized as uint64) 124 | - [float32-64](https://nim-lang.org/docs/system.html#SomeFloat) (`float` serializaed as float64) 125 | - [char](https://nim-lang.org/docs/system.html#char) 126 | - [string](https://nim-lang.org/docs/system.html#string) 127 | - [seq](https://nim-lang.org/docs/system.html#seq) 128 | - [array](https://nim-lang.org/docs/system.html#array) 129 | - enum (serialized as the parser decides) 130 | - tuple (serialized as `array`) 131 | - [set](https://nim-lang.org/docs/system.html#set) 132 | - [range](https://nim-lang.org/docs/system.html#range) 133 | - [Option](https://nim-lang.org/docs/options.html#Option) 134 | - [HashSet](https://nim-lang.org/docs/sets.html#HashSet) 135 | - [OrderedSet](https://nim-lang.org/docs/sets.html#OrderedSet) 136 | - [Table](https://nim-lang.org/docs/tables.html#Table) 137 | - [OrderedTable](https://nim-lang.org/docs/tables.html#OrderedTable) 138 | 139 | # Supported formats 140 | [How to make bindings](#how-to-make-bindings) 141 | - JSON - [deser_json](https://github.com/gabbhack/deser_json) 142 | 143 | # Customize serialization process 144 | Deser allows you to customize the serialization process, and the configuration will be applied to any parser. 145 | 146 | Configuration is done with pragmas that are applied at compile-time. [View available pragmas](deser/pragmas.html). 147 | 148 | # How to make bindings 149 | Check example at [deser_json](https://github.com/gabbhack/deser_json). 150 | 151 | Check helpers templates for [serialization](deser/ser/helpers.html) and [deserialization](deser/des/helpers.html). 152 | 153 | .. Note:: This section of the documentation is being supplemented. 154 | ]## 155 | 156 | import deser/[ 157 | pragmas, 158 | des, 159 | ser, 160 | helpers 161 | ] 162 | 163 | export 164 | pragmas, 165 | des, 166 | ser, 167 | helpers 168 | -------------------------------------------------------------------------------- /src/deser/base_error.nim: -------------------------------------------------------------------------------- 1 | type 2 | DeserError* = object of CatchableError ## \ 3 | ## Base `deser` error 4 | -------------------------------------------------------------------------------- /src/deser/des.nim: -------------------------------------------------------------------------------- 1 | import des/[ 2 | helpers, 3 | errors, 4 | impls, 5 | make 6 | ] 7 | 8 | export 9 | helpers, 10 | errors, 11 | impls, 12 | make 13 | -------------------------------------------------------------------------------- /src/deser/des/errors.nim: -------------------------------------------------------------------------------- 1 | import std/[ 2 | strformat 3 | ] 4 | 5 | from deser/base_error import 6 | DeserError 7 | 8 | 9 | type 10 | DeserializationError* = object of DeserError ## \ 11 | ## Error during deserialization 12 | 13 | InvalidType* = object of DeserializationError ## \ 14 | ## Raised when a `Deserialize` receives a type different from what it was expecting. 15 | 16 | InvalidValue* = object of DeserializationError ## \ 17 | ## Raised when a `Deserialize` receives a value of the right type but that is wrong for some other reason. 18 | 19 | InvalidLength* = object of DeserializationError ## \ 20 | ## Raised when deserializing a sequence or map and the input data contains too many or too few elements. 21 | 22 | UnknownField* = object of DeserializationError ## \ 23 | ## Raised when a `Deserialize` enum type received a variant with an unrecognized name. 24 | 25 | MissingField* = object of DeserializationError ## \ 26 | ## Raised when a `Deserialize` struct type expected to receive a required field with a particular name but that field was not present in the input. 27 | 28 | DuplicateField* = object of DeserializationError ## \ 29 | ## Raised when a `Deserialize` struct type received more than one of the same field. 30 | 31 | UnknownUntaggedVariant* = object of DeserializationError ## \ 32 | ## Raised when a `Deserialize` struct type cannot derive case variant. 33 | 34 | UnexpectedKind* = enum 35 | Bool, 36 | Unsigned, 37 | Signed, 38 | Float, 39 | Char, 40 | String, 41 | Bytes, 42 | Option, 43 | Seq, 44 | Map 45 | 46 | Unexpected* = object 47 | case kind*: UnexpectedKind 48 | of Bool: 49 | boolValue*: bool 50 | of Unsigned: 51 | unsignedValue*: uint64 52 | of Signed: 53 | signedValue*: int64 54 | of Float: 55 | floatValue*: float64 56 | of Char: 57 | charValue*: char 58 | of String: 59 | stringValue*: string 60 | of Bytes: 61 | bytesValue*: seq[byte] 62 | else: 63 | nil 64 | 65 | 66 | when defined(release): 67 | {.push inline.} 68 | func `$`*(self: Unexpected): string {.inline.} = 69 | case self.kind 70 | of Bool: 71 | &"boolean `{self.boolValue}`" 72 | of Unsigned: 73 | &"integer `{self.unsignedValue}`" 74 | of Signed: 75 | &"integer `{self.signedValue}`" 76 | of Float: 77 | &"floating point `{self.floatValue}`" 78 | of Char: 79 | &"character `{self.charValue}`" 80 | of String: 81 | &"string `{self.stringValue}`" 82 | of Bytes: 83 | "byte array" 84 | of Option: 85 | "Option value" 86 | of Seq: 87 | "sequence" 88 | of Map: 89 | "map" 90 | 91 | 92 | func initUnexpectedBool*(value: bool): auto = Unexpected(kind: Bool, boolValue: value) 93 | 94 | func initUnexpectedUnsigned*(value: uint64): auto = Unexpected(kind: Unsigned, unsignedValue: value) 95 | 96 | func initUnexpectedSigned*(value: int64): auto = Unexpected(kind: Signed, signedValue: value) 97 | 98 | func initUnexpectedFloat*(value: float64): auto = Unexpected(kind: Float, floatValue: value) 99 | 100 | func initUnexpectedChar*(value: char): auto = Unexpected(kind: Char, charValue: value) 101 | 102 | func initUnexpectedString*(value: sink string): auto = Unexpected(kind: String, stringValue: value) 103 | 104 | func initUnexpectedBytes*(value: sink seq[byte]): auto = Unexpected(kind: Bytes, bytesValue: value) 105 | 106 | func initUnexpectedOption*(): auto = Unexpected(kind: Option) 107 | 108 | func initUnexpectedSeq*(): auto = Unexpected(kind: Seq) 109 | 110 | func initUnexpectedMap*(): auto = Unexpected(kind: Map) 111 | when defined(release): 112 | {.pop.} 113 | 114 | 115 | {.push noinline, noreturn.} 116 | proc raiseInvalidType*(unexp: Unexpected, exp: auto) = 117 | mixin expecting 118 | 119 | raise newException(InvalidType, &"invalid type: {$unexp}, expected: {exp.expecting()}") 120 | 121 | proc raiseInvalidValue*(unexp: Unexpected, exp: auto) = 122 | mixin expecting 123 | 124 | raise newException(InvalidValue, &"invalid value: {$unexp}, expected: {exp.expecting()}") 125 | 126 | proc raiseInvalidLength*(unexp: int, exp: auto) = 127 | raise newException(InvalidLength, &"invalid length: {$unexp}, expected: {$exp}") 128 | 129 | proc raiseUnknownField*(unexp: sink string) = 130 | raise newException(UnknownField, &"unknown field {$unexp}, there are no fields") 131 | 132 | proc raiseMissingField*(field: static[string]) = 133 | raise newException(MissingField, &"missing field `{field}`") 134 | 135 | proc raiseDuplicateField*(field: static[string]) = 136 | raise newException(DuplicateField, &"duplicate field `{field}`") 137 | 138 | proc raiseUnknownUntaggedVariant*(struct: static[string], field: static[string]) = 139 | raise newException(UnknownUntaggedVariant, &"not possible to derive value of case field `{field}` of struct `{struct}`") 140 | {.pop.} 141 | -------------------------------------------------------------------------------- /src/deser/des/impls.nim: -------------------------------------------------------------------------------- 1 | import std/[ 2 | options, 3 | macros, 4 | strutils, 5 | strformat, 6 | typetraits, 7 | sets, 8 | tables, 9 | enumerate 10 | ] 11 | 12 | from errors import 13 | raiseInvalidValue, 14 | raiseMissingField, 15 | initUnexpectedString, 16 | initUnexpectedSigned, 17 | initUnexpectedFloat 18 | 19 | from deser/macroutils/generation/des/utils import 20 | genPrimitive, 21 | genArray, 22 | visitEnumIntBody, 23 | visitRangeIntBody, 24 | visitRangeFloatBody, 25 | rangeUnderlyingType 26 | 27 | from helpers import 28 | implVisitor, 29 | NoneSeed, 30 | IgnoredAny 31 | 32 | 33 | when defined(release): 34 | {.push inline.} 35 | 36 | {.push used.} 37 | proc deserialize*[T](self: NoneSeed[T], deserializer: var auto): T = 38 | mixin deserialize 39 | 40 | result = T.deserialize(deserializer) 41 | 42 | 43 | type 44 | IgnoredAnyVisitorRaw[Value] = object 45 | IgnoredAnyVisitor = IgnoredAnyVisitorRaw[IgnoredAny] 46 | 47 | implVisitor(IgnoredAnyVisitor, public=true) 48 | 49 | proc expecting*(self: IgnoredAnyVisitor): string = "anything" 50 | 51 | proc visitBool*(self: IgnoredAnyVisitor, value: bool): self.Value = IgnoredAny() 52 | 53 | proc visitInt8*(self: IgnoredAnyVisitor, value: int8): self.Value = IgnoredAny() 54 | 55 | proc visitInt16*(self: IgnoredAnyVisitor, value: int16): self.Value = IgnoredAny() 56 | 57 | proc visitInt32*(self: IgnoredAnyVisitor, value: int32): self.Value = IgnoredAny() 58 | 59 | proc visitInt64*(self: IgnoredAnyVisitor, value: int64): self.Value = IgnoredAny() 60 | 61 | proc visitUint8*(self: IgnoredAnyVisitor, value: uint8): self.Value = IgnoredAny() 62 | 63 | proc visitUint16*(self: IgnoredAnyVisitor, value: uint16): self.Value = IgnoredAny() 64 | 65 | proc visitUint32*(self: IgnoredAnyVisitor, value: uint32): self.Value = IgnoredAny() 66 | 67 | proc visitUint64*(self: IgnoredAnyVisitor, value: uint64): self.Value = IgnoredAny() 68 | 69 | proc visitFloat32*(self: IgnoredAnyVisitor, value: float32): self.Value = IgnoredAny() 70 | 71 | proc visitFloat64*(self: IgnoredAnyVisitor, value: float64): self.Value = IgnoredAny() 72 | 73 | proc visitChar*(self: IgnoredAnyVisitor, value: char): self.Value = IgnoredAny() 74 | 75 | proc visitString*(self: IgnoredAnyVisitor, value: sink string): self.Value = IgnoredAny() 76 | 77 | proc visitBytes*(self: IgnoredAnyVisitor, value: openArray[byte]): self.Value = IgnoredAny() 78 | 79 | proc visitNone*(self: IgnoredAnyVisitor): self.Value = IgnoredAny() 80 | 81 | proc visitSome*(self: IgnoredAnyVisitor, deserializer: var auto): self.Value = 82 | mixin deserialize 83 | 84 | deserialize(IgnoredAny, deserializer) 85 | 86 | 87 | proc visitSeq*(self: IgnoredAnyVisitor, sequence: var auto): self.Value = 88 | mixin items 89 | 90 | for value in items[IgnoredAny](sequence): 91 | discard 92 | 93 | IgnoredAny() 94 | 95 | 96 | proc visitMap*(self: IgnoredAnyVisitor, map: var auto): self.Value = 97 | mixin keys, nextValue 98 | 99 | for key in keys[IgnoredAny](map): 100 | discard nextValue[IgnoredAny](map) 101 | 102 | IgnoredAny() 103 | 104 | 105 | proc deserialize*(Self: typedesc[IgnoredAny], deserializer: var auto): Self = 106 | mixin deserializeIgnoredAny 107 | 108 | deserializer.deserializeIgnoredAny(IgnoredAnyVisitor()) 109 | 110 | 111 | type 112 | BoolVisitorRaw[Value] = object 113 | BoolVisitor = BoolVisitorRaw[bool] 114 | 115 | implVisitor(BoolVisitor, public=true) 116 | 117 | proc expecting*(self: BoolVisitor): string = "a boolean" 118 | 119 | proc visitBool*(self: BoolVisitor, value: bool): self.Value = value 120 | 121 | proc deserialize*(Self: typedesc[bool], deserializer: var auto): Self = 122 | mixin deserializeBool 123 | 124 | deserializer.deserializeBool(BoolVisitor()) 125 | 126 | 127 | genPrimitive(int8) 128 | genPrimitive(int16) 129 | genPrimitive(int32) 130 | genPrimitive(int64) 131 | genPrimitive(int, deserializeInt64) 132 | 133 | genPrimitive(uint8) 134 | genPrimitive(uint16) 135 | genPrimitive(uint32) 136 | genPrimitive(uint64) 137 | genPrimitive(uint, deserializeUint64) 138 | 139 | genPrimitive(float32, floats=true) 140 | genPrimitive(float64, floats=true) 141 | 142 | 143 | type 144 | CharVisitorRaw[Value] = object 145 | CharVisitor = CharVisitorRaw[char] 146 | 147 | implVisitor(CharVisitor, public=true) 148 | 149 | proc expecting*(self: CharVisitor): string = "a character" 150 | 151 | proc visitChar*(self: CharVisitor, value: char): self.Value = value 152 | 153 | proc visitString*(self: CharVisitor, value: string): self.Value = 154 | if value.len == 1: 155 | value[0] 156 | else: 157 | raiseInvalidValue(initUnexpectedString(value), self) 158 | 159 | 160 | proc deserialize*(Self: typedesc[char], deserializer: var auto): Self = 161 | mixin deserializeChar 162 | 163 | deserializer.deserializeChar(CharVisitor()) 164 | 165 | 166 | type 167 | StringVisitorRaw[Value] = object 168 | StringVisitor = StringVisitorRaw[string] 169 | 170 | implVisitor(StringVisitor, public=true) 171 | 172 | proc expecting*(self: StringVisitor): string = "a string" 173 | 174 | proc visitString*(self: StringVisitor, value: string): self.Value = value 175 | 176 | proc deserialize*(Self: typedesc[string], deserializer: var auto): Self = 177 | mixin deserializeString 178 | 179 | deserializer.deserializeString(StringVisitor()) 180 | 181 | 182 | type 183 | BytesVisitorRaw[Value] = object 184 | BytesVisitor[T: seq or array] = BytesVisitorRaw[T] 185 | 186 | implVisitor(BytesVisitor, public=true) 187 | 188 | proc expecting*(self: BytesVisitor): string = "byte array or seq" 189 | 190 | proc expecting*[T](self: BytesVisitor[T]): string = $T 191 | 192 | proc visitBytes*[T](self: BytesVisitor[T], value: openArray[byte]): T = 193 | when T is array: 194 | if value.len == T.len: 195 | copyMem result[0].unsafeAddr, value[0].unsafeAddr, T.len * sizeof(result[0]) 196 | else: 197 | raiseInvalidLength(value.len, T.len) 198 | else: 199 | @value 200 | 201 | 202 | proc visitSeq*[T](self: BytesVisitor[T], sequence: var auto): T = 203 | mixin items, sizeHint 204 | 205 | when T is array: 206 | for index, i in enumerate(items[byte](sequence)): 207 | result[index] = i 208 | else: 209 | result = newSeqOfCap[byte](sequence.sizeHint.get(10)) 210 | for i in items[byte](sequence): 211 | result.add i 212 | 213 | 214 | proc deserialize*(Self: typedesc[seq[byte]], deserializer: var auto): Self = 215 | mixin deserializeBytes 216 | 217 | deserializer.deserializeBytes(BytesVisitor[Self]()) 218 | 219 | 220 | proc deserialize*[Size](Self: typedesc[array[Size, byte]], deserializer: var auto): Self = 221 | mixin deserializeBytes 222 | 223 | deserializer.deserializeBytes(BytesVisitor[Self]()) 224 | 225 | 226 | type 227 | OptionVisitorRaw[Value] = object 228 | OptionVisitor[T] = OptionVisitorRaw[Option[T]] 229 | 230 | implVisitor(OptionVisitor, public=true) 231 | 232 | proc expecting*(self: OptionVisitor): string = "option" 233 | 234 | proc expecting*[T](self: OptionVisitor[T]): string = &"option of `{$T}`" 235 | 236 | proc visitNone*[T](self: OptionVisitor[T]): Option[T] = none T 237 | 238 | proc visitSome*[T](self: OptionVisitor[T], deserializer: var auto): Option[T] = 239 | mixin deserialize 240 | 241 | some deserialize(T, deserializer) 242 | 243 | 244 | proc deserialize*[T](Self: typedesc[Option[T]], deserializer: var auto): Self = 245 | mixin deserializeOption 246 | 247 | deserializer.deserializeOption(OptionVisitor[T]()) 248 | 249 | 250 | type 251 | SeqVisitorRaw[Value] = object 252 | SeqVisitor[T] = SeqVisitorRaw[seq[T]] 253 | 254 | implVisitor(SeqVisitor, public=true) 255 | 256 | proc expecting*(self: SeqVisitor): string = "sequence" 257 | 258 | proc expecting*[T](self: SeqVisitor[T]): string = &"sequence of `{$T}`" 259 | 260 | proc visitSeq*[T](self: SeqVisitor[T], sequence: var auto): seq[T] = 261 | mixin sizeHint 262 | 263 | let size = sizeHint(sequence).get(0) 264 | 265 | result = newSeqOfCap[T](size) 266 | 267 | for item in items[T](sequence): 268 | result.add item 269 | 270 | 271 | proc deserialize*[T](Self: typedesc[seq[T]], deserializer: var auto): Self = 272 | mixin deserializeSeq 273 | 274 | deserializer.deserializeSeq(SeqVisitor[T]()) 275 | 276 | 277 | type 278 | ArrayVisitorRaw[Value] = object 279 | ArrayVisitor[T] = ArrayVisitorRaw[T] 280 | 281 | implVisitor(ArrayVisitor, public=true) 282 | 283 | proc expecting*(self: ArrayVisitor): string = "array" 284 | 285 | proc expecting*[T](self: ArrayVisitor[T]): string = $T 286 | 287 | proc visitSeq*[T](self: ArrayVisitor[T], sequence: var auto): T = 288 | mixin nextElement 289 | 290 | genArray(T.high, type(result[0])) 291 | 292 | 293 | proc deserialize*[Size](Self: typedesc[array[Size, not byte]], deserializer: var auto): Self = 294 | mixin deserializeArray 295 | 296 | deserializer.deserializeArray(Self.len, ArrayVisitor[Self]()) 297 | 298 | 299 | type 300 | EnumVisitorRaw[Value] = object 301 | EnumVisitor[T] = EnumVisitorRaw[T] 302 | 303 | implVisitor(EnumVisitor, public=true) 304 | 305 | proc expecting*(self: EnumVisitor): string = "enum" 306 | 307 | proc expecting*[T: enum](self: EnumVisitor[T]): string = &"enum `{$T}`" 308 | 309 | proc visitString*[T: enum](self: EnumVisitor[T], value: sink string): T = parseEnum[T](value) 310 | 311 | proc visitInt8*[T](self: EnumVisitor[T], value: int8): T = visitEnumIntBody() 312 | proc visitInt16*[T](self: EnumVisitor[T], value: int16): T = visitEnumIntBody() 313 | proc visitInt32*[T](self: EnumVisitor[T], value: int32): T = visitEnumIntBody() 314 | proc visitInt64*[T](self: EnumVisitor[T], value: int64): T = visitEnumIntBody() 315 | 316 | proc visitUint8*[T](self: EnumVisitor[T], value: uint8): T = visitEnumIntBody() 317 | proc visitUint16*[T](self: EnumVisitor[T], value: uint16): T = visitEnumIntBody() 318 | proc visitUint32*[T](self: EnumVisitor[T], value: uint32): T = visitEnumIntBody() 319 | proc visitUint64*[T](self: EnumVisitor[T], value: uint64): T = visitEnumIntBody() 320 | 321 | 322 | proc deserialize*(Self: typedesc[enum], deserializer: var auto): Self = 323 | mixin deserializeEnum 324 | 325 | deserializer.deserializeEnum(EnumVisitor[Self]()) 326 | 327 | 328 | type 329 | TupleVisitorRaw[Value] = object 330 | TupleVisitor[T] = TupleVisitorRaw[T] 331 | 332 | implVisitor(TupleVisitor, public=true) 333 | 334 | proc expecting*(self: TupleVisitor): string = "a tuple" 335 | 336 | proc expecting*[T](self: TupleVisitor[T]): string = &"{$T}" 337 | 338 | proc visitSeq*[T](self: TupleVisitor[T], sequence: var auto): T = 339 | mixin nextElement 340 | 341 | result = default(T) 342 | 343 | for name, field in fieldPairs(result): 344 | field = ( 345 | let temp = nextElement[field.type](sequence) 346 | if temp.isSome: 347 | temp.unsafeGet 348 | else: 349 | raiseMissingField(name) 350 | ) 351 | 352 | 353 | proc deserialize*(Self: typedesc[tuple], deserializer: var auto): Self = 354 | mixin deserializeArray 355 | 356 | deserializer.deserializeArray(tupleLen(Self), TupleVisitor[Self]()) 357 | 358 | 359 | type 360 | SetVisitorRaw[Value] = object 361 | SetVisitor[T] = SetVisitorRaw[set[T]] 362 | 363 | implVisitor(SetVisitor, public=true) 364 | 365 | proc expecting*(self: SetVisitor): string = "a set" 366 | 367 | proc expecting*[T](self: SetVisitor[T]): string = &"a set of `{$T}`" 368 | 369 | proc visitSeq*[T](self: SetVisitor[T], sequence: var auto): set[T] = 370 | for item in items[T](sequence): 371 | result.incl item 372 | 373 | 374 | proc deserialize*[T](Self: typedesc[set[T]], deserializer: var auto): Self = 375 | mixin 376 | deserializeSeq 377 | 378 | deserializer.deserializeSeq(SetVisitor[T]()) 379 | 380 | 381 | type 382 | OrderedSetVisitorRaw[Value] = object 383 | OrderedSetVisitor[T] = OrderedSetVisitorRaw[OrderedSet[T]] 384 | 385 | implVisitor(OrderedSetVisitor, public=true) 386 | 387 | proc expecting*(self: OrderedSetVisitor): string = "a set" 388 | 389 | proc expecting*[T](self: OrderedSetVisitor[T]): string = &"a set of `{$T}`" 390 | 391 | proc visitSeq*[T](self: OrderedSetVisitor[T], sequence: var auto): OrderedSet[T] = 392 | mixin sizeHint 393 | 394 | let size = sequence.sizeHint().get(64) 395 | 396 | result = initOrderedSet[T](size) 397 | 398 | for item in items[T](sequence): 399 | result.incl item 400 | 401 | 402 | proc deserialize*[T](Self: typedesc[OrderedSet[T]], deserializer: var auto): Self = 403 | mixin deserializeSeq 404 | 405 | deserializer.deserializeSeq(OrderedSetVisitor[T]()) 406 | 407 | 408 | type 409 | HashSetVisitorRaw[Value] = object 410 | HashSetVisitor[T] = HashSetVisitorRaw[HashSet[T]] 411 | 412 | implVisitor(HashSetVisitor, public=true) 413 | 414 | proc expecting*(self: HashSetVisitor): string = "a set" 415 | 416 | proc expecting*[T](self: HashSetVisitor[T]): string = &"a set of `{$T}`" 417 | 418 | proc visitSeq*[T](self: HashSetVisitor[T], sequence: var auto): HashSet[T] = 419 | mixin sizeHint 420 | 421 | let size = sequence.sizeHint().get(64) 422 | 423 | result = initHashSet[T](size) 424 | 425 | for item in items[T](sequence): 426 | result.incl item 427 | 428 | 429 | proc deserialize*[T](Self: typedesc[HashSet[T]], deserializer: var auto): Self = 430 | mixin deserializeSeq 431 | 432 | deserializer.deserializeSeq(HashSetVisitor[T]()) 433 | 434 | 435 | type 436 | TableVisitorRaw[Value] = object 437 | TableVisitor[Key, Value] = TableVisitorRaw[Table[Key, Value]] 438 | 439 | implVisitor(TableVisitor, public=true) 440 | 441 | proc expecting*(self: TableVisitor): string = "a table" 442 | 443 | proc expecting*[Key, Value](self: TableVisitor[Key, Value]): string = &"a table with type of key `{$Key}` and type of value `{$Value}`" 444 | 445 | proc visitMap*[Key, Value](self: TableVisitor[Key, Value], map: var auto): Table[Key, Value] = 446 | mixin pairs, sizeHint 447 | 448 | let size = map.sizeHint().get(32) 449 | 450 | result = initTable[Key, Value](size) 451 | 452 | for (key, value) in pairs[Key, Value](map): 453 | result[key] = value 454 | 455 | 456 | proc deserialize*[Key, Value](Self: typedesc[Table[Key, Value]], deserializer: var auto): Self = 457 | deserializer.deserializeMap(TableVisitor[Key, Value]()) 458 | 459 | 460 | type 461 | OrderedTableVisitorRaw[Value] = object 462 | OrderedTableVisitor[Key, Value] = OrderedTableVisitorRaw[OrderedTable[Key, Value]] 463 | 464 | implVisitor(OrderedTableVisitor, public=true) 465 | 466 | proc expecting*(self: OrderedTableVisitor): string = "a table" 467 | 468 | proc expecting*[Key, Value](self: OrderedTableVisitor[Key, Value]): string = &"a table with type of key `{$Key}` and type of value `{$Value}`" 469 | 470 | proc visitMap*[Key, Value](self: OrderedTableVisitor[Key, Value], map: var auto): OrderedTable[Key, Value] = 471 | mixin pairs, sizeHint 472 | 473 | let size = map.sizeHint().get(32) 474 | 475 | result = initOrderedTable[Key, Value](size) 476 | 477 | for (key, value) in pairs[Key, Value](map): 478 | result[key] = value 479 | 480 | 481 | proc deserialize*[Key, Value](Self: typedesc[OrderedTable[Key, Value]], deserializer: var auto): Self = 482 | deserializer.deserializeMap(OrderedTableVisitor[Key, Value]()) 483 | 484 | 485 | type 486 | RangeVisitorRaw[Value] = object 487 | RangeVisitor[T] = RangeVisitorRaw[T] 488 | 489 | implVisitor(RangeVisitor, public=true) 490 | 491 | proc expecting*(self: RangeVisitor): string = "a range" 492 | 493 | proc expecting*[T: range](self: RangeVisitor[T]): string = $T 494 | 495 | proc visitInt8*[T: range](self: RangeVisitor[T], value: int8): T = visitRangeIntBody() 496 | proc visitInt16*[T: range](self: RangeVisitor[T], value: int16): T = visitRangeIntBody() 497 | proc visitInt32*[T: range](self: RangeVisitor[T], value: int32): T = visitRangeIntBody() 498 | proc visitInt64*[T: range](self: RangeVisitor[T], value: int64): T = visitRangeIntBody() 499 | 500 | proc visitUint8*[T: range](self: RangeVisitor[T], value: uint8): T = visitRangeIntBody() 501 | proc visitUint16*[T: range](self: RangeVisitor[T], value: uint16): T = visitRangeIntBody() 502 | proc visitUint32*[T: range](self: RangeVisitor[T], value: uint32): T = visitRangeIntBody() 503 | proc visitUint64*[T: range](self: RangeVisitor[T], value: uint64): T = visitRangeIntBody() 504 | 505 | proc visitFloat32*[T](self: RangeVisitor[T], value: float32): T = visitRangeFloatBody() 506 | proc visitFloat64*[T](self: RangeVisitor[T], value: float64): T = visitRangeFloatBody() 507 | 508 | proc deserialize*(Self: typedesc[range], deserializer: var auto): Self = 509 | mixin 510 | deserializeInt8, 511 | deserializeInt16, 512 | deserializeInt32, 513 | deserializeInt64, 514 | deserializeUint8, 515 | deserializeUint16, 516 | deserializeUint32, 517 | deserializeUint64, 518 | deserializeFloat32, 519 | deserializeFloat64 520 | 521 | type UnderlyingType = rangeUnderlyingType(Self) 522 | 523 | when UnderlyingType is int8: 524 | deserializer.deserializeInt8(RangeVisitor[Self]()) 525 | elif UnderlyingType is int16: 526 | deserializer.deserializeInt16(RangeVisitor[Self]()) 527 | elif UnderlyingType is int32: 528 | deserializer.deserializeInt32(RangeVisitor[Self]()) 529 | elif UnderlyingType is int64: 530 | deserializer.deserializeInt64(RangeVisitor[Self]()) 531 | elif UnderlyingType is uint8: 532 | deserializer.deserializeUint8(RangeVisitor[Self]()) 533 | elif UnderlyingType is uint16: 534 | deserializer.deserializeUint16(RangeVisitor[Self]()) 535 | elif UnderlyingType is uint32: 536 | deserializer.deserializeUint32(RangeVisitor[Self]()) 537 | elif UnderlyingType is uint64: 538 | deserializer.deserializeUint64(RangeVisitor[Self]()) 539 | elif UnderlyingType is float32: 540 | deserializer.deserializeFloat32(RangeVisitor[Self]()) 541 | elif UnderlyingType is float64: 542 | deserializer.deserializeFloat64(RangeVisitor[Self]()) 543 | else: 544 | deserializer.deserializeInt64(RangeVisitor[Self]()) 545 | {.pop.} 546 | 547 | proc deserialize*(Self: typedesc[ref], deserializer: var auto): Self = 548 | mixin deserialize 549 | 550 | new result 551 | result[] = pointerBase(Self).deserialize(deserializer) 552 | 553 | when defined(release): 554 | {.pop.} 555 | -------------------------------------------------------------------------------- /src/deser/des/make.nim: -------------------------------------------------------------------------------- 1 | import std/[ 2 | macros 3 | ] 4 | 5 | from deser/macroutils/types import 6 | Struct, 7 | `duplicateCheck=` 8 | 9 | from deser/macroutils/parsing/struct import 10 | fromTypeSym 11 | 12 | from deser/macroutils/generation/des import 13 | defDeserialize 14 | 15 | 16 | macro makeDeserializable*( 17 | types: varargs[typedesc], 18 | public: static[bool] = false, 19 | duplicateCheck: static[bool] = true, 20 | debugOutput: static[bool] = false, 21 | debugTreeOutput: static[bool] = false 22 | ) = 23 | ##[ 24 | Generate `deserialize` procedure for your type. Use `public` parameter to export. 25 | 26 | Works only for objects and ref objects. 27 | 28 | Compile with `-d:debugMakeDeserializable` to see macro output. 29 | Compile with `-d:debugMakeDeserializableTree` to see macro output as NimNode tree. 30 | 31 | **Example**: 32 | ```nim 33 | makeDeserializable(Foo) 34 | 35 | # Use array of types if you want to make deserializable many types 36 | makeDeserializable([ 37 | Foo, 38 | Bar 39 | ]) 40 | ``` 41 | ]## 42 | result = newStmtList() 43 | 44 | for typeSym in types: 45 | var struct = Struct.fromTypeSym(typeSym) 46 | struct.duplicateCheck = duplicateCheck 47 | 48 | result.add defDeserialize(struct, public) 49 | 50 | if defined(debugMakeDeserializable) or debugOutput: 51 | debugEcho result.toStrLit 52 | 53 | if defined(debugMakeDeserializableTree) or debugTreeOutput: 54 | debugEcho result.treeRepr 55 | -------------------------------------------------------------------------------- /src/deser/errors.nim: -------------------------------------------------------------------------------- 1 | import deser/base_error 2 | 3 | import deser/des/errors 4 | 5 | export 6 | base_error, 7 | errors 8 | -------------------------------------------------------------------------------- /src/deser/helpers.nim: -------------------------------------------------------------------------------- 1 | import std/times 2 | 3 | import deser 4 | 5 | 6 | when defined(release): 7 | {.push inline.} 8 | 9 | type UnixTimeFormat* = object ##[ 10 | Deserialize and serialize [Time](https://nim-lang.org/docs/times.html#Time) as unix timestamp integer. 11 | 12 | **Example:** 13 | ```nim 14 | import deser 15 | 16 | type 17 | User = object 18 | created {.deserWith(UnixTimeFormat).}: Time 19 | 20 | makeSerializable(User) 21 | makeDeserializable(User) 22 | 23 | let user = User(created: fromUnix(123)) 24 | 25 | assert user == User.fromJson("""{"created": 123}""") 26 | assert user.toJson() == """{"created":123}""" 27 | ``` 28 | ]## 29 | 30 | proc deserialize*(self: typedesc[UnixTimeFormat], deserializer: var auto): Time = 31 | mixin deserialize 32 | 33 | fromUnix(deserialize(int64, deserializer)) 34 | 35 | proc serialize*(self: typedesc[UnixTimeFormat], field: Time, serializer: var auto) = 36 | mixin serialize 37 | 38 | serializer.serializeInt64(field.toUnix()) 39 | 40 | 41 | type DateTimeWith* = object ##[ 42 | Deserialize and serialize [DateTime](https://nim-lang.org/docs/times.html#DateTime) as string of your format. 43 | 44 | **Example:** 45 | ```nim 46 | type User = object 47 | created {.deserWith(DateTimeWith(format: "yyyy-MM-dd")).}: DateTime 48 | ``` 49 | ]## 50 | format*: string 51 | 52 | proc deserialize*(self: DateTimeWith, deserializer: var auto): DateTime = 53 | mixin deserialize 54 | 55 | parse(deserialize(string, deserializer), self.format) 56 | 57 | proc serialize*(self: DateTimeWith, field: DateTime, serializer: var auto) = 58 | mixin serialize 59 | 60 | serializer.serializeString(field.format(self.format)) 61 | 62 | when defined(release): 63 | {.pop.} 64 | -------------------------------------------------------------------------------- /src/deser/macroutils/anycase.nim: -------------------------------------------------------------------------------- 1 | #[ 2 | The MIT License (MIT) 3 | 4 | Copyright 2019 Konstantin Epishev 5 | Copyright (c) 2020 Nikita Gabbasov 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software is furnished to do so, 12 | subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | ]# 24 | 25 | from std/strutils import 26 | isUpperAscii, 27 | toLowerAscii, 28 | toUpperAscii, 29 | join, 30 | capitalizeAscii 31 | 32 | from std/sequtils import 33 | map, 34 | concat 35 | 36 | from deser/pragmas import 37 | RenameCase 38 | 39 | 40 | func words(str: string): seq[string] = 41 | var 42 | newStr = "" 43 | waitStringEnd = false 44 | 45 | for ch in str: 46 | if waitStringEnd and (ch.isUpperAscii or ch in {'_', '-'}): 47 | waitStringEnd = false 48 | 49 | result.add newStr 50 | newStr.setLen 0 51 | if ch.isUpperAscii: 52 | newStr.add ch.toLowerAscii 53 | 54 | elif ch.isUpperAscii: 55 | waitStringEnd = true 56 | newStr.add ch.toLowerAscii 57 | 58 | else: 59 | waitStringEnd = true 60 | newStr.add ch 61 | 62 | if newStr.len != 0: 63 | result.add newStr 64 | 65 | func camel(str: string): string = 66 | let 67 | parts = words(str) 68 | capitalizedParts = map(parts[1..parts.len - 1], capitalizeAscii) 69 | 70 | join(concat([parts[0..0], capitalizedParts])) 71 | 72 | func kebab(str: string): string = 73 | join(words(str), "-") 74 | 75 | func cobol(str: string): string = 76 | kebab(str).toUpperAscii() 77 | 78 | func pascal(str: string): string = 79 | let 80 | parts = words(str) 81 | capitalizedParts = map(parts, capitalizeAscii) 82 | 83 | join(capitalizedParts) 84 | 85 | func path(str: string): string = 86 | let parts = words(str) 87 | 88 | join(parts, "/") 89 | 90 | func plain(str: string): string = 91 | let parts = words(str) 92 | 93 | join(parts, " ") 94 | 95 | func train(str: string): string = 96 | let 97 | parts = words(str) 98 | capitalizedParts = map(parts, capitalizeAscii) 99 | 100 | join(capitalizedParts, "-") 101 | 102 | func snake(str: string): string = 103 | let parts = words(str) 104 | 105 | join(parts, "_") 106 | 107 | func upperSnake(str: string): string = 108 | snake(str).toUpperAscii() 109 | 110 | func toCase*(str: string, renameCase: RenameCase): string = 111 | case renameCase 112 | of CamelCase: 113 | str.camel 114 | of CobolCase: 115 | str.cobol 116 | of KebabCase: 117 | str.kebab 118 | of PascalCase: 119 | str.pascal 120 | of PathCase: 121 | str.path 122 | of PlainCase: 123 | str.plain 124 | of SnakeCase: 125 | str.snake 126 | of TrainCase: 127 | str.train 128 | of UpperSnakeCase: 129 | str.upperSnake 130 | -------------------------------------------------------------------------------- /src/deser/macroutils/generation/des.nim: -------------------------------------------------------------------------------- 1 | import std/[ 2 | macros 3 | ] 4 | 5 | import des/[ 6 | keys, 7 | values 8 | ] 9 | 10 | export 11 | keys, 12 | values 13 | 14 | from deser/macroutils/types import 15 | Struct 16 | 17 | 18 | func defDeserialize*(struct: Struct, public: bool): NimNode = 19 | let 20 | fieldVisitor = genSym(nskType, "FieldVisitor") 21 | valueVisitor = genSym(nskType, "Visitor") 22 | 23 | newStmtList( 24 | defKeyDeserialize(fieldVisitor, struct, public), 25 | defValueDeserialize(valueVisitor, struct, public), 26 | ) 27 | -------------------------------------------------------------------------------- /src/deser/macroutils/generation/des/keys.nim: -------------------------------------------------------------------------------- 1 | import std/[ 2 | macros, 3 | enumerate, 4 | options 5 | ] 6 | 7 | import deser/macroutils/matching 8 | 9 | from deser/macroutils/types import 10 | Struct, 11 | flattenFields, 12 | nskTypeEnumSym, 13 | nskEnumFieldUnknownSym, 14 | typeSym, 15 | # Field 16 | nskEnumFieldSym, 17 | deserializeName, 18 | # StructFeatures 19 | onUnknownKeys, 20 | # Field and Struct 21 | features 22 | 23 | from deser/macroutils/generation/utils import 24 | defMaybeExportedIdent, 25 | defPushPop 26 | 27 | from utils as des_utils import 28 | defImplVisitor, 29 | defExpectingProc, 30 | toByteArray 31 | 32 | 33 | # Forward declarations 34 | func defKeysEnum(struct: Struct): NimNode 35 | 36 | func defVisitorKeyType(visitorType, valueType: NimNode): NimNode 37 | 38 | func defVisitStringProc(selfType, returnType, body: NimNode): NimNode 39 | 40 | func defVisitUintProc(selfType, returnType, body: NimNode): NimNode 41 | 42 | func defVisitBytesProc(selfType, returnType, body: NimNode): NimNode 43 | 44 | func defDeserializeKeyProc(selfType, body: NimNode, public: bool): NimNode 45 | 46 | func defKeyDeserializeBody(visitorType: NimNode): NimNode 47 | 48 | func defStrToKeyCase(struct: Struct): NimNode 49 | 50 | func defBytesToKeyCase(struct: Struct): NimNode 51 | 52 | func defUintToKeyCase(struct: Struct): NimNode 53 | 54 | func defToKeyElseBranch(struct: Struct): NimNode 55 | 56 | 57 | func defKeyDeserialize*(visitorType: NimNode, struct: Struct, public: bool): NimNode = 58 | let 59 | keysEnum = defKeysEnum(struct) 60 | visitorTypeDef = defVisitorKeyType( 61 | visitorType, 62 | valueType=struct.nskTypeEnumSym 63 | ) 64 | visitorImpl = defImplVisitor( 65 | visitorType, 66 | public=public 67 | ) 68 | expectingProc = defExpectingProc( 69 | visitorType, 70 | body=newLit "field identifier" 71 | ) 72 | visitStringProc = defVisitStringProc( 73 | visitorType, 74 | returnType=struct.nskTypeEnumSym, 75 | body=defStrToKeyCase(struct) 76 | ) 77 | visitBytesProc = defVisitBytesProc( 78 | visitorType, 79 | returnType=struct.nskTypeEnumSym, 80 | body=defBytesToKeyCase(struct) 81 | ) 82 | visitUintProc = defVisitUintProc( 83 | visitorType, 84 | returnType=struct.nskTypeEnumSym, 85 | body=defUintToKeyCase(struct) 86 | ) 87 | deserializeProc = defDeserializeKeyProc( 88 | struct.nskTypeEnumSym, 89 | body=defKeyDeserializeBody(visitorType), 90 | public=public 91 | ) 92 | 93 | defPushPop: 94 | newStmtList( 95 | keysEnum, 96 | visitorTypeDef, 97 | visitorImpl, 98 | expectingProc, 99 | visitStringProc, 100 | visitBytesProc, 101 | visitUintProc, 102 | deserializeProc 103 | ) 104 | 105 | func defKeysEnum(struct: Struct): NimNode = 106 | #[ 107 | type Enum = enum 108 | FirstKey 109 | SecondKey 110 | ]# 111 | var enumNode = nnkEnumTy.newTree( 112 | newEmptyNode() 113 | ) 114 | 115 | for field in struct.flattenFields: 116 | enumNode.add field.nskEnumFieldSym 117 | 118 | enumNode.add struct.nskEnumFieldUnknownSym 119 | 120 | nnkTypeSection.newTree( 121 | nnkTypeDef.newTree( 122 | struct.nskTypeEnumSym, 123 | newEmptyNode(), 124 | enumNode 125 | ) 126 | ) 127 | 128 | func defVisitorKeyType(visitorType, valueType: NimNode): NimNode = 129 | quote do: 130 | type 131 | # special type to avoid specifying the generic `Value` every time 132 | HackType[Value] = object 133 | `visitorType` = HackType[`valueType`] 134 | 135 | func defVisitStringProc(selfType, returnType, body: NimNode): NimNode = 136 | let 137 | visitStringIdent = ident "visitString" 138 | selfIdent = ident "self" 139 | valueIdent = ident "value" 140 | 141 | quote do: 142 | proc `visitStringIdent`(`selfIdent`: `selfType`, `valueIdent`: string): `returnType` = 143 | `body` 144 | 145 | func defVisitBytesProc(selfType, returnType, body: NimNode): NimNode = 146 | let 147 | visitBytesIdent = ident "visitBytes" 148 | selfIdent = ident "self" 149 | valueIdent = ident "value" 150 | 151 | quote do: 152 | proc `visitBytesIdent`(`selfIdent`: `selfType`, `valueIdent`: openArray[byte]): `returnType` = 153 | `body` 154 | 155 | func defVisitUintProc(selfType, returnType, body: NimNode): NimNode = 156 | let 157 | visitUintIdent = ident "visitUint64" 158 | selfIdent = ident "self" 159 | valueIdent = ident "value" 160 | 161 | quote do: 162 | proc `visitUintIdent`(`selfIdent`: `selfType`, `valueIdent`: uint64): `returnType` = 163 | `body` 164 | 165 | func defDeserializeKeyProc(selfType, body: NimNode, public: bool): NimNode = 166 | let 167 | deserializeProcIdent = defMaybeExportedIdent(ident "deserialize", public) 168 | selfIdent = ident "selfType" 169 | deserializerIdent = ident "deserializer" 170 | 171 | quote do: 172 | proc `deserializeProcIdent`(`selfIdent`: typedesc[`selfType`], `deserializerIdent`: var auto): `selfIdent` = 173 | `body` 174 | 175 | func defKeyDeserializeBody(visitorType: NimNode): NimNode = 176 | let 177 | deserializeIdentifierIdent = ident "deserializeIdentifier" 178 | deserializerIdent = ident "deserializer" 179 | 180 | newStmtList( 181 | nnkMixinStmt.newTree(deserializeIdentifierIdent), 182 | newCall(deserializeIdentifierIdent, deserializerIdent, newCall(visitorType)) 183 | ) 184 | 185 | func defStrToKeyCase(struct: Struct): NimNode = 186 | #[ 187 | case value 188 | of "key": 189 | Enum.Key 190 | else: 191 | 192 | ]# 193 | result = nnkCaseStmt.newTree( 194 | newIdentNode("value") 195 | ) 196 | 197 | for field in struct.flattenFields: 198 | for name in field.deserializeName: 199 | result.add nnkOfBranch.newTree( 200 | newLit name, 201 | newStmtList( 202 | newDotExpr( 203 | struct.nskTypeEnumSym, 204 | field.nskEnumFieldSym 205 | ) 206 | ) 207 | ) 208 | 209 | result.add defToKeyElseBranch(struct) 210 | 211 | func defBytesToKeyCase(struct: Struct): NimNode = 212 | if struct.flattenFields.len == 0: 213 | # hardcode for empty objects 214 | # cause if statement with only `else` branch is nonsense 215 | result = newDotExpr(struct.nskTypeEnumSym, struct.nskEnumFieldUnknownSym) 216 | else: 217 | result = nnkIfStmt.newTree() 218 | 219 | for field in struct.flattenFields: 220 | for name in field.deserializeName: 221 | result.add nnkElifBranch.newTree( 222 | nnkInfix.newTree( 223 | ident "==", 224 | ident "value", 225 | newCall(bindSym "toByteArray", newLit name) 226 | ), 227 | newStmtList( 228 | newDotExpr( 229 | struct.nskTypeEnumSym, 230 | field.nskEnumFieldSym 231 | ) 232 | ) 233 | ) 234 | 235 | result.add defToKeyElseBranch(struct) 236 | 237 | func defUintToKeyCase(struct: Struct): NimNode = 238 | # HACK: https://github.com/nim-lang/Nim/issues/20031 239 | if struct.flattenFields.len == 0: 240 | # hardcode for empty objects 241 | # cause if statement with only `else` branch is nonsense 242 | result = newDotExpr(struct.nskTypeEnumSym, struct.nskEnumFieldUnknownSym) 243 | else: 244 | result = nnkIfStmt.newTree() 245 | 246 | for (num, field) in enumerate(struct.flattenFields): 247 | result.add nnkElifBranch.newTree( 248 | nnkInfix.newTree( 249 | ident "==", 250 | ident "value", 251 | newLit num 252 | ), 253 | newStmtList( 254 | newDotExpr( 255 | struct.nskTypeEnumSym, 256 | field.nskEnumFieldSym 257 | ) 258 | ) 259 | ) 260 | 261 | result.add defToKeyElseBranch(struct) 262 | 263 | func defToKeyElseBranch(struct: Struct): NimNode = 264 | let 265 | callOnUnknownKeys = block: 266 | if Some(@onUnknownKeys) ?= struct.features.onUnknownKeys: 267 | newCall( 268 | onUnknownKeys, 269 | toStrLit struct.typeSym, 270 | ident "value" 271 | ) 272 | else: 273 | newEmptyNode() 274 | 275 | nnkElse.newTree( 276 | newStmtList( 277 | callOnUnknownKeys, 278 | newDotExpr( 279 | struct.nskTypeEnumSym, 280 | struct.nskEnumFieldUnknownSym 281 | ) 282 | ) 283 | ) 284 | -------------------------------------------------------------------------------- /src/deser/macroutils/generation/des/utils.nim: -------------------------------------------------------------------------------- 1 | import std/[ 2 | macros, 3 | options 4 | ] 5 | 6 | from std/strutils import 7 | capitalizeAscii 8 | 9 | from deser/des/helpers import 10 | implVisitor 11 | 12 | from deser/des/errors import 13 | initUnexpectedSigned, 14 | initUnexpectedUnsigned, 15 | initUnexpectedString, 16 | initUnexpectedFloat, 17 | raiseInvalidValue, 18 | raiseMissingField 19 | 20 | 21 | func defImplVisitor*(selfType: NimNode, public: bool): NimNode = 22 | # implVisitor(selfType, returnType) 23 | let 24 | implVisitorSym = bindSym "implVisitor" 25 | public = newLit public 26 | 27 | quote do: 28 | `implVisitorSym`(`selfType`, public=`public`) 29 | 30 | func defExpectingProc*(selfType, body: NimNode): NimNode = 31 | let 32 | expectingIdent = ident "expecting" 33 | selfIdent = ident "self" 34 | 35 | quote do: 36 | proc `expectingIdent`(`selfIdent`: `selfType`): string = 37 | `body` 38 | 39 | func defFieldNamesLit*(names: seq[string]): NimNode = 40 | doAssert names.len > 0 41 | 42 | if names.len == 0: 43 | newLit names[0] 44 | else: 45 | var str = "" 46 | for num, name in names: 47 | str.add name 48 | if num != names.high: 49 | str.add "|" 50 | 51 | newLit str 52 | 53 | macro toByteArray*(str: static[string]): array = 54 | result = nnkBracket.newTree() 55 | 56 | for s in str: 57 | result.add newLit s.byte 58 | 59 | template getOrDefault*[T](field: Option[T]): T = 60 | bind 61 | isSome, 62 | unsafeGet 63 | 64 | if isSome(field): 65 | unsafeGet(field) 66 | else: 67 | # HACK: https://github.com/nim-lang/Nim/issues/20033 68 | default(typedesc[T]) 69 | 70 | template getOrDefaultValue*[T](field: Option[T], defaultValue: untyped): T = 71 | bind 72 | isSome, 73 | unsafeGet 74 | 75 | if isSome(field): 76 | unsafeGet(field) 77 | else: 78 | when compiles(defaultValue[T]()): 79 | defaultValue[T]() 80 | else: 81 | defaultValue 82 | 83 | template getOrRaise*[T](field: Option[T], name: static[string]): T = 84 | bind 85 | Option, 86 | isSome, 87 | unsafeGet, 88 | none, 89 | raiseMissingField 90 | 91 | if isSome(field): 92 | unsafeGet(field) 93 | else: 94 | when T is Option: 95 | # HACK: https://github.com/nim-lang/Nim/issues/20033 96 | default(typedesc[T]) 97 | else: 98 | raiseMissingField(name) 99 | 100 | template getOrBreak*[T](field: Option[T]): T = 101 | bind 102 | Option, 103 | isSome, 104 | unsafeGet, 105 | none 106 | 107 | if isSome(field): 108 | unsafeGet(field) 109 | else: 110 | when T is Option: 111 | # HACK: https://github.com/nim-lang/Nim/issues/20033 112 | default(typedesc[T]) 113 | else: 114 | break untaggedBlock 115 | 116 | macro genPrimitive*(typ: typed{`type`}, deserializeMethod: untyped = nil, floats: static[bool] = false) = 117 | result = newStmtList() 118 | var 119 | selfIdent = ident "self" 120 | valueIdent = ident "value" 121 | deserializerIdent = ident "deserializer" 122 | visitorType = genSym(nskType, "Visitor") 123 | deserializeMethodIdent = ( 124 | if deserializeMethod.kind != nnkNilLit: 125 | deserializeMethod 126 | else: 127 | ident "deserialize" & typ.strVal.capitalizeAscii 128 | ) 129 | typeStringLit = typ.toStrLit 130 | procs = @[ 131 | (ident "visitInt8", ident "int8"), 132 | (ident "visitInt16", ident "int16"), 133 | (ident "visitInt32", ident "int32"), 134 | (ident "visitInt64", ident "int64"), 135 | (ident "visitUint8", ident "uint8"), 136 | (ident "visitUint16", ident "uint16"), 137 | (ident "visitUint32", ident "uint32"), 138 | (ident "visitUint64", ident "uint64"), 139 | ] 140 | 141 | body = quote do: 142 | when type(value) is self.Value: 143 | value 144 | else: 145 | when self.Value is SomeUnsignedInt: 146 | when value is SomeSignedInt: 147 | if not (0 <= value and value.uint64 <= self.Value.high.uint64): 148 | raiseInvalidValue(initUnexpectedSigned(value.int64), self) 149 | elif value is SomeUnsignedInt: 150 | if not (value.uint64 <= self.Value.high.uint64): 151 | raiseInvalidValue(initUnexpectedUnsigned(value.uint64), self) 152 | else: 153 | {.error: "Unknown type `" & $self.Value & "`, expected int or uint".} 154 | elif self.Value is SomeSignedInt: 155 | when value is SomeSignedInt: 156 | if not (self.Value.low.int64 <= value.int64 and value.int64 <= self.Value.high.int64): 157 | raiseInvalidValue(initUnexpectedSigned(value.int64), self) 158 | elif value is SomeUnsignedInt: 159 | if not (value.uint64 <= self.Value.high.uint64): 160 | raiseInvalidValue(initUnexpectedUnsigned(value.uint64), self) 161 | else: 162 | {.error: "Unknown type `" & $self.Value() & "`, expected int or uint".} 163 | 164 | self.Value(value) 165 | 166 | result.add quote do: 167 | type HackType[Value] = object 168 | type `visitorType` = HackType[`typ`] 169 | implVisitor(`visitorType`, true) 170 | 171 | proc expecting*(`selfIdent`: `visitorType`): string = `typeStringLit` 172 | 173 | proc deserialize*(`selfIdent`: typedesc[`typ`], `deserializerIdent`: var auto): `typ` = 174 | mixin `deserializeMethodIdent` 175 | 176 | deserializer.`deserializeMethodIdent`(`visitorType`()) 177 | 178 | if floats: 179 | procs.add @[ 180 | (ident "visitFloat32", ident "float32"), 181 | (ident "visitFloat64", ident "float64") 182 | ] 183 | 184 | for (procIdent, valueType) in procs: 185 | result.add quote do: 186 | proc `procIdent`*(`selfIdent`: `visitorType`, `valueIdent`: `valueType`): self.Value = 187 | `body` 188 | 189 | 190 | macro genArray*(size: static[int], T: typedesc): array = 191 | # [get(nextElement[T](sequence)), ...] 192 | result = nnkBracket.newTree() 193 | 194 | for i in 0..size: 195 | result.add newCall( 196 | bindSym "get", 197 | newCall( 198 | nnkBracketExpr.newTree( 199 | ident "nextElement", 200 | T 201 | ), 202 | ident "sequence" 203 | ) 204 | ) 205 | 206 | 207 | template visitEnumIntBody* {.dirty.} = 208 | bind 209 | raiseInvalidValue, 210 | initUnexpectedSigned, 211 | initUnexpectedUnsigned 212 | 213 | if value.int64 in T.low.int64..T.high.int64: 214 | value.T 215 | else: 216 | when value is SomeSignedInt: 217 | raiseInvalidValue(initUnexpectedSigned(value), self) 218 | else: 219 | raiseInvalidValue(initUnexpectedUnsigned(value), self) 220 | 221 | 222 | template visitRangeIntBody* {.dirty.} = 223 | bind visitEnumIntBody 224 | 225 | visitEnumIntBody() 226 | 227 | 228 | template visitRangeFloatBody* {.dirty.} = 229 | bind 230 | raiseInvalidValue, 231 | initUnexpectedFloat 232 | 233 | if value.float64 in T.low.float64..T.high.float64: 234 | value.T 235 | else: 236 | raiseInvalidValue(initUnexpectedFloat(value.float64), self) 237 | 238 | 239 | # https://github.com/GULPF/samson/blob/71ead61104302abfc0d4463c91f73a4126b2184c/src/samson/private/xtypetraits.nim#L29 240 | macro rangeUnderlyingType*(typ: typedesc[range]): typedesc = 241 | result = getType(getType(typ)[1][1]) 242 | -------------------------------------------------------------------------------- /src/deser/macroutils/generation/des/values.nim: -------------------------------------------------------------------------------- 1 | import std/[ 2 | macros, 3 | options 4 | ] 5 | 6 | import deser/macroutils/matching 7 | 8 | from deser/des/errors import 9 | raiseDuplicateField, 10 | raiseUnknownUntaggedVariant, 11 | raiseMissingField 12 | 13 | from deser/des/helpers import 14 | IgnoredAny 15 | 16 | from deser/macroutils/types import 17 | Struct, 18 | flattenFields, 19 | nskTypeEnumSym, 20 | nskEnumFieldUnknownSym, 21 | typeSym, 22 | genericParams, 23 | fields, 24 | Field, 25 | isCase, 26 | branches, 27 | nskEnumFieldSym, 28 | deserializeName, 29 | nameIdent, 30 | nskTypeDeserializeWithSym, 31 | duplicateCheck, 32 | # FieldFeatures 33 | deserializeWith, 34 | skipDeserializing, 35 | defaultValue, 36 | untagged, 37 | deserWith, 38 | # Field and Struct 39 | features, 40 | # FieldBranch 41 | kind, 42 | conditionOfBranch, 43 | FieldBranchKind 44 | 45 | from utils as des_utils import 46 | defImplVisitor, 47 | defExpectingProc, 48 | defFieldNamesLit, 49 | getOrDefault, 50 | getOrDefaultValue, 51 | getOrRaise, 52 | getOrBreak 53 | 54 | from deser/macroutils/generation/utils import 55 | defWithType, 56 | defMaybeExportedIdent, 57 | defPushPop 58 | 59 | # Forward declarations 60 | func defVisitorValueType(struct: Struct, visitorType, valueType: NimNode, public: bool): NimNode 61 | 62 | func defDeserializeWithType(struct: Struct, public: bool): NimNode 63 | 64 | func defWithGenerics(someType: NimNode, genericParams: NimNode): NimNode 65 | 66 | func defVisitSeqProc(struct: Struct, visitorType, body: NimNode): NimNode 67 | 68 | func defFieldLets(struct: Struct): NimNode 69 | 70 | func defFieldType(struct: Struct, field: Field): NimNode 71 | 72 | func defInitResolver(struct: Struct): NimNode 73 | 74 | func defStructType(struct: Struct): NimNode 75 | 76 | func resolve(struct: Struct, fields: seq[Field], objConstr: NimNode, raiseOnNone = true): NimNode 77 | 78 | func addToObjConstr(objConstr, ident, value: NimNode) 79 | 80 | func defGetField(field: Field, raiseOnNone: bool): NimNode 81 | 82 | func defGetOrDefault(fieldIdent: NimNode): NimNode 83 | 84 | func defGetOrDefaultValue(fieldIdent: NimNode, defaultValue: NimNode): NimNode 85 | 86 | func defGetOrRaise(fieldIdent: NimNode, fieldName: NimNode): NimNode 87 | 88 | func defGetOrBreak(fieldIdent: NimNode): NimNode 89 | 90 | func defVisitMapProc(struct: Struct, visitorType, body: NimNode): NimNode 91 | 92 | func defOptionFieldVars(struct: Struct): NimNode 93 | 94 | func defForKeys(keyType, body: NimNode): NimNode 95 | 96 | func defKeyToValueCase(struct: Struct): NimNode 97 | 98 | func defDeserializeValueProc(struct: Struct, body: NimNode, public: bool): NimNode 99 | 100 | func defValueDeserializeBody(struct: Struct, visitorType: NimNode): NimNode 101 | 102 | func defValueDeserialize*(visitorType: NimNode, struct: Struct, public: bool): NimNode = 103 | let 104 | visitorTypeDef = defVisitorValueType( 105 | struct, 106 | visitorType, 107 | valueType=struct.typeSym, 108 | public=public 109 | ) 110 | visitorImpl = defImplVisitor( 111 | visitorType, 112 | public=public 113 | ) 114 | expectingProc = defExpectingProc( 115 | visitorType, 116 | body=newLit "struct " & '`' & struct.typeSym.strVal & '`' 117 | ) 118 | visitSeqProc = defVisitSeqProc( 119 | struct, 120 | visitorType, 121 | body=newStmtList( 122 | nnkMixinStmt.newTree(ident "nextElement"), 123 | defFieldLets(struct), 124 | defInitResolver(struct) 125 | ) 126 | ) 127 | visitMapProc = defVisitMapProc( 128 | struct, 129 | visitorType, 130 | body=newStmtList( 131 | nnkMixinStmt.newTree(ident "keys"), 132 | nnkMixinStmt.newTree(ident "nextValue"), 133 | defOptionFieldVars(struct), 134 | defForKeys(struct.nskTypeEnumSym, defKeyToValueCase(struct)), 135 | defInitResolver(struct) 136 | ) 137 | ) 138 | deserializeProc = defDeserializeValueProc( 139 | struct, 140 | body=defValueDeserializeBody(struct, visitorType), 141 | public=public 142 | ) 143 | 144 | defPushPop: 145 | newStmtList( 146 | visitorTypeDef, 147 | visitorImpl, 148 | expectingProc, 149 | visitSeqProc, 150 | visitMapProc, 151 | deserializeProc 152 | ) 153 | 154 | func defVisitorValueType(struct: Struct, visitorType, valueType: NimNode, public: bool): NimNode = 155 | result = newStmtList() 156 | var generics, valueTyp: NimNode 157 | 158 | if Some(@genericParams) ?= struct.genericParams: 159 | valueTyp = defWithGenerics(valueType, genericParams) 160 | generics = genericParams 161 | else: 162 | generics = newEmptyNode() 163 | valueTyp = valueType 164 | 165 | let hackType = genSym(nskType, "HackType") 166 | 167 | result.add defDeserializeWithType(struct, public) 168 | 169 | result.add nnkTypeSection.newTree( 170 | nnkTypeDef.newTree( 171 | hackType, 172 | nnkGenericParams.newTree( 173 | nnkIdentDefs.newTree( 174 | ident "Value", 175 | newEmptyNode(), 176 | newEmptyNode() 177 | ) 178 | ), 179 | nnkObjectTy.newTree( 180 | newEmptyNode(), 181 | newEmptyNode(), 182 | newEmptyNode() 183 | ) 184 | ), 185 | nnkTypeDef.newTree( 186 | visitorType, 187 | generics, 188 | nnkBracketExpr.newTree( 189 | hackType, 190 | valueTyp 191 | ) 192 | ) 193 | ) 194 | 195 | func defDeserializeWithType(struct: Struct, public: bool): NimNode = 196 | result = newStmtList() 197 | 198 | for field in struct.flattenFields: 199 | if field.features.deserializeWith.isSome or field.features.deserWith.isSome: 200 | let 201 | typ = field.nskTypeDeserializeWithSym 202 | deserializeIdent = defMaybeExportedIdent(ident "deserialize", public) 203 | deserializerIdent = ident "deserializer" 204 | value = 205 | if Some(@deserializeWith) ?= field.features.deserializeWith: 206 | newCall(deserializeWith, deserializerIdent) 207 | elif Some(@deserWith) ?= field.features.deserWith: 208 | newCall(ident "deserialize", deserWith, deserializerIdent) 209 | else: 210 | newEmptyNode() 211 | genericValue = block: 212 | let tmp = copy value 213 | tmp[0] = nnkBracketExpr.newTree(tmp[0], ident "T") 214 | tmp 215 | 216 | result.add defWithType(typ) 217 | 218 | result.add quote do: 219 | proc `deserializeIdent`[T](selfTy: typedesc[`typ`[T]], `deserializerIdent`: var auto): selfTy {.inline.} = 220 | mixin deserialize 221 | 222 | when compiles(`genericValue`): 223 | selfTy(value: `genericValue`) 224 | else: 225 | selfTy(value: `value`) 226 | 227 | func defWithGenerics(someType: NimNode, genericParams: NimNode): NimNode = 228 | assertKind someType, {nnkIdent, nnkSym} 229 | assertKind genericParams, {nnkGenericParams} 230 | 231 | result = nnkBracketExpr.newTree(someType) 232 | 233 | for param in genericParams: 234 | # HACK: https://github.com/nim-lang/Nim/issues/19670 235 | result.add ident param.strVal 236 | 237 | func defVisitSeqProc(struct: Struct, visitorType, body: NimNode): NimNode = 238 | var generics, returnType, visitorTyp: NimNode 239 | 240 | if Some(@genericParams) ?= struct.genericParams: 241 | returnType = defWithGenerics(struct.typeSym, genericParams) 242 | visitorTyp = defWithGenerics(visitorType, genericParams) 243 | generics = genericParams 244 | else: 245 | generics = newEmptyNode() 246 | returnType = struct.typeSym 247 | visitorTyp = visitorType 248 | 249 | result = nnkProcDef.newTree( 250 | ident "visitSeq", 251 | newEmptyNode(), 252 | generics, 253 | nnkFormalParams.newTree( 254 | returnType, 255 | nnkIdentDefs.newTree( 256 | ident "self", 257 | visitorTyp, 258 | newEmptyNode() 259 | ), 260 | nnkIdentDefs.newTree( 261 | ident "sequence", 262 | nnkVarTy.newTree( 263 | newIdentNode("auto") 264 | ), 265 | newEmptyNode() 266 | ) 267 | ), 268 | newEmptyNode(), 269 | newEmptyNode(), 270 | nnkStmtList.newTree( 271 | body 272 | ) 273 | ) 274 | 275 | func defFieldLets(struct: Struct): NimNode = 276 | # let someField = nextElement[FieldType](sequence) 277 | result = newStmtList() 278 | 279 | for field in struct.flattenFields: 280 | let 281 | genericTypeArgument = 282 | if field.features.deserializeWith.isSome or field.features.deserWith.isSome: 283 | let 284 | withType = field.nskTypeDeserializeWithSym 285 | originType = defFieldType(struct, field) 286 | 287 | nnkBracketExpr.newTree( 288 | withType, 289 | originType 290 | ) 291 | else: 292 | defFieldType(struct, field) 293 | 294 | sequenceIdent = ident "sequence" 295 | 296 | 297 | var nextElementCall = quote do: 298 | nextElement[`genericTypeArgument`](`sequenceIdent`) 299 | 300 | if field.features.deserializeWith.isSome or field.features.deserWith.isSome: 301 | # nextElement returns Option[T] 302 | nextElementCall = quote do: 303 | block: 304 | let tempNextElement = `nextElementCall` 305 | if isSome(tempNextElement): 306 | some(unsafeGet(tempNextElement).value) 307 | else: 308 | none(`genericTypeArgument`.T) 309 | 310 | result.add newLetStmt( 311 | field.nameIdent, 312 | nextElementCall 313 | ) 314 | 315 | func defFieldType(struct: Struct, field: Field): NimNode = 316 | newCall( 317 | ident "typeof", 318 | newDotExpr( 319 | ident "result", 320 | field.nameIdent 321 | ) 322 | ) 323 | 324 | func defInitResolver(struct: Struct): NimNode = 325 | let objConstr = nnkObjConstr.newTree(defStructType(struct)) 326 | 327 | resolve(struct, struct.fields, objConstr) 328 | 329 | func defStructType(struct: Struct): NimNode = 330 | if Some(@genericParams) ?= struct.genericParams: 331 | defWithGenerics(struct.typeSym, genericParams) 332 | else: 333 | struct.typeSym 334 | 335 | template resolveUntagged {.dirty.} = 336 | var body = newStmtList() 337 | 338 | for variant in field.branches: 339 | case variant.kind 340 | of Of: 341 | # every variant has diferent `kind field` value 342 | # so we need diferent object constructors 343 | var objConstr = copy objConstr 344 | #[ 345 | case kind: bool 346 | of --> true <-- variant.condition[0] 347 | ]# 348 | addToObjConstr(objConstr, field.nameIdent, variant.conditionOfBranch[0]) 349 | # on untagged case we need to try all variants 350 | # so, we recursively call the resolver for each variant. 351 | # if an error occurs during deserialization, for example, there is no required field 352 | # we should exit (break) the block and try to deserialize another variant, and not throw an exception 353 | let variantBody = resolve(struct, variant.fields, objConstr, raiseOnNone=false) 354 | body.add newBlockStmt(ident "untaggedBlock", variantBody) 355 | of Else: 356 | # since the resolver takes the value from the `Of` condition 357 | # we cannot guess the value in the `Else` branch 358 | error("untagged cases cannot have `else` branch", field.nameIdent) 359 | 360 | # exception is raised only for the top level case field 361 | if raiseOnNone: 362 | let fieldNameLit = newLit field.nameIdent.strVal 363 | body.add quote do: 364 | `raiseUnknownUntaggedVariantSym`(`structNameLit`, `fieldNameLit`) 365 | result = body 366 | 367 | template resolveTagged {.dirty.} = 368 | # HACK: need to generate temp let 369 | # to prove that case field value is correct 370 | let 371 | tempKindLetSym = genSym(nskLet, field.nameIdent.strVal) 372 | # get case field value from data 373 | tempKindLet = newLetStmt(tempKindLetSym, defGetField(field, raiseOnNone)) 374 | 375 | addToObjConstr(objConstr, field.nameIdent, tempKindLetSym) 376 | var caseStmt = nnkCaseStmt.newTree(tempKindLetSym) 377 | 378 | for variant in field.branches: 379 | case variant.kind 380 | of Of: 381 | #[ 382 | type Foo = object 383 | case kind: bool 384 | of true: 385 | ... 386 | else: 387 | ... 388 | | 389 | | 390 | | 391 | V 392 | of true <--- condition 393 | ]# 394 | var condition = copy variant.conditionOfBranch 395 | let variantBody = resolve(struct, variant.fields, objConstr, raiseOnNone) 396 | condition.add variantBody 397 | caseStmt.add condition 398 | of Else: 399 | let variantBody = resolve(struct, variant.fields, objConstr, raiseOnNone) 400 | caseStmt.add nnkElse.newTree(variantBody) 401 | 402 | result = newStmtList( 403 | tempKindLet, 404 | caseStmt 405 | ) 406 | 407 | func resolve(struct: Struct, fields: seq[Field], objConstr: NimNode, raiseOnNone = true): NimNode = 408 | let 409 | raiseUnknownUntaggedVariantSym = bindSym "raiseUnknownUntaggedVariant" 410 | structNameLit = struct.typeSym.toStrLit 411 | 412 | var 413 | objConstr = copy objConstr 414 | caseField = none Field 415 | 416 | for field in fields: 417 | if not field.features.skipDeserializing: 418 | if field.isCase: 419 | if caseField.isSome: 420 | # Must not be raised 421 | error("Object cannot contain more than one `case` expression at the same level", field.nameIdent) 422 | caseField = some field 423 | else: 424 | addToObjConstr(objConstr, field.nameIdent, defGetField(field, raiseOnNone)) 425 | 426 | if caseField.isNone: 427 | # there is no case field, so just return statement 428 | result = nnkReturnStmt.newTree(objConstr) 429 | else: 430 | let field = caseField.unsafeGet 431 | 432 | if field.features.untagged: 433 | resolveUntagged 434 | else: 435 | resolveTagged 436 | 437 | func addToObjConstr(objConstr, ident, value: NimNode) = 438 | # (ident: value) 439 | objConstr.add newColonExpr(ident, value) 440 | 441 | func defGetField(field: Field, raiseOnNone: bool): NimNode = 442 | if Some(@defaultValueNode) ?= field.features.defaultValue: 443 | if defaultValueNode.kind == nnkEmpty: 444 | defGetOrDefault(field.nameIdent) 445 | else: 446 | defGetOrDefaultValue(field.nameIdent, defaultValueNode) 447 | elif raiseOnNone: 448 | defGetOrRaise(field.nameIdent, defFieldNamesLit(field.deserializeName)) 449 | else: 450 | defGetOrBreak(field.nameIdent) 451 | 452 | func defGetOrDefault(fieldIdent: NimNode): NimNode = 453 | newCall(bindSym "getOrDefault", fieldIdent) 454 | 455 | func defGetOrDefaultValue(fieldIdent: NimNode, defaultValue: NimNode): NimNode = 456 | newCall(bindSym "getOrDefaultValue", fieldIdent, defaultValue) 457 | 458 | func defGetOrRaise(fieldIdent: NimNode, fieldName: NimNode): NimNode = 459 | newCall(bindSym "getOrRaise", fieldIdent, fieldName) 460 | 461 | func defGetOrBreak(fieldIdent: NimNode): NimNode = 462 | newCall(bindSym "getOrBreak", fieldIdent) 463 | 464 | func defVisitMapProc(struct: Struct, visitorType, body: NimNode): NimNode = 465 | var generics, returnType, visitorTyp: NimNode 466 | 467 | if struct.genericParams.isSome: 468 | generics = struct.genericParams.unsafeGet 469 | returnType = defWithGenerics(struct.typeSym, struct.genericParams.unsafeGet) 470 | visitorTyp = defWithGenerics(visitorType, struct.genericParams.unsafeGet) 471 | else: 472 | generics = newEmptyNode() 473 | returnType = struct.typeSym 474 | visitorTyp = visitorType 475 | 476 | result = nnkProcDef.newTree( 477 | ident "visitMap", 478 | newEmptyNode(), 479 | generics, 480 | nnkFormalParams.newTree( 481 | returnType, 482 | nnkIdentDefs.newTree( 483 | ident "self", 484 | visitorTyp, 485 | newEmptyNode() 486 | ), 487 | nnkIdentDefs.newTree( 488 | ident "map", 489 | nnkVarTy.newTree( 490 | newIdentNode("auto") 491 | ), 492 | newEmptyNode() 493 | ) 494 | ), 495 | newEmptyNode(), 496 | newEmptyNode(), 497 | nnkStmtList.newTree( 498 | body 499 | ) 500 | ) 501 | 502 | func defOptionFieldVars(struct: Struct): NimNode = 503 | # var fieldName = none(FieldType) 504 | result = newStmtList() 505 | 506 | for field in struct.flattenFields: 507 | result.add newVarStmt( 508 | field.nameIdent, 509 | newCall( 510 | nnkBracketExpr.newTree( 511 | bindSym("none"), 512 | defFieldType(struct, field) 513 | ) 514 | ) 515 | ) 516 | 517 | func defForKeys(keyType, body: NimNode): NimNode = 518 | #[ 519 | for key in keys[keyType](map): 520 | body 521 | ]# 522 | nnkForStmt.newTree( 523 | ident "key", 524 | newCall( 525 | nnkBracketExpr.newTree( 526 | ident "keys", 527 | keyType 528 | ), 529 | ident "map" 530 | ), 531 | body 532 | ) 533 | 534 | func defKeyToValueCase(struct: Struct): NimNode = 535 | #[ 536 | case key 537 | of FirstField: 538 | firstField = some nextValue[FirstFieldType](map) 539 | ... 540 | else: 541 | nextValue[IgnoredAny](map) 542 | ]# 543 | result = nnkCaseStmt.newTree( 544 | ident "key" 545 | ) 546 | 547 | for field in struct.flattenFields: 548 | let genericTypeArgument = 549 | if field.features.deserializeWith.isSome or field.features.deserWith.isSome: 550 | let 551 | withType = field.nskTypeDeserializeWithSym 552 | originType = defFieldType(struct, field) 553 | nnkBracketExpr.newTree( 554 | withType, 555 | originType 556 | ) 557 | else: 558 | defFieldType(struct, field) 559 | 560 | var nextValueCall = 561 | newCall( 562 | nnkBracketExpr.newTree( 563 | ident "nextValue", 564 | genericTypeArgument 565 | ), 566 | ident "map", 567 | ) 568 | 569 | if field.features.deserializeWith.isSome or field.features.deserWith.isSome: 570 | nextValueCall = newDotExpr(nextValueCall, ident "value") 571 | 572 | let duplicateCheck = 573 | if struct.duplicateCheck: 574 | nnkIfStmt.newTree( 575 | nnkElifBranch.newTree( 576 | newCall(bindSym("isSome"), field.nameIdent), 577 | newStmtList( 578 | newCall(bindSym("raiseDuplicateField"), defFieldNamesLit(field.deserializeName)) 579 | ) 580 | ) 581 | ) 582 | else: 583 | newEmptyNode() 584 | 585 | result.add nnkOfBranch.newTree( 586 | newDotExpr(struct.nskTypeEnumSym, field.nskEnumFieldSym), 587 | newStmtList( 588 | duplicateCheck, 589 | newAssignment( 590 | field.nameIdent, 591 | newCall( 592 | bindSym("some"), 593 | nextValueCall 594 | ) 595 | ) 596 | ) 597 | ) 598 | 599 | result.add nnkElse.newTree( 600 | nnkDiscardStmt.newTree( 601 | newCall( 602 | nnkBracketExpr.newTree( 603 | ident "nextValue", 604 | bindSym "IgnoredAny" 605 | ), 606 | ident "map" 607 | ) 608 | ) 609 | ) 610 | 611 | func defDeserializeValueProc(struct: Struct, body: NimNode, public: bool): NimNode = 612 | let 613 | deserializeProcIdent = defMaybeExportedIdent(ident "deserialize", public) 614 | selfType = 615 | if Some(@genericParams) ?= struct.genericParams: 616 | defwithGenerics(struct.typeSym, genericParams) 617 | else: 618 | struct.typeSym 619 | 620 | nnkProcDef.newTree( 621 | deserializeProcIdent, 622 | newEmptyNode(), 623 | struct.genericParams.get(newEmptyNode()), 624 | nnkFormalParams.newTree( 625 | ident "Self", 626 | nnkIdentDefs.newTree( 627 | ident "Self", 628 | nnkBracketExpr.newTree( 629 | ident "typedesc", 630 | selfType 631 | ), 632 | newEmptyNode() 633 | ), 634 | nnkIdentDefs.newTree( 635 | ident "deserializer", 636 | nnkVarTy.newTree( 637 | newIdentNode("auto") 638 | ), 639 | newEmptyNode() 640 | ) 641 | ), 642 | newEmptyNode(), 643 | newEmptyNode(), 644 | nnkStmtList.newTree( 645 | body 646 | ) 647 | ) 648 | 649 | func defValueDeserializeBody(struct: Struct, visitorType: NimNode): NimNode = 650 | let visitorType = ( 651 | if Some(@genericParams) ?= struct.genericParams: 652 | defWithGenerics(visitorType, genericParams) 653 | else: 654 | visitorType 655 | ) 656 | 657 | nnkStmtList.newTree( 658 | nnkMixinStmt.newTree( 659 | newIdentNode("deserializeMap") 660 | ), 661 | nnkCall.newTree( 662 | newIdentNode("deserializeMap"), 663 | newIdentNode("deserializer"), 664 | nnkCall.newTree( 665 | visitorType 666 | ) 667 | ) 668 | ) 669 | -------------------------------------------------------------------------------- /src/deser/macroutils/generation/ser.nim: -------------------------------------------------------------------------------- 1 | import ser/ser 2 | 3 | export ser 4 | -------------------------------------------------------------------------------- /src/deser/macroutils/generation/ser/ser.nim: -------------------------------------------------------------------------------- 1 | import std/[ 2 | macros, 3 | options 4 | ] 5 | 6 | import deser/macroutils/matching 7 | 8 | from deser/ser/helpers import 9 | asAddr 10 | 11 | from deser/macroutils/types import 12 | Struct, 13 | flattenFields, 14 | typeSym, 15 | fields, 16 | Field, 17 | typeNode, 18 | features, 19 | isCase, 20 | branches, 21 | nskTypeSerializeWithSym, 22 | serializeName, 23 | nameIdent, 24 | # FieldFeatures 25 | serializeWith, 26 | skipSerializing, 27 | skipSerializeIf, 28 | untagged, 29 | deserWith, 30 | # FieldBranch 31 | kind, 32 | conditionOfBranch, 33 | FieldBranchKind 34 | 35 | from deser/macroutils/generation/utils import 36 | defWithType, 37 | defMaybeExportedIdent, 38 | defPushPop 39 | 40 | 41 | # Forward declarations 42 | func defSerializeWith(struct: Struct, public: bool): NimNode 43 | 44 | func defSerializeProc(struct: Struct, body: NimNode, public: bool): NimNode 45 | 46 | func defSerializeBody(struct: Struct): NimNode 47 | 48 | func defState(): NimNode 49 | 50 | func defSerializeFields(fields: seq[Field]): NimNode 51 | 52 | func defSerializeField(field: Field, checkSkipIf: bool): NimNode 53 | 54 | func defSerializeCaseField(field: Field): NimNode 55 | 56 | func defEndMap(): NimNode 57 | 58 | func defSelfDotField(field: Field): NimNode 59 | 60 | func defCheckedSerializeField(field: Field, checker, body: NimNode): NimNode 61 | 62 | func defNilCheck(): NimNode 63 | 64 | 65 | func defSerialize*(struct: Struct, public: bool): NimNode = 66 | defPushPop: 67 | newStmtList( 68 | defSerializeWith( 69 | struct, 70 | public=public 71 | ), 72 | defSerializeProc( 73 | struct, 74 | body=defSerializeBody(struct), 75 | public=public 76 | ) 77 | ) 78 | 79 | func defSerializeWith(struct: Struct, public: bool): NimNode = 80 | result = newStmtList() 81 | 82 | for field in struct.flattenFields: 83 | if field.features.serializeWith.isSome or field.features.deserWith.isSome: 84 | let 85 | typ = field.nskTypeSerializeWithSym 86 | serializeIdent = defMaybeExportedIdent(ident "serialize", public) 87 | selfIdent = ident "self" 88 | serializerIdent = ident "serializer" 89 | serializeWithBody = 90 | if Some(@serializeWith) ?= field.features.serializeWith: 91 | newCall(serializeWith, newDotExpr(ident "self", ident "value"), ident "serializer") 92 | elif Some(@deserWith) ?= field.features.deserWith: 93 | newCall(ident "serialize", deserWith, newDotExpr(ident "self", ident "value"), ident "serializer") 94 | else: 95 | doAssert false 96 | newEmptyNode() 97 | 98 | result.add defWithType(typ) 99 | 100 | result.add quote do: 101 | proc `serializeIdent`[T](`selfIdent`: `typ`[T], `serializerIdent`: var auto) {.inline.} = 102 | `serializeWithBody` 103 | 104 | func defSerializeProc(struct: Struct, body: NimNode, public: bool): NimNode = 105 | let procName = defMaybeExportedIdent(ident "serialize", public) 106 | 107 | nnkProcDef.newTree( 108 | procName, 109 | newEmptyNode(), 110 | newEmptyNode(), 111 | nnkFormalParams.newTree( 112 | newEmptyNode(), 113 | nnkIdentDefs.newTree( 114 | newIdentNode("self"), 115 | struct.typeSym, 116 | newEmptyNode() 117 | ), 118 | nnkIdentDefs.newTree( 119 | newIdentNode("serializer"), 120 | nnkVarTy.newTree( 121 | newIdentNode("auto") 122 | ), 123 | newEmptyNode() 124 | ) 125 | ), 126 | newEmptyNode(), 127 | newEmptyNode(), 128 | body 129 | ) 130 | 131 | func defSerializeBody(struct: Struct): NimNode = 132 | newStmtList( 133 | nnkMixinStmt.newTree( 134 | ident "serializeMap" 135 | ), 136 | nnkMixinStmt.newTree( 137 | ident "serializeMapEntry" 138 | ), 139 | nnkMixinStmt.newTree( 140 | ident "endMap" 141 | ), 142 | defNilCheck(), 143 | defState(), 144 | defSerializeFields(struct.fields), 145 | defEndMap() 146 | ) 147 | 148 | func defNilCheck(): NimNode = 149 | let 150 | selfIdent = ident "self" 151 | serializerIdent = ident "serializer" 152 | serializeNoneIdent = ident "serializeNone" 153 | 154 | quote do: 155 | when `selfIdent` is ref: 156 | if `selfIdent`.isNil: 157 | `serializeNoneIdent`(`serializerIdent`) 158 | return 159 | 160 | func defState(): NimNode = 161 | newCall( 162 | bindSym "asAddr", 163 | ident "state", 164 | nnkCall.newTree( 165 | newIdentNode("serializeMap"), 166 | newIdentNode("serializer"), 167 | newCall( 168 | bindSym "none", 169 | ident "int" 170 | ) 171 | ) 172 | ) 173 | 174 | func defSerializeFields(fields: seq[Field]): NimNode = 175 | result = newStmtList() 176 | 177 | for field in fields: 178 | if not field.features.skipSerializing: 179 | if field.isCase: 180 | result.add defSerializeCaseField(field) 181 | else: 182 | result.add defSerializeField(field, checkSkipIf=true) 183 | 184 | func defEndMap(): NimNode = 185 | newCall( 186 | ident "endMap", 187 | ident "state" 188 | ) 189 | 190 | func defSerializeCaseField(field: Field): NimNode = 191 | let skipSerializeIf = field.features.skipSerializeIf 192 | var tempStmt = newStmtList() 193 | 194 | if not field.features.untagged: 195 | tempStmt.add defSerializeField(field, checkSkipIf=false) 196 | 197 | var caseStmt = nnkCaseStmt.newTree(defSelfDotField(field)) 198 | for variant in field.branches: 199 | let variantBody = defSerializeFields(variant.fields) 200 | 201 | case variant.kind 202 | of Of: 203 | var condition = (copy variant.conditionOfBranch).add variantBody 204 | caseStmt.add condition 205 | else: 206 | caseStmt.add nnkElse.newTree(variantBody) 207 | 208 | tempStmt.add caseStmt 209 | 210 | if skipSerializeIf.isSome: 211 | defCheckedSerializeField( 212 | field, 213 | skipSerializeIf.unsafeGet, 214 | body = tempStmt 215 | ) 216 | else: 217 | tempStmt 218 | 219 | func defSerializeField(field: Field, checkSkipIf: bool): NimNode = 220 | let 221 | skipSerializeIf = field.features.skipSerializeIf 222 | serializeWithType = field.nskTypeSerializeWithSym 223 | serializeFieldArgument = 224 | if field.features.serializeWith.isSome or field.features.deserWith.isSome: 225 | nnkObjConstr.newTree( 226 | nnkBracketExpr.newTree( 227 | serializeWithType, 228 | field.typeNode 229 | ), 230 | nnkExprColonExpr.newTree( 231 | newIdentNode("value"), 232 | defSelfDotField(field) 233 | ) 234 | ) 235 | else: 236 | defSelfDotField(field) 237 | 238 | call = newCall( 239 | ident "serializeMapEntry", 240 | ident "state", 241 | newLit field.serializeName, 242 | serializeFieldArgument 243 | ) 244 | 245 | if skipSerializeIf.isSome and checkSkipIf: 246 | defCheckedSerializeField( 247 | field, 248 | checker=skipSerializeIf.unsafeGet, 249 | body=call 250 | ) 251 | else: 252 | call 253 | 254 | func defSelfDotField(field: Field): NimNode = 255 | newDotExpr( 256 | ident "self", 257 | field.nameIdent 258 | ) 259 | 260 | func defCheckedSerializeField(field: Field, checker, body: NimNode): NimNode = 261 | nnkIfStmt.newTree( 262 | nnkElifBranch.newTree( 263 | nnkPrefix.newTree( 264 | ident "not", 265 | nnkCall.newTree( 266 | checker, 267 | defSelfDotField(field) 268 | ) 269 | ), 270 | body 271 | ) 272 | ) 273 | -------------------------------------------------------------------------------- /src/deser/macroutils/generation/utils.nim: -------------------------------------------------------------------------------- 1 | import std/[ 2 | macros 3 | ] 4 | 5 | 6 | func defWithType*(name: NimNode): NimNode = 7 | result = nnkTypeSection.newTree( 8 | nnkTypeDef.newTree( 9 | name, 10 | nnkGenericParams.newTree( 11 | nnkIdentDefs.newTree( 12 | newIdentNode("T"), 13 | newEmptyNode(), 14 | newEmptyNode() 15 | ) 16 | ), 17 | nnkObjectTy.newTree( 18 | newEmptyNode(), 19 | newEmptyNode(), 20 | nnkRecList.newTree( 21 | nnkIdentDefs.newTree( 22 | newIdentNode("value"), 23 | newIdentNode("T"), 24 | newEmptyNode() 25 | ) 26 | ) 27 | ) 28 | ) 29 | ) 30 | 31 | func defMaybeExportedIdent*(id: NimNode, public: bool): NimNode = 32 | if public: 33 | nnkPostfix.newTree( 34 | ident "*", 35 | id 36 | ) 37 | else: 38 | id 39 | 40 | macro maybePublic*(public: static[bool], body: untyped): untyped = 41 | if not public: 42 | result = body 43 | else: 44 | result = newStmtList() 45 | 46 | for element in body: 47 | if element.kind notin {nnkProcDef, nnkIteratorDef}: 48 | result.add element 49 | else: 50 | element[0] = nnkPostfix.newTree( 51 | ident "*", 52 | element[0] 53 | ) 54 | result.add element 55 | 56 | proc defPushPop*(stmtList: NimNode): NimNode = 57 | newStmtList( 58 | nnkPragma.newTree( 59 | ident "push", 60 | ident "used", 61 | ident "inline", 62 | ), 63 | stmtList, 64 | nnkPragma.newTree( 65 | ident "pop" 66 | ) 67 | ) 68 | -------------------------------------------------------------------------------- /src/deser/macroutils/parsing/field.nim: -------------------------------------------------------------------------------- 1 | import std/[ 2 | macros, 3 | options 4 | ] 5 | 6 | from deser/macroutils/types import 7 | Field, 8 | initField, 9 | 10 | FieldFeatures, 11 | initFieldFeatures, 12 | initEmptyFieldFeatures, 13 | 14 | FieldBranch, 15 | initFieldBranch, 16 | 17 | FieldBranchKind, 18 | FieldFeatures, 19 | TypeInfo, 20 | 21 | recList, 22 | merge 23 | 24 | from pragmas as parse_pragmas import 25 | parsePragma 26 | 27 | from deser/pragmas import 28 | untagged, 29 | skipped, 30 | skipSerializing, 31 | skipDeserializing, 32 | serializeWith, 33 | deserializeWith, 34 | renamed, 35 | renameSerialize, 36 | renameDeserialize, 37 | skipSerializeIf, 38 | defaultValue, 39 | aliases, 40 | deserWith 41 | 42 | # for pattern matching and assertKind 43 | import deser/macroutils/matching 44 | 45 | # Forward declaration 46 | func fieldsFromRecList*(recList: NimNode): seq[Field] 47 | 48 | func fromIdentDefs*(fieldTy: typedesc[Field], identDefs: NimNode): Field 49 | 50 | func fromRecCase*(fieldTy: typedesc[Field], recCase: NimNode): Field 51 | 52 | func fromPragma*(featuresTy: typedesc[FieldFeatures], pragma: Option[NimNode]): FieldFeatures 53 | 54 | func fromBranch*(branchTy: typedesc[FieldBranch], branch: NimNode): seq[FieldBranch] 55 | 56 | func unpackIdentDefs(identDefs: NimNode): tuple[typeNode, name: NimNode, pragma: Option[NimNode], isPublic: bool] 57 | 58 | func deSymBracketExpr(bracket: NimNode): NimNode 59 | 60 | 61 | # Parse 62 | func parseFields*(typeInfo: TypeInfo): seq[Field] = 63 | ## Parse fields from a typeInfo 64 | if Some(@reclist) ?= typeInfo.recList: 65 | fieldsFromRecList(recList) 66 | else: 67 | newSeqOfCap[Field](0) 68 | 69 | func fieldsFromRecList*(recList: NimNode): seq[Field] = 70 | ## Parse fields from a recList 71 | var firstCaseField = none Field 72 | 73 | for fieldNode in recList: 74 | case fieldNode.kind 75 | of nnkIdentDefs: 76 | # usual field 77 | result.add Field.fromIdentDefs(fieldNode) 78 | of nnkRecCase: 79 | # case field 80 | let field = Field.fromRecCase(fieldNode) 81 | if firstCaseField.isNone: 82 | firstCaseField = some field 83 | else: 84 | # Merge all cases from this level into first case field. 85 | #[ 86 | Example: 87 | type Test = object 88 | case firstKind: FirstKind 89 | of Foo: 90 | discard 91 | of Bar: 92 | discard 93 | 94 | case secondKind: SecondKind 95 | of Fizz: 96 | discard 97 | of Bazz: 98 | discard 99 | 100 | In our internal representation it will have the form: 101 | type Test = object 102 | case firstKind: FirstKind 103 | of Foo: 104 | case secondKind: SecondKind 105 | of Fizz: 106 | discard 107 | of Bazz: 108 | discard 109 | of Bar: 110 | case secondKind: SecondKind 111 | of Fizz: 112 | discard 113 | of Bazz: 114 | discard 115 | ]# 116 | # This transformation makes it easier to generate deserialization code. 117 | firstCaseField.get().merge(field) 118 | of nnkNilLit: 119 | # empty field: discard/nil 120 | discard 121 | else: 122 | assertKind fieldNode, {nnkIdentDefs, nnkRecCase, nnkNilLit} 123 | 124 | if firstCaseField.isSome: 125 | result.add firstCaseField.get() 126 | 127 | func fromIdentDefs*(fieldTy: typedesc[Field], identDefs: NimNode): Field = 128 | ## Parse a field from an identDefs node 129 | assertKind identDefs, {nnkIdentDefs} 130 | 131 | let (typeNode, nameIdent, pragmas, isPublic) = unpackIdentDefs(identDefs) 132 | 133 | initField( 134 | nameIdent=nameIdent, 135 | typeNode=typeNode, 136 | features=FieldFeatures.fromPragma(pragmas), 137 | public=isPublic, 138 | isCase=false, 139 | branches=newSeqOfCap[FieldBranch](0) 140 | ) 141 | 142 | func fromRecCase*(fieldTy: typedesc[Field], recCase: NimNode): Field = 143 | ## Parse a field from a recCase node 144 | assertKind recCase, {nnkRecCase} 145 | assertMatch recCase: 146 | RecCase[@identDefs, all @rawBranches] 147 | 148 | var branches = newSeqOfCap[FieldBranch](recCase.len-1) 149 | 150 | for branch in rawBranches: 151 | {.warning[UnsafeSetLen]: off.} 152 | branches.add FieldBranch.fromBranch(branch) 153 | {.warning[UnsafeSetLen]: on.} 154 | 155 | let (typeNode, nameIdent, pragmas, isPublic) = unpackIdentDefs(identDefs) 156 | 157 | initField( 158 | nameIdent=nameIdent, 159 | typeNode=typeNode, 160 | features=FieldFeatures.fromPragma(pragmas), 161 | public=isPublic, 162 | isCase=true, 163 | branches=branches 164 | ) 165 | 166 | func fromBranch*(branchTy: typedesc[FieldBranch], branch: NimNode): seq[FieldBranch] = 167 | ## Parse a field branch from a branch node 168 | 169 | assertMatch branch: 170 | OfBranch[until @condition is RecList(), @recList] | 171 | Else[@recList] 172 | 173 | let fields = fieldsFromRecList(recList) 174 | 175 | if condition.len > 0: 176 | result = newSeqOfCap[FieldBranch](condition.len) 177 | for cond in condition: 178 | result.add initFieldBranch( 179 | fields=fields, 180 | conditionOfBranch=some nnkOfBranch.newTree(cond) 181 | ) 182 | else: 183 | result.add initFieldBranch( 184 | fields=fields, 185 | conditionOfBranch=none NimNode 186 | ) 187 | 188 | func fromPragma*(featuresTy: typedesc[FieldFeatures], pragma: Option[NimNode]): FieldFeatures = 189 | ## Parse features from a pragma 190 | 191 | if Some(@pragma) ?= pragma: 192 | assertKind pragma, {nnkPragma} 193 | 194 | let 195 | untaggedSym = bindSym("untagged") 196 | skippedSym = bindSym("skipped") 197 | skipSerializingSym = bindSym("skipSerializing") 198 | skipDeserializingSym = bindSym("skipDeserializing") 199 | serializeWithSym = bindSym("serializeWith") 200 | deserializeWithSym = bindSym("deserializeWith") 201 | renamedSym = bindSym("renamed") 202 | renameSerializeSym = bindSym("renameSerialize") 203 | renameDeserializeSym = bindSym("renameDeserialize") 204 | skipSerializeIfSym = bindSym("skipSerializeIf") 205 | defaultValueSym = bindSym("defaultValue") 206 | aliasesSym = bindSym("aliases") 207 | deserWithSym = bindSym("deserWith") 208 | 209 | var 210 | untagged = false 211 | skipSerializing = false 212 | skipDeserializing = false 213 | serializeWith = none NimNode 214 | deserializeWith = none NimNode 215 | renameDeserialize = none NimNode 216 | renameSerialize = none NimNode 217 | skipSerializeIf = none NimNode 218 | defaultValue = none NimNode 219 | aliases = newSeqOfCap[NimNode](0) 220 | deserWith = none NimNode 221 | 222 | for symbol, values in parsePragma(pragma): 223 | if symbol == untaggedSym: 224 | untagged = true 225 | elif symbol == skippedSym: 226 | skipDeserializing = true 227 | skipSerializing = true 228 | elif symbol == skipSerializingSym: 229 | skipSerializing = true 230 | elif symbol == skipDeserializingSym: 231 | skipDeserializing = true 232 | elif symbol == serializeWithSym: 233 | serializeWith = some values[0] 234 | elif symbol == deserializeWithSym: 235 | deserializeWith = some values[0] 236 | elif symbol == renamedSym: 237 | renameDeserialize = some values[0] 238 | renameSerialize = some values[0] 239 | elif symbol == renameSerializeSym: 240 | renameSerialize = some values[0] 241 | elif symbol == renameDeserializeSym: 242 | renameDeserialize = some values[0] 243 | elif symbol == skipSerializeIfSym: 244 | skipSerializeIf = some values[0] 245 | elif symbol == defaultValueSym: 246 | if values[0].kind == nnkNilLit: 247 | defaultValue = some newEmptyNode() 248 | else: 249 | defaultValue = some values[0] 250 | elif symbol == aliasesSym: 251 | assertKind values[0], {nnkHiddenStdConv} 252 | assertMatch values[0]: 253 | HiddenStdConv[Empty(), Bracket[all @values]] 254 | aliases = values 255 | elif symbol == deserWithSym: 256 | deserWith = some values[0] 257 | 258 | if aliases.len > 0 and renameDeserialize.isSome: 259 | error("Cannot use both `aliases` and `renameDeserialize` on the same field.", pragma) 260 | 261 | if deserWith.isSome and (serializeWith.isSome or deserializeWith.isSome): 262 | error("Cannot use both `deserWith` and `serializeWith` or `deserializeWith` on the same field", pragma) 263 | 264 | result = initFieldFeatures( 265 | skipSerializing = skipSerializing, 266 | skipDeserializing = skipDeserializing, 267 | untagged = untagged, 268 | renameSerialize = renameSerialize, 269 | renameDeserialize = renameDeserialize, 270 | skipSerializeIf = skipSerializeIf, 271 | serializeWith = serializeWith, 272 | deserializeWith = deserializeWith, 273 | defaultValue = defaultValue, 274 | aliases = aliases, 275 | deserWith = deserWith 276 | ) 277 | else: 278 | result = initEmptyFieldFeatures() 279 | 280 | func unpackIdentDefs(identDefs: NimNode): tuple[typeNode, name: NimNode, pragma: Option[NimNode], isPublic: bool] = 281 | assertMatch identDefs: 282 | IdentDefs[ 283 | (@name is Ident()) | 284 | PragmaExpr[@name is Ident(), @pragma is Pragma()] | 285 | (@public is Postfix[_, @name is Ident()]) | 286 | PragmaExpr[@public is (Postfix[_, @name is Ident()]), @pragma is Pragma()] | 287 | AccQuoted[@name is Ident()] | 288 | (@public is Postfix[_, AccQuoted[@name is Ident()]]) | 289 | PragmaExpr[AccQuoted[@name is Ident()], @pragma is Pragma()] | 290 | PragmaExpr[(@public is Postfix[_, AccQuoted[@name is Ident()]]), @pragma is Pragma()], 291 | @typeNode, 292 | _ 293 | ] 294 | 295 | let typeNodeResult = 296 | case typeNode.kind 297 | # generic 298 | of nnkBracketExpr: 299 | deSymBracketExpr(typeNode) 300 | else: 301 | typeNode 302 | 303 | (typeNodeResult, name, pragma, public.isSome) 304 | 305 | func deSymBracketExpr(bracket: NimNode): NimNode = 306 | ## HACK: https://github.com/nim-lang/Nim/issues/19670 307 | assertKind bracket, {nnkBracketExpr} 308 | 309 | result = nnkBracketExpr.newTree(bracket[0]) 310 | 311 | for i in bracket[1..bracket.len-1]: 312 | case i.kind 313 | of nnkSym: 314 | result.add i.strVal.ident 315 | of nnkBracketExpr: 316 | result.add deSymBracketExpr(i) 317 | else: 318 | result.add i 319 | -------------------------------------------------------------------------------- /src/deser/macroutils/parsing/pragmas.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | action: "compile" 3 | """ 4 | {.experimental: "caseStmtMacros".} 5 | 6 | import std/[ 7 | macros 8 | ] 9 | 10 | # for pattern matching and assertKind 11 | import deser/macroutils/matching 12 | 13 | 14 | iterator parsePragma*(pragmas: NimNode): (NimNode, seq[NimNode]) = 15 | ## Parse `nnkPragma` node and return tuple with pragma symbol and pragma values. 16 | runnableExamples: 17 | import std/[macros] 18 | 19 | template test(a: int) {.pragma.} 20 | 21 | macro run() = 22 | let pragma = nnkPragma.newTree( 23 | newCall( 24 | bindSym"test", 25 | newLit 123 26 | ) 27 | ) 28 | 29 | for sym, values in pragma.parsePragma: 30 | doAssert sym == bindSym"test" 31 | doAssert values == @[newLit 123] 32 | 33 | run() 34 | 35 | assertKind pragmas, {nnkPragma} 36 | 37 | for pragma in pragmas: 38 | case pragma: 39 | of Sym(): 40 | # {.pragmaName.} 41 | yield (pragma, @[]) 42 | of Call[@sym, all @values] | ExprColonExpr[@sym, all @values]: 43 | # {.pragmaName(values).} 44 | # or 45 | # {.pragmaName: value.} 46 | yield (sym, values) 47 | of Ident(): 48 | discard "process only typed nodes" 49 | else: 50 | assertKind pragma, {nnkSym, nnkCall, nnkExprColonExpr, nnkIdent} 51 | -------------------------------------------------------------------------------- /src/deser/macroutils/parsing/struct.nim: -------------------------------------------------------------------------------- 1 | {.experimental: "caseStmtMacros".} 2 | 3 | import std/[ 4 | macros, 5 | options, 6 | strformat 7 | ] 8 | 9 | from deser/macroutils/types as macro_types import 10 | TypeInfo, 11 | initTypeInfo, 12 | recList, 13 | pragma, 14 | 15 | Struct, 16 | initStruct, 17 | genericParams, 18 | 19 | StructFeatures, 20 | initStructFeatures, 21 | initEmptyStructFeatures, 22 | 23 | Field, 24 | isCase, 25 | branches, 26 | public, 27 | features, 28 | fields, 29 | aliases, 30 | renameSerialize, 31 | renameDeserialize, 32 | `skipSerializing=`, 33 | `skipDeserializing=`, 34 | `renameSerialize=`, 35 | `renameDeserialize=`, 36 | `defaultValue=` 37 | 38 | from deser/macroutils/types import nil 39 | 40 | from field import 41 | parseFields 42 | 43 | from pragmas as parse_pragmas import 44 | parsePragma 45 | 46 | from deser/pragmas import 47 | renameAll, 48 | onUnknownKeys, 49 | skipPrivate, 50 | skipPrivateSerializing, 51 | skipPrivateDeserializing, 52 | defaultValue, 53 | RenameCase 54 | 55 | # for pattern matching and assertKind 56 | import deser/macroutils/matching 57 | 58 | # Forward declaration 59 | func fromTypeSym*(typeInfoTy: typedesc[TypeInfo], typeSym: NimNode): TypeInfo 60 | 61 | func fromPragma*(featuresTy: typedesc[StructFeatures], pragma: Option[NimNode]): StructFeatures 62 | 63 | func showUnsupportedObjectError(symbol: NimNode, nodeKind: NimNodeKind) {.noreturn.} 64 | 65 | func mergeRecList*(toNode, fromNode: NimNode): NimNode 66 | 67 | proc propagateFeatures(fields: var seq[Field], features: StructFeatures) 68 | 69 | func mergePragma(toNode, fromNode: NimNode): NimNode 70 | 71 | # Parse 72 | func fromTypeSym*(structTy: typedesc[Struct], typeSym: NimNode): Struct = 73 | ## Parse `nnkSym` node and return `Struct`. 74 | bind 75 | pragmas.onUnknownKeys, 76 | pragmas.skipPrivate, 77 | pragmas.renameAll, 78 | pragmas.skipPrivateSerializing, 79 | pragmas.skipPrivateDeserializing, 80 | pragmas.defaultValue 81 | 82 | let 83 | typeInfo = TypeInfo.fromTypeSym(typeSym) 84 | features = StructFeatures.fromPragma(typeInfo.pragma) 85 | 86 | var fields = parseFields(typeInfo) 87 | propagateFeatures(fields, features) 88 | 89 | initStruct( 90 | typeSym=typeSym, 91 | fields=fields, 92 | features=features, 93 | genericParams=typeInfo.genericParams 94 | ) 95 | 96 | func fromTypeSym*(typeInfoTy: typedesc[TypeInfo], typeSym: NimNode): TypeInfo = 97 | ## Parse `nnkSym` node and return `TypeInfo`. 98 | 99 | assertMatch typeSym: 100 | (@typeSym is Sym()) | 101 | (BracketExpr[@typeSym is Sym(), .._]) 102 | 103 | let declaration = typeSym.getImpl() 104 | 105 | if declaration.kind == nnkNilLit: 106 | error( 107 | "No type declaration. Maybe it is a built-in type. Almost all built-in types are serializable by default", 108 | typeSym 109 | ) 110 | else: 111 | assertKind declaration, {nnkTypeDef} 112 | 113 | let implementation = declaration[2] 114 | 115 | if implementation.kind notin {nnkObjectTy, nnkRefTy}: 116 | showUnsupportedObjectError(typeSym, implementation.kind) 117 | 118 | if implementation.kind == nnkRefTy and implementation[0].kind != nnkObjectTy: 119 | showUnsupportedObjectError(typeSym, implementation[0].kind) 120 | 121 | assertMatch declaration: 122 | TypeDef[ 123 | PragmaExpr[_, @pragma] | Sym(), 124 | (@genericParams is GenericParams()) | Empty(), 125 | RefTy[@objectTy is ObjectTy()] | (@objectTy is ObjectTy()), 126 | ] 127 | 128 | assertMatch objectTy: 129 | ObjectTy[ 130 | _, 131 | OfInherit[@parentTypeSym] | Empty(), 132 | (@recList is RecList()) | Empty() 133 | ] 134 | 135 | if Some(@parentTypeSym) ?= parentTypeSym: 136 | let parentTypeInfo = TypeInfo.fromTypeSym(parentTypeSym) 137 | 138 | if Some(@parentRecList) ?= parentTypeInfo.recList: 139 | if Some(@recListValue) ?= recList: 140 | recList = some mergeRecList(parentRecList, recListValue) 141 | else: 142 | recList = some parentRecList 143 | 144 | if Some(@parentPragma) ?= parentTypeInfo.pragma: 145 | if Some(@pragmaValue) ?= pragma: 146 | pragma = some mergePragma(pragmaValue, parentPragma) 147 | else: 148 | pragma = some parentPragma 149 | 150 | initTypeInfo( 151 | typeSym=typeSym, 152 | pragma=pragma, 153 | recList=recList, 154 | genericParams=genericParams 155 | ) 156 | 157 | func fromPragma*(featuresTy: typedesc[StructFeatures], pragma: Option[NimNode]): StructFeatures = 158 | ## Parse `nnkPragma` node and return `StructFeatures`. 159 | 160 | if Some(@pragma) ?= pragma: 161 | assertKind pragma, {nnkPragma} 162 | 163 | let 164 | onUnknownKeysSym = bindSym("onUnknownKeys") 165 | renameAllSym = bindSym("renameAll") 166 | skipPrivateSym = bindSym("skipPrivate") 167 | skipPrivateSerializingSym = bindSym("skipPrivateSerializing") 168 | skipPrivateDeserializingSym = bindSym("skipPrivateDeserializing") 169 | defaultValueSym = bindSym("defaultValue") 170 | 171 | var 172 | onUnknownKeys = none NimNode 173 | renameAll = none NimNode 174 | skipPrivateSerializing = false 175 | skipPrivateDeserializing = false 176 | defaultValue = none NimNode 177 | 178 | for symbol, values in parsePragma(pragma): 179 | if symbol == onUnknownKeysSym: 180 | onUnknownKeys = some values[0] 181 | elif symbol == renameAllSym: 182 | renameAll = some values[0] 183 | elif symbol == skipPrivateSym: 184 | skipPrivateSerializing = true 185 | skipPrivateDeserializing = true 186 | elif symbol == skipPrivateSerializingSym: 187 | skipPrivateSerializing = true 188 | elif symbol == skipPrivateDeserializingSym: 189 | skipPrivateDeserializing = true 190 | elif symbol == defaultValueSym: 191 | if values[0].kind == nnkNilLit: 192 | defaultValue = some newEmptyNode() 193 | else: 194 | defaultValue = some values[0] 195 | 196 | initStructFeatures( 197 | onUnknownKeys=onUnknownKeys, 198 | renameAll=renameAll, 199 | skipPrivateSerializing=skipPrivateSerializing, 200 | skipPrivateDeserializing=skipPrivateDeserializing, 201 | defaultValue=defaultValue 202 | ) 203 | else: 204 | initEmptyStructFeatures() 205 | 206 | func showUnsupportedObjectError(symbol: NimNode, nodeKind: NimNodeKind) {.noreturn.} = 207 | case nodeKind 208 | of nnkSym: 209 | error("Aliases are not supported. Call the `make(De)Serializable` macro for the base type.", symbol) 210 | of nnkEnumTy: 211 | error("Enums are serializable by default.", symbol) 212 | of nnkInfix, nnkTypeClassTy: 213 | error("Type classes are not supported.", symbol) 214 | of nnkTupleConstr, nnkTupleTy: 215 | error("Tuples are serializable by default.", symbol) 216 | else: 217 | error( 218 | fmt"Node of kind `{nodeKind}` is not supported.", 219 | symbol 220 | ) 221 | 222 | func mergeRecList(toNode, fromNode: NimNode): NimNode = 223 | assertKind toNode, {nnkRecList} 224 | assertKind fromNode, {nnkRecList} 225 | 226 | result = copy toNode 227 | 228 | for field in fromNode: 229 | result.add field 230 | 231 | func mergePragma(toNode, fromNode: NimNode): NimNode = 232 | assertKind toNode, {nnkPragma} 233 | assertKind fromNode, {nnkPragma} 234 | 235 | result = copy toNode 236 | 237 | for pragma in fromNode: 238 | result.add pragma 239 | 240 | proc propagateFeatures(fields: var seq[Field], features: StructFeatures) = 241 | for field in fields.mitems: 242 | if types.skipPrivateSerializing(features) and not public field: 243 | field.features.skipSerializing = true 244 | if types.skipPrivateDeserializing(features) and not public field: 245 | field.features.skipDeserializing = true 246 | 247 | if macro_types.defaultValue(field.features).isNone: 248 | field.features.defaultValue = types.defaultValue(features) 249 | 250 | # do not check aliases here, because they are useless for serialization 251 | if field.features.renameSerialize.isNone: 252 | field.features.renameSerialize = types.renameAll(features) 253 | 254 | if field.features.renameDeserialize.isNone and field.features.aliases.len == 0: 255 | field.features.renameDeserialize = types.renameAll(features) 256 | 257 | if field.isCase: 258 | for branch in field.branches.mitems: 259 | branch.fields.propagateFeatures(features) 260 | -------------------------------------------------------------------------------- /src/deser/pragmas.nim: -------------------------------------------------------------------------------- 1 | type 2 | RenameCase* = enum 3 | CamelCase 4 | CobolCase 5 | KebabCase 6 | PascalCase 7 | PathCase 8 | SnakeCase 9 | PlainCase 10 | TrainCase 11 | UpperSnakeCase 12 | 13 | 14 | template untagged*() {.pragma.} ##[ 15 | By default, the discriminant of object variants is de/serialized as a regular field: 16 | 17 | ```nim 18 | type 19 | Test = object 20 | kind: bool 21 | of true: 22 | trueField: string 23 | else: 24 | falseField: string 25 | ``` 26 | equals to this JSON: 27 | 28 | ```json 29 | { 30 | "kind": true 31 | "trueField": "" 32 | } 33 | ``` 34 | 35 | However, `deser` can deduce the discriminant from the raw data: 36 | 37 | ```nim 38 | import deser_json 39 | 40 | type 41 | Test = object 42 | kind {.untagged.}: bool 43 | of true: 44 | trueField: string 45 | else: 46 | falseField: string 47 | 48 | makeDeserializable(Test) 49 | 50 | const json = """ 51 | { 52 | "trueField": "" 53 | } 54 | """ 55 | let test = Test.fromJson(json) 56 | 57 | assert test.kind == true 58 | ``` 59 | ]## 60 | 61 | template serializeWith*(with: typed) {.pragma.} ##[ 62 | Serialize this field using a procedure. 63 | 64 | The given function must be callable as `proc (self: FieldType, serializer: var auto)`. 65 | 66 | **Example:** 67 | ```nim 68 | import std/times 69 | 70 | import 71 | deser, 72 | deser_json 73 | 74 | proc toTimestamp(self: Time, serializer: var auto) = 75 | serializer.serializeInt64(self.toUnix()) 76 | 77 | type 78 | User = object 79 | created {.serializeWith(toTimestamp).}: Time 80 | 81 | makeSerializable(User) 82 | 83 | assert User(created: fromUnix(123)).toJson() == """{"created":123}""" 84 | ``` 85 | ]## 86 | 87 | template deserializeWith*(with: typed) {.pragma.} ##[ 88 | Deserialize this field using a procedure. 89 | 90 | The given procedure must be callable as `proc (deserializer: var auto): FieldType` or `proc [T](deserializer: var auto): T`. 91 | 92 | **Example:** 93 | ```nim 94 | import std/times 95 | 96 | import 97 | deser, 98 | deser_json 99 | 100 | proc fromTimestamp(deserializer: var auto): Time = 101 | fromUnix(deserialize(int64, deserializer)) 102 | 103 | type 104 | User = object 105 | created {.deserializeWith(fromTimestamp).}: Time 106 | 107 | makeDeserializable(User) 108 | 109 | assert User(created: fromUnix(123)) == User.fromJson("""{"created": 123}""") 110 | ``` 111 | ]## 112 | 113 | template deserWith*(with: typed) {.pragma.} ##[ 114 | Combination of `serializeWith` and `deserializeWith`. 115 | 116 | The given type (or anything actually) must have callable .serialize and .deserialize attributes. 117 | 118 | .serialize must be callable as `proc (self: withType, field: FieldType, serializer: var auto)`. 119 | 120 | .deserialize must be callable as `proc (self: withType, deserializer: var auto): FieldType` or `proc [T](self: withType, deserializer: var auto): T`. 121 | 122 | .. Note:: You dont need to reimplement this example in your project. Please use [helpers](https://deser.nim.town/deser/helpers.html) module. 123 | 124 | **Example:** 125 | ```nim 126 | import std/times 127 | 128 | import 129 | deser, 130 | deser_json 131 | 132 | 133 | type UnixTimeFormat = object 134 | 135 | proc deserialize(self: typedesc[UnixTimeFormat], deserializer: var auto): Time = 136 | fromUnix(deserialize(int64, deserializer)) 137 | 138 | proc serialize(self: typedesc[UnixTimeFormat], field: Time, serializer: var auto) = 139 | serializer.serializeInt64(self.toUnix()) 140 | 141 | type 142 | User = object 143 | created {.deserWith(UnixTimeFormat).}: Time 144 | 145 | makeSerializable(User) 146 | makeDeserializable(User) 147 | 148 | let user = User(created: fromUnix(123)) 149 | 150 | assert user == User.fromJson("""{"created": 123}""") 151 | assert user.toJson() == """{"created":123}""" 152 | ``` 153 | ]## 154 | 155 | template renamed*(renamed: string | RenameCase) {.pragma.} ##[ 156 | Serialize and deserialize field with the given name instead of its Nim name. 157 | ]## 158 | 159 | template renameSerialize*(renamed: string | RenameCase) {.pragma.} ##[ 160 | Serialize field with the given name instead of its Nim name. 161 | ]## 162 | 163 | template renameDeserialize*(renamed: string | RenameCase) {.pragma.} ##[ 164 | Deserialize field with the given name instead of its Nim name. 165 | ]## 166 | 167 | template skipped*() {.pragma.} ##[ 168 | Use this pragma to skip the field during serialization and deserialization. 169 | 170 | **Example**: 171 | 172 | ```nim 173 | type 174 | Test = object 175 | alwaysSkip {.skipped.}: int 176 | ``` 177 | ]## 178 | 179 | template skipSerializing*() {.pragma.} ##[ 180 | Use this pragma to skip the field during serialization. 181 | 182 | **Example**: 183 | ```nim 184 | type 185 | Test = object 186 | skipOnSerialization {.skipSerializing.}: int 187 | ``` 188 | ]## 189 | 190 | template skipDeserializing*() {.pragma.} ##[ 191 | Use this pragma to skip the field during deserialization. 192 | 193 | **Example**: 194 | 195 | ```nim 196 | type 197 | Test = object 198 | skipOnDeserialization {.skipDeserializing.}: int 199 | ``` 200 | ]## 201 | 202 | template skipSerializeIf*(condition: typed) {.pragma.} ##[ 203 | Use this pragma to skip the field during serialization based on the runtime value. 204 | 205 | You must specify a function or template that accepts an argument with the same type as the field, and return bool. 206 | 207 | **Example**: 208 | 209 | ```nim 210 | import std/options 211 | 212 | func isZero(x: int): bool = x == 0 213 | 214 | type 215 | Test = object 216 | someOption {.skipSerializeIf(isNone).}: Option[int] 217 | someInt {.skipSerializeIf(isZero).}: int 218 | ``` 219 | ]## 220 | 221 | template defaultValue*(value: typed = nil) {.pragma.} ##[ 222 | Uses the specified value, function or template if the field was not in the input. 223 | 224 | Function or template must be callable as `myDefault[T]()`. 225 | 226 | **Examples**: 227 | ```nim 228 | type 229 | User = object 230 | name {.defaultValue("noname").}: string 231 | ``` 232 | 233 | Do not specify a value, then system [default](https://nim-lang.org/docs/system.html#default%2Ctypedesc%5BT%5D) will be used: 234 | 235 | ```nim 236 | import deser_json 237 | 238 | type 239 | Foo = object 240 | id {.defaultValue.}: int 241 | 242 | assert Foo.fromJson("""{}""").id == 0 243 | ``` 244 | 245 | Specify a function 246 | 247 | ```nim 248 | proc myDefault[T](): T = 249 | when T is int: 250 | 123 251 | elif T is string: 252 | "hello" 253 | else: 254 | {.error.} 255 | 256 | type 257 | User = object 258 | id {.defaultValue(myDefault).}: int 259 | ``` 260 | 261 | You can use `defaultValue` on object. Than specified value will be used on all missing fields. 262 | 263 | ```nim 264 | type 265 | User {.defaultValue(myDefault).} = 266 | id: int 267 | bio: string 268 | ``` 269 | ]## 270 | 271 | template onUnknownKeys*(call: typed) {.pragma.} ##[ 272 | By default, the deserializer skips unknown fields. 273 | You can change this behavior by specifying a template or procedure that will be called when an unknown field is detected. 274 | 275 | The template or procedure must take two arguments: 276 | 277 | - the name of the object: string or static[string] 278 | 279 | - the field value: auto 280 | 281 | **Example**: 282 | 283 | ```nim 284 | import std/strformat 285 | 286 | # this example will not work with all parsers, 287 | # because it expects the field as a string, 288 | # but in some formats the field can be represented by a number 289 | proc showUpdateWarning(objName, fieldName: string) = 290 | # show warning only once 291 | var yet {.global.} = false 292 | 293 | if not yet: 294 | echo &"An unknown `{fieldName}` field was detected when deseralizing the `{objName}` object. Check the library updates" 295 | yet = true 296 | 297 | 298 | type 299 | User {.onUnknownKeys(showUpdateWarning).} = object 300 | id: int 301 | name: string 302 | ``` 303 | 304 | Another example with warnings output only once for each object: 305 | 306 | ```nim 307 | proc showUpdateWarning(objName: static[string], fieldName: string) = 308 | # Since the object name is known at compile time, 309 | # we can make the `objName` argument generic and use the behavior of the `global` pragma 310 | # https://nim-lang.org/docs/manual.html#pragmas-global-pragma 311 | var yet {.global.} = false 312 | 313 | if not yet: 314 | echo &"An unknown `{fieldName}` field was detected when deseralizing the `{objName}` object. Check the library updates" 315 | yet = true 316 | ``` 317 | ]## 318 | 319 | template renameAll*(renameTo: RenameCase) {.pragma.} ##[ 320 | Rename all fields to some case. 321 | 322 | .. Note:: Pragma respects other `rename` pragmas. For example, if a field has the `renameSerialize` pragma, only deserialization will be affected. 323 | 324 | **Example**: 325 | ```nim 326 | import deser_json 327 | 328 | type 329 | Foo {.renameAll(SnakeCase).} = object 330 | firstName: string 331 | lastName: string 332 | 333 | makeSerializable(Foo) 334 | 335 | assert Foo().toJson() == """{"first_name":"","last_name":""}""" 336 | ``` 337 | ]## 338 | 339 | template skipPrivateSerializing* {.pragma.} ##[ 340 | Use this pragma to skip all private fields during serialization 341 | 342 | **Example**: 343 | ```nim 344 | type 345 | User {.skipPrivateSerializing.} = object 346 | id*: int 347 | name*: string 348 | passwordHash: string 349 | ``` 350 | ]## 351 | 352 | template skipPrivateDeserializing* {.pragma.} ##[ 353 | Use this pragma to skip all private fields during deserialization 354 | 355 | **Example**: 356 | ```nim 357 | type 358 | User {.skipPrivateDeserializing.} = object 359 | id*: int 360 | name*: string 361 | passwordHash: string 362 | ``` 363 | ]## 364 | 365 | template skipPrivate* {.pragma.} ##[ 366 | Use this pragma to skip all private fields during serialization and deserialization. 367 | 368 | **Example**: 369 | ```nim 370 | type 371 | User {.skipPrivate.} = object 372 | id*: int 373 | name*: string 374 | passwordHash: string 375 | ``` 376 | ]## 377 | 378 | template aliases*(aliases: varargs[typed]) {.pragma.} ##[ 379 | Deserialize field from the given names or from its Nim name. 380 | Accepts strings and `RenameCase` values. 381 | 382 | **Example**: 383 | ```nim 384 | type 385 | User = object 386 | nickName {.aliases("username", "login", SnakeCase).}: string 387 | ``` 388 | ]## 389 | -------------------------------------------------------------------------------- /src/deser/ser.nim: -------------------------------------------------------------------------------- 1 | import ser/[ 2 | impls, 3 | make, 4 | helpers 5 | ] 6 | 7 | export 8 | impls, 9 | make, 10 | helpers 11 | -------------------------------------------------------------------------------- /src/deser/ser/helpers.nim: -------------------------------------------------------------------------------- 1 | import std/[options] 2 | 3 | from deser/macroutils/generation/utils import maybePublic 4 | 5 | template asAddr*(ident: untyped, exp: untyped): untyped = 6 | ## Get result from procedures by addr 7 | when compiles(addr(exp)): 8 | let temp = addr(exp) 9 | template ident: untyped = temp[] 10 | else: 11 | var ident = exp 12 | 13 | template implSerializer*(selfType: typed{`type`}, public: static[bool] = false) {.dirty.} = 14 | bind 15 | maybePublic, 16 | Option, 17 | asAddr 18 | 19 | maybePublic(public): 20 | # implementation expected 21 | proc serializeBool(self: var selfType, value: bool) 22 | 23 | proc serializeInt8(self: var selfType, value: int8) 24 | proc serializeInt16(self: var selfType, value: int16) 25 | proc serializeInt32(self: var selfType, value: int32) 26 | proc serializeInt64(self: var selfType, value: int64) 27 | 28 | proc serializeUint8(self: var selfType, value: uint8) 29 | proc serializeUint16(self: var selfType, value: uint16) 30 | proc serializeUint32(self: var selfType, value: uint32) 31 | proc serializeUint64(self: var selfType, value: uint64) 32 | 33 | proc serializeFloat32(self: var selfType, value: float32) 34 | proc serializeFloat64(self: var selfType, value: float64) 35 | 36 | proc serializeChar(self: var selfType, value: char) 37 | proc serializeString(self: var selfType, value: openArray[char]) 38 | 39 | proc serializeBytes(self: var selfType, value: openArray[byte]) 40 | 41 | proc serializeNone(self: var selfType) 42 | proc serializeSome(self: var selfType, value: auto) 43 | 44 | proc serializeEnum(self: var selfType, value: enum) 45 | 46 | # proc serializeSeq(self: selfType, len: Option[int]): auto 47 | 48 | # proc serializeArray(self: selfType, len: static[int]): auto 49 | 50 | # proc serializeMap(self: selfType, len: Option[int]): auto 51 | 52 | # proc serializeStruct(self: selfType, name: static[string], len: static[int]): auto 53 | 54 | when defined(release): 55 | {.push inline.} 56 | 57 | proc collectSeq(self: var selfType, iter: auto) = 58 | when compiles(iter.len): 59 | let length = some iter.len 60 | else: 61 | let length = none int 62 | 63 | asAddr state, self.serializeSeq(length) 64 | 65 | for value in iter: 66 | state.serializeSeqElement(value) 67 | 68 | state.endSeq() 69 | 70 | proc collectMap(self: var selfType, iter: auto) = 71 | when compiles(iter.len): 72 | let length = some iter.len 73 | else: 74 | let length = none int 75 | 76 | asAddr state, self.serializeMap(length) 77 | 78 | for key, value in iter: 79 | state.serializeMapEntry(key, value) 80 | 81 | state.endMap() 82 | 83 | when defined(nimHasIterable): 84 | template collectSeq*[Value](self: var selfType, iter: iterable[Value]) = 85 | asAddr state, self.serializeSeq(none int) 86 | 87 | for value in iter: 88 | state.serializeSeqElement(value) 89 | 90 | state.endSeq() 91 | 92 | template collectMap*[Key; Value](self: var auto, iter: iterable[(Key, Value)]) = 93 | asAddr state, self.serializeMap(none int) 94 | 95 | for key, value in iter: 96 | state.serializeMapEntry(key, value) 97 | 98 | state.endMap() 99 | 100 | when defined(release): 101 | {.pop.} 102 | 103 | 104 | template implSerializeSeq*(selfType: typed{`type`}, public: static[bool] = false) {.dirty.} = 105 | bind maybePublic 106 | 107 | maybePublic(public): 108 | proc serializeSeqElement(self: var selfType, value: auto) 109 | 110 | proc endSeq(self: var selfType) 111 | 112 | template implSerializeArray*(selfType: typed{`type`}, public: static[bool] = false) {.dirty.} = 113 | bind maybePublic 114 | 115 | maybePublic(public): 116 | proc serializeArrayElement(self: var selfType, value: auto) 117 | 118 | proc endArray(self: var selfType) 119 | 120 | template implSerializeMap*(selfType: typed{`type`}, public: static[bool] = false) {.dirty.} = 121 | bind maybePublic 122 | 123 | maybePublic(public): 124 | proc serializeMapKey(self: var selfType, key: auto) 125 | 126 | proc serializeMapValue(self: var selfType, value: auto) 127 | 128 | proc endMap(self: var selfType) 129 | 130 | when defined(release): 131 | {.push inline.} 132 | 133 | proc serializeMapEntry(self: var selfType, key: auto, value: auto) = 134 | self.serializeMapKey(key) 135 | self.serializeMapValue(value) 136 | 137 | when defined(release): 138 | {.pop.} 139 | 140 | template implSerializeStruct*(selfType: typed{`type`}, public: static[bool] = false) {.dirty.} = 141 | bind maybePublic 142 | 143 | maybePublic(public): 144 | proc serializeStructField(self: var selfType, key: static[string], value: auto) 145 | 146 | proc endStruct(self: var selfType) 147 | -------------------------------------------------------------------------------- /src/deser/ser/impls.nim: -------------------------------------------------------------------------------- 1 | ## Implementation of `serialize` for std types. 2 | 3 | import std/[options, typetraits, tables, sets] 4 | 5 | from helpers import asAddr 6 | 7 | 8 | when defined(release): 9 | {.push inline.} 10 | # Basic types 11 | proc serialize*(self: bool, serializer: var auto) = 12 | mixin serializeBool 13 | 14 | serializer.serializeBool(self) 15 | 16 | proc serialize*(self: SomeInteger, serializer: var auto) = 17 | mixin 18 | serializeInt8, 19 | serializeInt16, 20 | serializeInt32, 21 | serializeInt64, 22 | serializeUint8, 23 | serializeUint16, 24 | serializeUint32, 25 | serializeUint64 26 | 27 | when self is int8: 28 | serializer.serializeInt8(self) 29 | elif self is int16: 30 | serializer.serializeInt16(self) 31 | elif self is int32: 32 | serializer.serializeInt32(self) 33 | elif self is int64 | int: 34 | serializer.serializeInt64(self) 35 | elif self is uint8: 36 | serializer.serializeUint8(self) 37 | elif self is uint16: 38 | serializer.serializeUint16(self) 39 | elif self is uint32: 40 | serializer.serializeUint32(self) 41 | elif self is uint64 | uint: 42 | serializer.serializeUint64(self) 43 | 44 | proc serialize*(self: SomeFloat, serializer: var auto) = 45 | mixin 46 | serializeFloat32, 47 | serializeFloat64 48 | 49 | when self is float32: 50 | serializer.serializeFloat32(self) 51 | else: 52 | serializer.serializeFloat64(self) 53 | 54 | proc serializeStringSlice(self: openArray[char], serializer: var auto) = 55 | mixin serializeString 56 | 57 | serializer.serializeString(self) 58 | 59 | template serialize*(self: string, serializer: var auto) = 60 | # Workaround for string serialization 61 | # array or seq of char must be serialized as sequence 62 | bind serializeStringSlice 63 | serializeStringSlice(self, serializer) 64 | 65 | proc serialize*[T: char](self: T, serializer: var auto) = 66 | mixin serializeChar 67 | 68 | serializer.serializeChar(self) 69 | 70 | proc serialize*[T: enum](self: T, serializer: var auto) = 71 | mixin serializeEnum 72 | 73 | serializer.serializeEnum(self) 74 | 75 | proc serialize*[T: set](self: T, serializer: var auto) = 76 | mixin collectSeq 77 | 78 | serializer.collectSeq(self) 79 | 80 | proc serialize*(self: openArray[not byte], serializer: var auto) = 81 | mixin collectSeq 82 | 83 | serializer.collectSeq(self) 84 | 85 | proc serialize*(self: openArray[byte], serializer: var auto) = 86 | mixin serializeBytes 87 | 88 | serializer.serializeBytes(self) 89 | 90 | proc serialize*(self: tuple, serializer: var auto) = 91 | mixin 92 | serializeArray, 93 | serializeArrayElement, 94 | endArray 95 | 96 | asAddr state, serializer.serializeArray(self.tupleLen()) 97 | 98 | for value in self.fields: 99 | state.serializeArrayElement(value) 100 | 101 | state.endArray() 102 | 103 | 104 | # other std types 105 | proc serialize*(self: Option, serializer: var auto) = 106 | mixin 107 | serializeSome, 108 | serializeNone 109 | 110 | if self.isSome: 111 | serializer.serializeSome(self.unsafeGet) 112 | else: 113 | serializer.serializeNone() 114 | 115 | proc serialize*[SomeTable: Table | OrderedTable](self: SomeTable, serializer: var auto) = 116 | mixin collectMap 117 | 118 | serializer.collectMap(self) 119 | 120 | proc serialize*(self: SomeSet, serializer: var auto) = 121 | mixin collectSeq 122 | 123 | serializer.collectSeq(self) 124 | 125 | proc serialize*(self: ref, serializer: var auto) = 126 | mixin serialize 127 | 128 | if not self.isNil: 129 | serialize(self[], serializer) 130 | else: 131 | serializer.serializeNone() 132 | 133 | when defined(release): 134 | {.pop.} 135 | -------------------------------------------------------------------------------- /src/deser/ser/make.nim: -------------------------------------------------------------------------------- 1 | import std/[ 2 | macros 3 | ] 4 | 5 | from deser/macroutils/types import 6 | Struct 7 | 8 | from deser/macroutils/parsing/struct import 9 | fromTypeSym 10 | 11 | from deser/macroutils/generation/ser import 12 | defSerialize 13 | 14 | 15 | macro makeSerializable*( 16 | types: varargs[typedesc], 17 | public: static[bool] = false, 18 | debugOutput: static[bool] = false, 19 | debugTreeOutput: static[bool] = false 20 | ) = ##[ 21 | Generate `serialize` procedure for your type. Use `public` parameter to export. 22 | 23 | Works only for objects and ref objects. 24 | 25 | Compile with `-d:debugMakeSerializable` to see macro output. 26 | Compile with `-d:debugMakeSerializableTree` to see macro output as NimNode tree. 27 | 28 | **Example**: 29 | ```nim 30 | makeSerializable(Foo) 31 | 32 | # Use array of types if you want to make deserializable many types 33 | makeSerializable([ 34 | Foo, 35 | Bar 36 | ]) 37 | ``` 38 | ]## 39 | result = newStmtList() 40 | 41 | for typeSym in types: 42 | var struct = Struct.fromTypeSym(typeSym) 43 | 44 | result.add defSerialize(struct, public) 45 | 46 | if defined(debugMakeSerializable) or debugOutput: 47 | debugEcho result.toStrLit 48 | 49 | if defined(debugMakeSerializableTree) or debugTreeOutput: 50 | debugEcho result.treeRepr 51 | -------------------------------------------------------------------------------- /src/deser/test.nim: -------------------------------------------------------------------------------- 1 | ## A module for testing your `serialize` and `deserialize` procedures 2 | 3 | import test/[ 4 | ser, 5 | des, 6 | token 7 | ] 8 | 9 | export 10 | ser, 11 | des, 12 | token 13 | -------------------------------------------------------------------------------- /src/deser/test/des.nim: -------------------------------------------------------------------------------- 1 | ##[ 2 | 3 | ]## 4 | {.experimental: "caseStmtMacros".} 5 | import std/[ 6 | options, 7 | strformat, 8 | sugar 9 | ] 10 | 11 | import deser/des/[impls, helpers] 12 | 13 | import deser/macroutils/matching 14 | 15 | import token 16 | 17 | type 18 | Deserializer* = ref object 19 | tokens: seq[Token] 20 | 21 | SeqVisitor = object 22 | de: Deserializer 23 | len: Option[uint] 24 | endToken: Token 25 | 26 | MapVisitor = object 27 | de: Deserializer 28 | len: Option[uint] 29 | endToken: Token 30 | 31 | 32 | template endOfTokens = 33 | raise newException(AssertionDefect, "unexpected end of tokens") 34 | 35 | template unexpected(token: Token) = 36 | raise newException(AssertionDefect, &"deserialization did not expect this token: {token.kind}") 37 | 38 | func init*(Self: typedesc[Deserializer], tokens: openArray[Token]): Self = 39 | Self(tokens: @tokens) 40 | 41 | func init*(Self: typedesc[SeqVisitor | MapVisitor], de: Deserializer, len: Option[uint], endToken: Token): Self = 42 | Self(de: de, len: len, endToken: endToken) 43 | 44 | proc assertDesTokens*[T](value: T, tokens: openArray[Token]) = 45 | mixin 46 | deserialize 47 | 48 | var des = Deserializer.init tokens 49 | 50 | let res = T.deserialize(des) 51 | 52 | when T is ref: 53 | doAssert res[] == value[], &"The result is `{res}` but expected `{value}`" 54 | else: 55 | doAssert res == value, &"The result is `{res}` but expected `{value}`" 56 | 57 | doAssert des.tokens.len == 0, 58 | "The token sequence is not empty. There may have been a copy of the Deserializer instead of passing by reference." 59 | 60 | proc peekTokenOpt*(self: Deserializer): Option[Token] = 61 | if self.tokens.len == 0: 62 | none Token 63 | else: 64 | some self.tokens[0] 65 | 66 | proc peekToken*(self: Deserializer): Token = 67 | if self.tokens.len == 0: 68 | endOfTokens 69 | 70 | self.tokens[0] 71 | 72 | proc nextTokenOpt*(self: Deserializer): Option[Token] = 73 | if self.tokens.len == 0: 74 | result = none Token 75 | else: 76 | result = some self.tokens[0] 77 | self.tokens = self.tokens[1..^1] 78 | 79 | proc assertNextToken*(self: Deserializer, expected: Token) = 80 | let next = self.nextTokenOpt() 81 | 82 | if next.isSome: 83 | let tmp = next.unsafeGet 84 | if tmp != expected: 85 | raise newException(AssertionDefect, &"expected Token.{tmp.kind} but deserialization wants Token.{expected.kind}") 86 | else: 87 | raise newException(AssertionDefect, &"end of tokens byt deserialization wants Token.{expected.kind}" ) 88 | 89 | proc nextToken*(self: Deserializer): Token = 90 | if self.tokens.len == 0: 91 | endOfTokens 92 | 93 | result = self.tokens[0] 94 | self.tokens = self.tokens[1..^1] 95 | 96 | proc remaining*(self: Deserializer): int = self.tokens.len 97 | 98 | proc visitSeq*(self: var Deserializer, len: Option[int], endToken: Token, visitor: auto): visitor.Value = 99 | mixin visitSeq 100 | 101 | var sequence = SeqVisitor.init(self, len.map((x) => x.uint), endToken) 102 | 103 | result = visitor.visitSeq(sequence) 104 | 105 | assertNextToken self, endToken 106 | 107 | proc visitMap*(self: var Deserializer, len: Option[int], endToken: Token, visitor: auto): visitor.Value = 108 | mixin visitMap 109 | 110 | var map = MapVisitor.init(self, len.map((x) => x.uint), endToken) 111 | 112 | result = visitor.visitMap(map) 113 | 114 | assertNextToken self, endToken 115 | 116 | proc deserializeAny*(self: var Deserializer, visitor: auto): visitor.Value 117 | 118 | # forward to deserializeAny 119 | implDeserializer(Deserializer, public=true): 120 | self.deserializeAny(visitor) 121 | 122 | proc deserializeAny*(self: var Deserializer, visitor: auto): visitor.Value = 123 | mixin 124 | visitBool, 125 | visitInt8, 126 | visitInt16, 127 | visitInt32, 128 | visitInt64, 129 | visitUint8, 130 | visitUint16, 131 | visitUint32, 132 | visitUint64, 133 | visitFloat32, 134 | visitFloat64, 135 | visitChar, 136 | visitString, 137 | visitBytes, 138 | visitNone, 139 | visitSome 140 | 141 | let token = self.nextToken() 142 | 143 | case token.kind 144 | of Bool: 145 | visitor.visitBool(token.`bool`) 146 | of I8: 147 | visitor.visitInt8(token.i8) 148 | of I16: 149 | visitor.visitInt16(token.i16) 150 | of I32: 151 | visitor.visitInt32(token.i32) 152 | of I64: 153 | visitor.visitInt64(token.i64) 154 | of U8: 155 | visitor.visitUint8(token.u8) 156 | of U16: 157 | visitor.visitUint16(token.u16) 158 | of U32: 159 | visitor.visitUint32(token.u32) 160 | of U64: 161 | visitor.visitUint64(token.u64) 162 | of F32: 163 | visitor.visitFloat32(token.f32) 164 | of F64: 165 | visitor.visitFloat64(token.f64) 166 | of Char: 167 | visitor.visitChar(token.`char`) 168 | of String: 169 | visitor.visitString(token.`string`) 170 | of Bytes: 171 | visitor.visitBytes(token.bytes) 172 | of None: 173 | visitor.visitNone() 174 | of Some: 175 | visitor.visitSome(self) 176 | of Seq: 177 | self.visitSeq(token.seqLen, initSeqEndToken(), visitor) 178 | of Array: 179 | self.visitSeq(token.arrayLen, initArrayEndToken(), visitor) 180 | of Map: 181 | self.visitMap(token.mapLen, initMapEndToken(), visitor) 182 | of Struct: 183 | self.visitMap(some token.structLen, initStructEndToken(), visitor) 184 | else: 185 | unexpected token 186 | 187 | proc deserializeOption*(self: var Deserializer, visitor: auto): visitor.Value = 188 | mixin 189 | visitNone, 190 | visitSome 191 | 192 | let token = self.peekToken() 193 | 194 | case token.kind: 195 | of None: 196 | discard self.nextToken() 197 | visitor.visitNone() 198 | of Some: 199 | discard self.nextToken() 200 | visitor.visitSome(self) 201 | else: 202 | self.deserializeAny(visitor) 203 | 204 | proc deserializeStruct*(self: var Deserializer, name: static[string], fields: static[array], visitor: auto): visitor.Value = 205 | case self.peekToken(): 206 | of Struct(structLen: @len): 207 | assertNextToken self, initStructToken(name, len) 208 | self.visitMap(some(fields.len), initStructEndToken(), visitor) 209 | of Map(mapLen: @len): 210 | self.nextToken() 211 | self.visitMap(some(fields.len), initMapEndToken(), visitor) 212 | else: 213 | self.deserializeAny(visitor) 214 | 215 | implSeqAccess(SeqVisitor, public=true) 216 | 217 | proc nextElementSeed*(self: var SeqVisitor, seed: auto): Option[seed.Value] = 218 | mixin 219 | deserialize 220 | 221 | if self.de.peekTokenOpt() == some(self.endToken): 222 | return none(seed.Value) 223 | 224 | self.len = self.len.map((x) => x - 1) 225 | 226 | result = some seed.deserialize(self.de) 227 | 228 | proc sizeHint*(self: SeqVisitor): Option[int] = self.len.map((x) => x.int) 229 | 230 | implMapAccess(MapVisitor, public=true) 231 | 232 | proc nextKeySeed*(self: var MapVisitor, seed: auto): Option[seed.Value] = 233 | mixin 234 | deserialize 235 | 236 | if self.de.peekTokenOpt() == some(self.endToken): 237 | return none(seed.Value) 238 | 239 | self.len = self.len.map((x) => x - 1) 240 | 241 | result = some seed.deserialize(self.de) 242 | 243 | proc nextValueSeed*(self: var MapVisitor, seed: auto): seed.Value = 244 | mixin 245 | deserialize 246 | 247 | seed.deserialize(self.de) 248 | 249 | proc sizeHint*(self: MapVisitor): Option[int] = self.len.map((x) => x.int) 250 | -------------------------------------------------------------------------------- /src/deser/test/ser.nim: -------------------------------------------------------------------------------- 1 | import std/[ 2 | options 3 | ] 4 | 5 | import deser/ser/impls 6 | from deser/ser/helpers import asAddr 7 | 8 | import token 9 | 10 | 11 | type 12 | Serializer* = object 13 | tokens*: seq[Token] 14 | 15 | 16 | func init*(Self: typedesc[Serializer], tokens: openArray[Token]): Self = 17 | Self(tokens: @tokens) 18 | 19 | proc assertSerTokens*(value: auto, tokens: openArray[Token]) = 20 | mixin serialize 21 | 22 | var ser = Serializer.init tokens 23 | value.serialize(ser) 24 | # check that `ser` passed by ref 25 | doAssert ser.tokens.len == 0, 26 | "The token sequence is not empty. There may have been a copy of the Serializer instead of passing by reference." 27 | 28 | proc nextToken*(self: var Serializer): Option[Token] = 29 | if self.tokens.len == 0: 30 | result = none Token 31 | else: 32 | result = some self.tokens[0] 33 | self.tokens = self.tokens[1..^1] 34 | 35 | proc remaining*(self: Serializer): int = 36 | self.tokens.len 37 | 38 | proc assertNextToken*(ser: var Serializer, actual: Token) = 39 | let next = ser.nextToken() 40 | if next.isSome(): 41 | let value = next.get() 42 | doAssert value == actual, "Expected " & $value & " but serialized as " & $actual 43 | else: 44 | raise newException(AssertionDefect, "Expected end of tokens, but " & $actual & " was serialized") 45 | 46 | # Serializer impl 47 | proc serializeBool*(self: var Serializer, v: bool) = assertNextToken self, initBoolToken(v) 48 | 49 | proc serializeInt8*(self: var Serializer, v: int8) = assertNextToken self, initI8Token(v) 50 | 51 | proc serializeInt16*(self: var Serializer, v: int16) = assertNextToken self, initI16Token(v) 52 | 53 | proc serializeInt32*(self: var Serializer, v: int32) = assertNextToken self, initI32Token(v) 54 | 55 | proc serializeInt64*(self: var Serializer, v: int64) = assertNextToken self, initI64Token(v) 56 | 57 | proc serializeFloat32*(self: var Serializer, v: float32) = assertNextToken self, initF32Token(v) 58 | 59 | proc serializeFloat64*(self: var Serializer, v: float32) = assertNextToken self, initF64Token(v) 60 | 61 | proc serializeString*(self: var Serializer, v: openArray[char]) = assertNextToken self, initStringToken(v) 62 | 63 | proc serializeChar*(self: var Serializer, v: char) = assertNextToken self, initCharToken(v) 64 | 65 | proc serializeBytes*(self: var Serializer, v: openArray[byte]) = assertNextToken self, initBytesToken(@v) 66 | 67 | proc serializeNone*(self: var Serializer) = assertNextToken self, initNoneToken() 68 | 69 | proc serializeSome*(self: var Serializer, v: auto) = assertNextToken self, initSomeToken() 70 | 71 | proc serializeEnum*(self: var Serializer, value: enum) = assertNextToken self, initEnumToken() 72 | 73 | proc serializeArray*(self: var Serializer, len: static[int]): var Serializer = 74 | assertNextToken self, initArrayToken(some len) 75 | self 76 | 77 | proc serializeSeq*(self: var Serializer, len: Option[int]): var Serializer = 78 | assertNextToken self, initSeqToken(len) 79 | self 80 | 81 | proc serializeMap*(self: var Serializer, len: Option[int]): var Serializer = 82 | assertNextToken self, initMapToken(len) 83 | result = self 84 | 85 | proc serializeStruct*(self: var Serializer, name: static[string]): var Serializer = 86 | assertNextToken self, initStructToken(name) 87 | result = self 88 | 89 | # SerializeArray impl 90 | proc serializeArrayElement*(self: var Serializer, v: auto) = 91 | mixin serialize 92 | 93 | v.serialize(self) 94 | 95 | proc endArray*(self: var Serializer) = assertNextToken self, initArrayEndToken() 96 | 97 | 98 | # SerializeSeq impl 99 | proc serializeSeqElement*(self: var Serializer, v: auto) = 100 | mixin serialize 101 | 102 | v.serialize(self) 103 | 104 | proc endSeq*(self: var Serializer) = assertNextToken self, initSeqEndToken() 105 | 106 | # SerializeMap impl 107 | proc serializeMapKey*(self: var Serializer, key: auto) = 108 | mixin serialize 109 | 110 | key.serialize(self) 111 | 112 | proc serializeMapValue*(self: var Serializer, v: auto) = 113 | mixin serialize 114 | 115 | v.serialize(self) 116 | 117 | proc serializeMapEntry*(self: var Serializer, key: auto, value: auto) = 118 | self.serializeMapKey(key) 119 | self.serializeMapValue(value) 120 | 121 | proc endMap*(self: var Serializer) = assertNextToken self, initMapEndToken() 122 | 123 | # SerializeStruct impl 124 | proc serializeStructField*(self: var Serializer, key: static[string], v: auto) = 125 | mixin serialize 126 | 127 | key.serialize(self) 128 | v.serialize(self) 129 | 130 | proc endStruct*(self: var Serializer) = assertNextToken self, initStructEndToken() 131 | 132 | proc collectSeq*(self: var Serializer, iter: auto) = 133 | when compiles(iter.len): 134 | let length = some iter.len 135 | else: 136 | let length = none int 137 | 138 | asAddr state, self.serializeSeq(length) 139 | 140 | for value in iter: 141 | state.serializeSeqElement(value) 142 | 143 | state.endSeq() 144 | 145 | proc collectMap*(self: var Serializer, iter: auto) = 146 | when compiles(iter.len): 147 | let length = some iter.len 148 | else: 149 | let length = none int 150 | 151 | asAddr state, self.serializeMap(length) 152 | 153 | for key, value in iter: 154 | state.serializeMapEntry(key, value) 155 | 156 | state.endMap() 157 | -------------------------------------------------------------------------------- /src/deser/test/token.nim: -------------------------------------------------------------------------------- 1 | import std/[ 2 | options 3 | ] 4 | 5 | type 6 | TokenKind* = enum 7 | Bool, 8 | I8, I16, I32, I64, 9 | U8, U16, U32, U64, 10 | F32, F64, 11 | Char, String, Bytes, 12 | None, Some, 13 | Seq, SeqEnd, 14 | Array, ArrayEnd, 15 | Map, MapEnd, 16 | Struct, StructEnd, 17 | Enum 18 | 19 | Token* = object 20 | case kind*: TokenKind 21 | of Bool: 22 | `bool`*: bool 23 | of I8: 24 | i8*: int8 25 | of I16: 26 | i16*: int16 27 | of I32: 28 | i32*: int32 29 | of I64: 30 | i64*: int64 31 | of U8: 32 | u8*: uint8 33 | of U16: 34 | u16*: uint16 35 | of U32: 36 | u32*: uint32 37 | of U64: 38 | u64*: uint64 39 | of F32: 40 | f32*: float32 41 | of F64: 42 | f64*: float64 43 | of Char: 44 | `char`*: char 45 | of String: 46 | `string`*: string 47 | of Bytes: 48 | bytes*: seq[byte] 49 | of None: 50 | discard 51 | of Some: 52 | discard 53 | of Seq: 54 | seqLen*: Option[int] 55 | of SeqEnd: 56 | discard 57 | of Array: 58 | arrayLen*: Option[int] 59 | of ArrayEnd: 60 | discard 61 | of Map: 62 | mapLen*: Option[int] 63 | of MapEnd: 64 | discard 65 | of Struct: 66 | structName*: string 67 | structLen*: int 68 | of StructEnd: 69 | discard 70 | of Enum: 71 | discard 72 | 73 | proc initBoolToken*(value: bool): Token = 74 | Token(kind: Bool, `bool`: value) 75 | 76 | proc initI8Token*(value: int8): Token = 77 | Token(kind: I8, i8: value) 78 | 79 | proc initI16Token*(value: int16): Token = 80 | Token(kind: I16, i16: value) 81 | 82 | proc initI32Token*(value: int32): Token = 83 | Token(kind: I32, i32: value) 84 | 85 | proc initI64Token*(value: int64): Token = 86 | Token(kind: I64, i64: value) 87 | 88 | proc initU8Token*(value: uint8): Token = 89 | Token(kind: U8, u8: value) 90 | 91 | proc initU16Token*(value: uint16): Token = 92 | Token(kind: U16, u16: value) 93 | 94 | proc initU32Token*(value: uint32): Token = 95 | Token(kind: U32, u32: value) 96 | 97 | proc initU64Token*(value: uint64): Token = 98 | Token(kind: U64, u64: value) 99 | 100 | proc initF32Token*(value: float32): Token = 101 | Token(kind: F32, f32: value) 102 | 103 | proc initF64Token*(value: float64): Token = 104 | Token(kind: F64, f64: value) 105 | 106 | proc initCharToken*(value: char): Token = 107 | Token(kind: Char, `char`: value) 108 | 109 | proc initStringToken*(value: openArray[char]): Token = 110 | var temp = newStringOfCap(value.len) 111 | for i in value: 112 | temp.add i 113 | Token(kind: String, `string`: temp) 114 | 115 | proc initBytesToken*(value: seq[byte]): Token = 116 | Token(kind: Bytes, bytes: value) 117 | 118 | proc initNoneToken*(): Token = 119 | Token(kind: None) 120 | 121 | proc initSomeToken*(): Token = 122 | Token(kind: Some) 123 | 124 | proc initSeqToken*(len: Option[int]): Token = 125 | Token(kind: Seq, seqLen: len) 126 | 127 | proc initSeqEndToken*(): Token = 128 | Token(kind: SeqEnd) 129 | 130 | proc initArrayToken*(len: Option[int]): Token = 131 | Token(kind: Array, arrayLen: len) 132 | 133 | proc initArrayEndToken*(): Token = 134 | Token(kind: ArrayEnd) 135 | 136 | proc initMapToken*(len: Option[int]): Token = 137 | Token(kind: Map, mapLen: len) 138 | 139 | proc initMapEndToken*(): Token = 140 | Token(kind: MapEnd) 141 | 142 | proc initStructToken*(name: string, len: int): Token = 143 | Token(kind: Struct, structName: name, structLen: len) 144 | 145 | proc initStructEndToken*(): Token = 146 | Token(kind: StructEnd) 147 | 148 | proc initEnumToken*(): Token = 149 | Token(kind: Enum) 150 | 151 | proc `==`*(lhs, rhs: Token): bool = 152 | if lhs.kind == rhs.kind: 153 | case lhs.kind 154 | of Bool: 155 | lhs.`bool` == rhs.`bool` 156 | of I8: 157 | lhs.i8 == rhs.i8 158 | of I16: 159 | lhs.i16 == rhs.i16 160 | of I32: 161 | lhs.i32 == rhs.i32 162 | of I64: 163 | lhs.i64 == rhs.i64 164 | of U8: 165 | lhs.u8 == rhs.u8 166 | of U16: 167 | lhs.u16 == rhs.u16 168 | of U32: 169 | lhs.u32 == rhs.u32 170 | of U64: 171 | lhs.u64 == rhs.u64 172 | of F32: 173 | lhs.f32 == rhs.f32 174 | of F64: 175 | lhs.f64 == rhs.f64 176 | of Char: 177 | lhs.`char` == rhs.`char` 178 | of String: 179 | lhs.`string` == rhs.`string` 180 | of Bytes: 181 | lhs.bytes == rhs.bytes 182 | of None: 183 | true 184 | of Some: 185 | true 186 | of Seq: 187 | lhs.seqLen == rhs.seqLen 188 | of SeqEnd: 189 | true 190 | of Array: 191 | lhs.arrayLen == rhs.arrayLen 192 | of ArrayEnd: 193 | true 194 | of Map: 195 | lhs.mapLen == rhs.mapLen 196 | of MapEnd: 197 | true 198 | of Struct: 199 | lhs.structName == rhs.structName and lhs.structLen == rhs.structLen 200 | of StructEnd: 201 | true 202 | of Enum: 203 | true 204 | else: 205 | false -------------------------------------------------------------------------------- /tests/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") -------------------------------------------------------------------------------- /tests/des/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../../src") -------------------------------------------------------------------------------- /tests/des/timpls.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | matrix: "; -d:release; --gc:orc; -d:release --gc:orc; --threads:on" 3 | """ 4 | import std/[ 5 | unittest, 6 | options, 7 | tables, 8 | sets 9 | ] 10 | 11 | import deser/[ 12 | des, 13 | test 14 | ] 15 | 16 | 17 | suite "Deserialize default impls": 18 | test "initBoolToken": 19 | assertDesTokens true, [initBoolToken(true)] 20 | 21 | test "int": 22 | assertDesTokens 0i8, [initI8Token(0)] 23 | assertDesTokens 0i16, [initI16Token(0)] 24 | assertDesTokens 0i32, [initI32Token(0)] 25 | assertDesTokens 0i64, [initI64Token(0)] 26 | assertDesTokens 0, [initI64Token(0)] 27 | 28 | test "float": 29 | assertDesTokens 0f32, [initF32Token(0.0)] 30 | assertDesTokens 0f64, [initF64Token(0.0)] 31 | assertDesTokens 0.0, [initF64Token(0.0)] 32 | 33 | test "char": 34 | assertDesTokens 'a', [initCharToken('a')] 35 | 36 | test "enum": 37 | type SimpleEnum = enum 38 | First = "first" 39 | Second 40 | 41 | assertDesTokens SimpleEnum.First, [initI8Token(0)] 42 | assertDesTokens SimpleEnum.First, [initI16Token(0)] 43 | assertDesTokens SimpleEnum.First, [initI32Token(0)] 44 | assertDesTokens SimpleEnum.First, [initI64Token(0)] 45 | 46 | assertDesTokens SimpleEnum.First, [initStringToken("first")] 47 | assertDesTokens SimpleEnum.Second, [initStringToken("Second")] 48 | 49 | test "bytes": 50 | assertDesTokens [byte(0)], [initBytesToken(@[byte(0)])] 51 | assertDesTokens @[byte(0)], [initBytesToken(@[byte(0)])] 52 | 53 | test "set": 54 | assertDesTokens {1,2,3}, [ 55 | initSeqToken(some 3), 56 | initI64Token(1), 57 | initI64Token(2), 58 | initI64Token(3), 59 | initSeqEndToken() 60 | ] 61 | 62 | test "array": 63 | assertDesTokens [1,2,3], [ 64 | initArrayToken(some 3), 65 | initI64Token(1), 66 | initI64Token(2), 67 | initI64Token(3), 68 | initArrayEndToken() 69 | ] 70 | 71 | test "seq": 72 | assertDesTokens @[1,2,3], [ 73 | initSeqToken(some 3), 74 | initI64Token(1), 75 | initI64Token(2), 76 | initI64Token(3), 77 | initSeqEndToken() 78 | ] 79 | 80 | test "tuple": 81 | assertDesTokens (123, "123"), [ 82 | initArrayToken(some 2), 83 | initI64Token(123), 84 | initStringToken("123"), 85 | initArrayEndToken() 86 | ] 87 | 88 | test "named tuple": 89 | assertDesTokens (id: 123), [ 90 | initArrayToken(some 1), 91 | initI64Token(123), 92 | initArrayEndToken() 93 | ] 94 | 95 | test "option": 96 | assertDesTokens some 123, [ 97 | initSomeToken(), 98 | initI64Token(123) 99 | ] 100 | assertDesTokens none int, [ 101 | initNoneToken() 102 | ] 103 | 104 | test "tables": 105 | # Table | TableRef | OrderedTable | OrderedTableRef 106 | assertDesTokens {1: "1"}.toTable, [ 107 | initMapToken(some 1), 108 | initI64Token(1), 109 | initStringToken("1"), 110 | initMapEndToken() 111 | ] 112 | 113 | assertDesTokens {1: "1"}.newTable, [ 114 | initMapToken(some 1), 115 | initI64Token(1), 116 | initStringToken("1"), 117 | initMapEndToken() 118 | ] 119 | 120 | assertDesTokens {1: "1"}.toOrderedTable, [ 121 | initMapToken(some 1), 122 | initI64Token(1), 123 | initStringToken("1"), 124 | initMapEndToken() 125 | ] 126 | 127 | assertDesTokens {1: "1"}.newOrderedTable, [ 128 | initMapToken(some 1), 129 | initI64Token(1), 130 | initStringToken("1"), 131 | initMapEndToken() 132 | ] 133 | 134 | test "sets": 135 | # HashSet | OrderedSet 136 | assertDesTokens [1].toHashSet, [ 137 | initSeqToken(some 1), 138 | initI64Token(1), 139 | initSeqEndToken() 140 | ] 141 | 142 | assertDesTokens [1].toOrderedSet, [ 143 | initSeqToken(some 1), 144 | initI64Token(1), 145 | initSeqEndToken() 146 | ] 147 | -------------------------------------------------------------------------------- /tests/des/tmake.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | matrix: "; -d:release; --gc:orc; -d:release --gc:orc; --threads:on" 3 | """ 4 | import std/[ 5 | unittest, 6 | times, 7 | options, 8 | strformat, 9 | typetraits 10 | ] 11 | 12 | import deser/[ 13 | des, 14 | pragmas, 15 | test, 16 | helpers 17 | ] 18 | 19 | 20 | proc fromTimestamp(deserializer: var auto): Time = 21 | fromUnix(deserialize(int64, deserializer)) 22 | 23 | proc raiseError(objName, fieldValue: auto) = 24 | raise newException(ValueError, &"Unknown field `{fieldValue}`") 25 | 26 | proc myDefault[T](): T = 27 | when T is int: 28 | 123 29 | elif T is string: 30 | "hello" 31 | else: 32 | {.error.} 33 | 34 | 35 | type 36 | EmptyObject = object 37 | 38 | Object = object 39 | id*: int 40 | 41 | GenericObject[T] = object 42 | id: T 43 | 44 | RefObject = ref object 45 | id: int 46 | 47 | ObjectWithRef = object 48 | id: ref int 49 | 50 | InheritObject {.renameAll: SnakeCase.} = object of RootObj 51 | id* {.renamed: "i".}: int 52 | 53 | CaseObject = object 54 | case kind: bool 55 | of true: 56 | yes: string 57 | else: 58 | discard 59 | 60 | UntaggedCaseObject = object 61 | case kind {.untagged.}: bool 62 | of true: 63 | yes: string 64 | of false: 65 | discard 66 | 67 | SkipObject = object 68 | alwaysSkipped {.skipped.}: int 69 | serializeSkipped {.skipDeserializing.}: int 70 | 71 | DeserializeWithObject = object 72 | date {.deserializeWith(fromTimestamp).}: Time 73 | 74 | RenameObject = object 75 | name {.renameDeserialize("fullname").}: string 76 | kek {.renamed("lol").}: string 77 | 78 | DefaultObject = object 79 | id {.defaultValue(123).}: int 80 | integer {.defaultValue.}: int 81 | fun {.defaultValue(myDefault).}: int 82 | 83 | OnUnknownObject {.onUnknownKeys(raiseError).} = object 84 | 85 | RenameAllObject {.renameAll(SnakeCase).} = object 86 | text: string 87 | firstName {.renameDeserialize("firstName").}: string 88 | 89 | case kind: bool 90 | of true: 91 | lastName: string 92 | else: 93 | discard 94 | 95 | ChildObject = object of InheritObject 96 | text: string 97 | 98 | ChildGenericObject[T] = object of InheritObject 99 | text: T 100 | 101 | ChildGenericToObject = object of ChildGenericObject[string] 102 | 103 | ChildRefObject = ref object of InheritObject 104 | text: string 105 | 106 | ChildOfRefObject = object of ChildRefObject 107 | 108 | InfectedChild = object of InheritObject 109 | firstName: string 110 | 111 | SkipAllPrivateObject {.skipPrivate.} = object 112 | public*: int 113 | private: int 114 | 115 | SkipDesPrivateObject {.skipPrivateDeserializing.} = object 116 | public*: int 117 | private: int 118 | 119 | MultiCaseObject = object 120 | case kind: bool 121 | of true: 122 | yes: string 123 | else: 124 | no: string 125 | 126 | case kind2: bool 127 | of true: 128 | yes2: string 129 | else: 130 | no2: string 131 | 132 | MultiCaseObjectUntagged = object 133 | case kind {.untagged.}: bool 134 | of true: 135 | yes: string 136 | of false: 137 | no: string 138 | 139 | case kind2: bool 140 | of true: 141 | yes2: string 142 | else: 143 | no2: string 144 | 145 | MultiCaseObjectAllUntagged = object 146 | case kind {.untagged.}: bool 147 | of true: 148 | yes: string 149 | of false: 150 | no: string 151 | 152 | case kind2 {.untagged.}: bool 153 | of true: 154 | yes2: string 155 | of false: 156 | no2: string 157 | 158 | RenameWithCase = object 159 | lolKek {.renameDeserialize(SnakeCase).}: string 160 | kekLol {.renamed(SnakeCase).}: string 161 | 162 | CaseObjectMultiBranchKind = enum 163 | First, Second, Third, Fourth 164 | 165 | CaseObjectMultiBranch = object 166 | case kind: CaseObjectMultiBranchKind 167 | of First, Second: 168 | first: string 169 | of Third, Fourth: 170 | second: string 171 | 172 | AliasesPragma = object 173 | nickName {.aliases("username", "name", SnakeCase).}: string 174 | 175 | AliasesWithRenameAllPragma {.renameAll(SnakeCase).} = object 176 | nickName {.aliases("username", "name").}: string 177 | 178 | ObjectWithRequiresInit {.requiresInit.} = object 179 | text: string 180 | 181 | Quotes = object 182 | `first`: string 183 | `second`*: string 184 | `third` {.skipped.}: string 185 | `fourth`* {.skipped.}: string 186 | 187 | DuplicateCheck = object 188 | field: int8 189 | 190 | DeserWith = object 191 | created {.deserWith(UnixTimeFormat).}: Time 192 | 193 | EmptyDefaultValue {.defaultValue.} = object 194 | id: int 195 | text: string 196 | 197 | DefaultValueFun {.defaultValue(myDefault).} = object 198 | id: int 199 | text: string 200 | 201 | proc `==`*(x, y: ObjectWithRef): bool = x.id[] == y.id[] 202 | 203 | proc `==`*(x, y: CaseObject | UntaggedCaseObject): bool = 204 | if x.kind == y.kind: 205 | if x.kind == true and y.kind == true: 206 | return x.yes == y.yes 207 | return true 208 | return false 209 | 210 | proc `==`*(x, y: RenameAllObject): bool = 211 | if x.kind == y.kind and x.text == y.text and x.firstName == y.firstName: 212 | if x.kind == true and y.kind == true: 213 | return x.lastName == y.lastName 214 | return true 215 | return false 216 | 217 | proc `==`*(x, y: MultiCaseObject | MultiCaseObjectUntagged | MultiCaseObjectAllUntagged): bool = 218 | if x.kind == y.kind and x.kind2 == y.kind2: 219 | case x.kind 220 | of true: 221 | if x.kind2: 222 | return x.yes == y.yes and x.yes2 == y.yes2 223 | else: 224 | return x.yes == y.yes and x.no2 == y.no2 225 | of false: 226 | if x.kind2: 227 | return x.no == y.no and x.yes2 == y.yes2 228 | else: 229 | return x.no == y.no and x.no2 == y.no2 230 | return false 231 | 232 | proc `==`*(x, y: CaseObjectMultiBranch): bool = 233 | if x.kind == y.kind: 234 | case x.kind 235 | of First, Second: 236 | return x.first == y.first 237 | of Third, Fourth: 238 | return x.second == y.second 239 | return false 240 | 241 | proc `$`*(x: ref): string = $x[] 242 | 243 | makeDeserializable([ 244 | EmptyObject, 245 | Object, 246 | GenericObject, 247 | RefObject, 248 | ObjectWithRef, 249 | InheritObject, 250 | CaseObject, 251 | UntaggedCaseObject, 252 | SkipObject, 253 | DeserializeWithObject, 254 | RenameObject, 255 | DefaultObject, 256 | OnUnknownObject, 257 | RenameAllObject, 258 | ChildObject, 259 | ChildGenericObject, 260 | ChildGenericToObject, 261 | ChildRefObject, 262 | ChildOfRefObject, 263 | InfectedChild, 264 | SkipAllPrivateObject, 265 | SkipDesPrivateObject, 266 | MultiCaseObject, 267 | MultiCaseObjectUntagged, 268 | MultiCaseObjectAllUntagged, 269 | RenameWithCase, 270 | CaseObjectMultiBranch, 271 | AliasesPragma, 272 | AliasesWithRenameAllPragma, 273 | ObjectWithRequiresInit, 274 | Quotes, 275 | DeserWith, 276 | EmptyDefaultValue, 277 | DefaultValueFun, 278 | ], public=true) 279 | 280 | makeDeserializable([DuplicateCheck], public=true, duplicateCheck=false) 281 | 282 | suite "makeDeserializable": 283 | test "Deserialize at CT": 284 | static: 285 | assertDesTokens EmptyObject(), [ 286 | initStructToken("EmptyObject", 0), 287 | initStructEndToken() 288 | ] 289 | 290 | test "EmptyObject": 291 | assertDesTokens EmptyObject(), [ 292 | initStructToken("EmptyObject", 0), 293 | initStructEndToken() 294 | ] 295 | 296 | test "Object": 297 | assertDesTokens Object(id: 123), [ 298 | initStructToken("Object", 1), 299 | initStringToken("id"), 300 | initI64Token(123), 301 | initStructEndToken() 302 | ] 303 | 304 | test "GenericObject": 305 | assertDesTokens GenericObject[int](id: 123), [ 306 | initStructToken("GenericObject", 1), 307 | initStringToken("id"), 308 | initI64Token(123), 309 | initStructEndToken() 310 | ] 311 | 312 | test "RefObject": 313 | assertDesTokens RefObject(id: 123), [ 314 | initStructToken("RefObject", 1), 315 | initStringToken("id"), 316 | initI64Token(123), 317 | initStructEndToken() 318 | ] 319 | 320 | test "ObjectWithRef": 321 | let temp = new int 322 | temp[] = 123 323 | assertDesTokens ObjectWithRef(id: temp), [ 324 | initStructToken("ObjectWithRef", 1), 325 | initStringToken("id"), 326 | initI64Token(123), 327 | initStructEndToken() 328 | ] 329 | 330 | test "InheritObject": 331 | assertDesTokens InheritObject(id: 123), [ 332 | initStructToken("InheritObject", 1), 333 | initStringToken("i"), 334 | initI64Token(123), 335 | initStructEndToken() 336 | ] 337 | 338 | test "CaseObject": 339 | assertDesTokens CaseObject(kind: true), [ 340 | initMapToken(none int), 341 | initStringToken("kind"), 342 | initBoolToken(true), 343 | initStringToken("yes"), 344 | initStringToken(""), 345 | initMapEndToken() 346 | ] 347 | 348 | assertDesTokens CaseObject(kind: false), [ 349 | initMapToken(none int), 350 | initStringToken("kind"), 351 | initBoolToken(false), 352 | initMapEndToken() 353 | ] 354 | 355 | 356 | test "UntaggedCaseObject": 357 | assertDesTokens UntaggedCaseObject(kind: true), [ 358 | initMapToken(none int), 359 | initStringToken("yes"), 360 | initStringToken(""), 361 | initMapEndToken() 362 | ] 363 | 364 | assertDesTokens UntaggedCaseObject(kind: false), [ 365 | initMapToken(none int), 366 | initMapEndToken() 367 | ] 368 | 369 | test "SkipObject": 370 | assertDesTokens SkipObject(), [ 371 | initStructToken("SkipObject", 0), 372 | initStructEndToken() 373 | ] 374 | 375 | test "DeserializeWithObject": 376 | assertDesTokens DeserializeWithObject(date: fromUnix(123)), [ 377 | initStructToken("DeserializeWithObject", 1), 378 | initStringToken("date"), 379 | initI64Token(123), 380 | initStructEndToken() 381 | ] 382 | 383 | test "RenameObject": 384 | assertDesTokens RenameObject(name: "123", kek: "123"), [ 385 | initStructToken("RenameObject", 1), 386 | initStringToken("fullname"), 387 | initStringToken("123"), 388 | initStringToken("lol"), 389 | initStringToken("123"), 390 | initStructEndToken() 391 | ] 392 | 393 | test "DefaultObject": 394 | assertDesTokens DefaultObject(id: 123, integer: 0, fun: 123), [ 395 | initStructToken("DefaultObject", 1), 396 | initStructEndToken() 397 | ] 398 | 399 | # crash on "-d:release --gc:refc" 400 | #[ 401 | test "OnUnknownObject": 402 | expect(ValueError): 403 | assertDesTokens OnUnknownObject(), [ 404 | initStructToken("OnUnknownObject", 1), 405 | initStringToken("test"), 406 | initStringToken("123"), 407 | initStructEndToken() 408 | ] 409 | ]# 410 | 411 | test "Ignore extra fields": 412 | assertDesTokens Object(id: 123), [ 413 | initStructToken("Object", 1), 414 | initStringToken("id"), 415 | initI64Token(123), 416 | initStringToken("text"), 417 | initStringToken("text"), 418 | initStructEndToken() 419 | ] 420 | 421 | test "RenameAllObject": 422 | assertDesTokens RenameAllObject(kind: true), [ 423 | initStructToken("RenameAllObject", 2), 424 | initStringToken("text"), 425 | initStringToken(""), 426 | initStringToken("firstName"), 427 | initStringToken(""), 428 | initStringToken("kind"), 429 | initBoolToken(true), 430 | initStringToken("last_name"), 431 | initStringToken(""), 432 | initStructEndToken() 433 | ] 434 | 435 | test "ChildObject": 436 | assertDesTokens ChildObject(id: 123, text: "123"), [ 437 | initStructToken("ChildObject", 2), 438 | initStringToken("i"), 439 | initI64Token(123), 440 | initStringToken("text"), 441 | initStringToken("123"), 442 | initStructEndToken() 443 | ] 444 | 445 | test "ChildGenericObject": 446 | assertDesTokens ChildGenericObject[string](id: 123, text: "123"), [ 447 | initStructToken("ChildGenericObject", 2), 448 | initStringToken("i"), 449 | initI64Token(123), 450 | initStringToken("text"), 451 | initStringToken("123"), 452 | initStructEndToken() 453 | ] 454 | 455 | test "ChildRefObject": 456 | assertDesTokens ChildRefObject(id: 123, text: "123"), [ 457 | initStructToken("ChildRefObject", 2), 458 | initStringToken("i"), 459 | initI64Token(123), 460 | initStringToken("text"), 461 | initStringToken("123"), 462 | initStructEndToken() 463 | ] 464 | 465 | test "ChildGenericToObject": 466 | assertDesTokens ChildGenericToObject(id: 123, text: "123"), [ 467 | initStructToken("ChildGenericToObject", 2), 468 | initStringToken("i"), 469 | initI64Token(123), 470 | initStringToken("text"), 471 | initStringToken("123"), 472 | initStructEndToken() 473 | ] 474 | 475 | test "ChildOfRefObject": 476 | assertDesTokens ChildOfRefObject(id: 123, text: "123"), [ 477 | initStructToken("ChildOfRefObject", 2), 478 | initStringToken("i"), 479 | initI64Token(123), 480 | initStringToken("text"), 481 | initStringToken("123"), 482 | initStructEndToken() 483 | ] 484 | 485 | test "InfectedChild": 486 | assertDesTokens InfectedChild(id: 123, firstName: "123"), [ 487 | initStructToken("InfectedChild", 2), 488 | initStringToken("i"), 489 | initI64Token(123), 490 | initStringToken("first_name"), 491 | initStringToken("123"), 492 | initStructEndToken() 493 | ] 494 | 495 | test "SkipAllPrivateObject": 496 | assertDesTokens SkipAllPrivateObject(public: 123), [ 497 | initStructToken("SkipAllPrivateObject", 1), 498 | initStringToken("public"), 499 | initI64Token(123), 500 | initStructEndToken() 501 | ] 502 | 503 | test "SkipDesPrivateObject": 504 | assertDesTokens SkipDesPrivateObject(public: 123), [ 505 | initStructToken("SkipAllPrivateObject", 1), 506 | initStringToken("public"), 507 | initI64Token(123), 508 | initStructEndToken() 509 | ] 510 | 511 | test "MultiCaseObject": 512 | assertDesTokens MultiCaseObject(kind: true, yes: "yes", kind2: false, no2: "no"), [ 513 | initMapToken(none int), 514 | initStringToken("kind"), 515 | initBoolToken(true), 516 | initStringToken("yes"), 517 | initStringToken("yes"), 518 | initStringToken("kind2"), 519 | initBoolToken(false), 520 | initStringToken("no2"), 521 | initStringToken("no"), 522 | initMapEndToken() 523 | ] 524 | 525 | test "MultiCaseObjectUntagged": 526 | assertDesTokens MultiCaseObjectUntagged(kind: true, yes: "yes", kind2: false, no2: "no"), [ 527 | initMapToken(none int), 528 | initStringToken("yes"), 529 | initStringToken("yes"), 530 | initStringToken("kind2"), 531 | initBoolToken(false), 532 | initStringToken("no2"), 533 | initStringToken("no"), 534 | initMapEndToken() 535 | ] 536 | 537 | test "MultiCaseObjectAllUntagged": 538 | assertDesTokens MultiCaseObjectAllUntagged(kind: true, yes: "yes", kind2: false, no2: "no"), [ 539 | initMapToken(none int), 540 | initStringToken("yes"), 541 | initStringToken("yes"), 542 | initStringToken("no2"), 543 | initStringToken("no"), 544 | initMapEndToken() 545 | ] 546 | 547 | test "RenameWithCase": 548 | assertDesTokens RenameWithCase(), [ 549 | initMapToken(none int), 550 | initStringToken("lol_kek"), 551 | initStringToken(""), 552 | initStringToken("kek_lol"), 553 | initStringToken(""), 554 | initMapEndToken() 555 | ] 556 | 557 | test "CaseObjectMultiBranch": 558 | assertDesTokens CaseObjectMultiBranch(kind: First, first: "123"), [ 559 | initMapToken(none int), 560 | initStringToken("kind"), 561 | initStringToken("First"), 562 | initStringToken("first"), 563 | initStringToken("123"), 564 | initMapEndToken() 565 | ] 566 | 567 | assertDesTokens CaseObjectMultiBranch(kind: Third, second: "123"), [ 568 | initMapToken(none int), 569 | initStringToken("kind"), 570 | initStringToken("Third"), 571 | initStringToken("second"), 572 | initStringToken("123"), 573 | initMapEndToken() 574 | ] 575 | 576 | test "AliasesPragma": 577 | assertDesTokens AliasesPragma(nickName: "Name"), [ 578 | initStructToken("AliasesPragma", 1), 579 | initStringToken("name"), 580 | initStringToken("Name"), 581 | initStructEndToken() 582 | ] 583 | 584 | assertDesTokens AliasesPragma(nickName: "Name"), [ 585 | initStructToken("AliasesPragma", 1), 586 | initStringToken("username"), 587 | initStringToken("Name"), 588 | initStructEndToken() 589 | ] 590 | 591 | assertDesTokens AliasesPragma(nickName: "Name"), [ 592 | initStructToken("AliasesPragma", 1), 593 | initStringToken("nick_name"), 594 | initStringToken("Name"), 595 | initStructEndToken() 596 | ] 597 | 598 | assertDesTokens AliasesPragma(nickName: "Name"), [ 599 | initStructToken("AliasesPragma", 1), 600 | initStringToken("nickName"), 601 | initStringToken("Name"), 602 | initStructEndToken() 603 | ] 604 | 605 | test "AliasesWithRenameAllPragma": 606 | assertDesTokens AliasesWithRenameAllPragma(nickName: "Name"), [ 607 | initStructToken("AliasesWithRenameAllPragma", 1), 608 | initStringToken("name"), 609 | initStringToken("Name"), 610 | initStructEndToken() 611 | ] 612 | 613 | assertDesTokens AliasesWithRenameAllPragma(nickName: "Name"), [ 614 | initStructToken("AliasesWithRenameAllPragma", 1), 615 | initStringToken("username"), 616 | initStringToken("Name"), 617 | initStructEndToken() 618 | ] 619 | 620 | doAssertRaises(MissingField): 621 | assertDesTokens AliasesWithRenameAllPragma(nickName: "Name"), [ 622 | initStructToken("AliasesWithRenameAllPragma", 1), 623 | initStringToken("nick_name"), 624 | initStringToken("Name"), 625 | initStructEndToken() 626 | ] 627 | 628 | assertDesTokens AliasesWithRenameAllPragma(nickName: "Name"), [ 629 | initStructToken("AliasesWithRenameAllPragma", 1), 630 | initStringToken("nickName"), 631 | initStringToken("Name"), 632 | initStructEndToken() 633 | ] 634 | 635 | test "ObjectWithRequiresInit": 636 | assertDesTokens ObjectWithRequiresInit(text: "123"), [ 637 | initStructToken("ObjectWithRequiresInit", 1), 638 | initStringToken("text"), 639 | initStringToken("123"), 640 | initStructEndToken() 641 | ] 642 | 643 | test "Quotes": 644 | assertDesTokens Quotes(first: "1", second: "2"), [ 645 | initStructToken("Quotes", 2), 646 | initStringToken("first"), 647 | initStringToken("1"), 648 | initStringToken("second"), 649 | initStringToken("2"), 650 | initStructEndToken() 651 | ] 652 | 653 | test "Duplicate check": 654 | doAssertRaises(DuplicateField): 655 | assertDesTokens Object(id: 123), [ 656 | initStructToken("Object", 1), 657 | initStringToken("id"), 658 | initI64Token(123), 659 | initStringToken("id"), 660 | initI64Token(123), 661 | initStructEndToken() 662 | ] 663 | 664 | test "Disable duplicate check": 665 | assertDesTokens DuplicateCheck(field: 10), [ 666 | initStructToken("DuplicateCheck", 1), 667 | initStringToken("field"), 668 | initI8Token(0), 669 | initStringToken("field"), 670 | initI8Token(10), 671 | initStructEndToken() 672 | ] 673 | 674 | test "DeserWith": 675 | assertDesTokens DeserWith(created: fromUnix(123)), [ 676 | initStructToken("DeserWith", 1), 677 | initStringToken("created"), 678 | initI64Token(123), 679 | initStructEndToken() 680 | ] 681 | 682 | test "Empty default value on object": 683 | assertDesTokens EmptyDefaultValue(id: 0, text: ""), [ 684 | initStructToken("EmptyDefaultValue", 2), 685 | initStructEndToken() 686 | ] 687 | 688 | test "Fun default value on object": 689 | assertDesTokens DefaultValueFun(id: 123, text: "hello"), [ 690 | initStructToken("DefaultValueFun", 2), 691 | initStructEndToken() 692 | ] 693 | -------------------------------------------------------------------------------- /tests/macroutils/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../../src") -------------------------------------------------------------------------------- /tests/macroutils/tparsepragma.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | action: "compile" 3 | """ 4 | import std/[macros, enumerate, tables] 5 | import deser/macroutils/matching 6 | 7 | import deser/macroutils/parsing/pragmas 8 | 9 | 10 | template test(a = 123, b = 123) {.pragma.} 11 | template foo() {.pragma.} 12 | 13 | type Test = object 14 | first {.test.}: int 15 | second {.test().}: int 16 | third {.test: 123.}: int 17 | fourth {.test(123, 321).}: int 18 | fifth {.foo.}: int 19 | sixth {.test, test(), test: 123, test(123, 321), foo, foo().}: int 20 | 21 | macro run() = 22 | let recList = Test.getTypeInst().getImpl()[2][2] 23 | assertKind recList, {nnkRecList} 24 | 25 | for identDef in recList: 26 | let 27 | fieldName = identDef[0][0].strVal 28 | pragma = identDef[0][1] 29 | 30 | let checks = 31 | block: 32 | let 33 | emptyValues = newSeqOfCap[NimNode](0) 34 | defaultTestValues = @[newLit 123, newLit 123] 35 | testSym = bindSym"test" 36 | fooSym = bindSym"foo" 37 | 38 | case fieldName 39 | of "first", "second": 40 | {0: (testSym, defaultTestValues)}.toTable 41 | of "third": 42 | {0: (testSym, defaultTestValues)}.toTable 43 | of "fourth": 44 | {0: (testSym, @[newLit 123, newLit 321])}.toTable 45 | of "fifth": 46 | {0: (fooSym, emptyValues)}.toTable 47 | of "sixth": 48 | { 49 | 0: (testSym, defaultTestValues), 50 | 1: (testSym, defaultTestValues), 51 | 2: (testSym, defaultTestValues), 52 | 3: (testSym, @[newLit 123, newLit 321]), 53 | 4: (fooSym, emptyValues), 54 | 5: (fooSym, emptyValues) 55 | }.toTable 56 | else: 57 | raise newException(ValueError, "Unknown field") 58 | 59 | for num, (sym, values) in enumerate(pragma.parsePragma): 60 | let (checkSym, checkValues) = checks[num] 61 | doAssert sym == checkSym 62 | doAssert values == checkValues 63 | 64 | run() -------------------------------------------------------------------------------- /tests/macroutils/tparsestruct.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | action: "compile" 3 | """ 4 | import std/[ 5 | macros, 6 | options 7 | ] 8 | 9 | from deser/macroutils/types as macro_types import 10 | TypeInfo, 11 | initTypeInfo, 12 | recList, 13 | pragma, 14 | 15 | Struct, 16 | initStruct, 17 | genericParams, 18 | 19 | StructFeatures, 20 | initStructFeatures, 21 | initEmptyStructFeatures, 22 | 23 | Field, 24 | isCase, 25 | branches, 26 | public, 27 | features, 28 | fields, 29 | aliases, 30 | renameSerialize, 31 | renameDeserialize, 32 | `skipSerializing=`, 33 | `skipDeserializing=`, 34 | `renameSerialize=`, 35 | `renameDeserialize=`, 36 | `defaultValue=` 37 | 38 | import deser/macroutils/matching 39 | import deser/macroutils/parsing/struct 40 | import deser/pragmas 41 | 42 | 43 | template test() {.pragma.} 44 | 45 | type 46 | First {.test.} = object of RootObj 47 | id: int 48 | 49 | Second = object of First 50 | text: string 51 | 52 | Third = ref object of Second 53 | 54 | Fourth[T] = object 55 | 56 | Fifth {.onUnknownKeys(test), renameAll(CamelCase), skipPrivate, defaultValue.} = object 57 | 58 | macro run() = 59 | let 60 | firstTypeDef = First.getTypeInst().getImpl() 61 | secondTypeDef = Second.getTypeInst().getImpl() 62 | 63 | firstObjectTy = firstTypeDef[2] 64 | secondObjectTy = secondTypeDef[2] 65 | 66 | firstRecList = firstObjectTy[2] 67 | secondRecList = secondObjectTy[2] 68 | 69 | block: 70 | let 71 | mergedRecList = mergeRecList(firstRecList, secondRecList) 72 | checkRecList = nnkRecList.newTree( 73 | nnkIdentDefs.newTree( 74 | ident "id", 75 | bindSym "int", 76 | newEmptyNode() 77 | ), 78 | nnkIdentDefs.newTree( 79 | ident "text", 80 | bindSym "string", 81 | newEmptyNode() 82 | ) 83 | ) 84 | 85 | doAssert mergedRecList == checkRecList, mergedRecList.treeRepr() 86 | 87 | block: 88 | doAssertRaises(AssertionDefect): 89 | discard mergeRecList(newEmptyNode(), nnkRecList.newTree()) 90 | 91 | doAssertRaises(AssertionDefect): 92 | discard mergeRecList(nnkRecList.newTree(), newEmptyNode()) 93 | 94 | block: 95 | let typeInfo = Typeinfo.fromTypeSym(First.getTypeInst()) 96 | 97 | doAssert typeInfo.recList.get() == nnkRecList.newTree( 98 | nnkIdentDefs.newTree( 99 | ident "id", 100 | bindSym "int", 101 | newEmptyNode() 102 | ) 103 | ), typeInfo.recList.get().treeRepr() 104 | 105 | block: 106 | let typeInfo = Typeinfo.fromTypeSym(Second.getTypeInst()) 107 | 108 | doAssert typeInfo.recList.get() == nnkRecList.newTree( 109 | nnkIdentDefs.newTree( 110 | ident "id", 111 | bindSym "int", 112 | newEmptyNode() 113 | ), 114 | nnkIdentDefs.newTree( 115 | ident "text", 116 | bindSym "string", 117 | newEmptyNode() 118 | ) 119 | ), typeInfo.recList.get().treeRepr() 120 | 121 | block: 122 | let typeInfo = Typeinfo.fromTypeSym(Third.getTypeInst()) 123 | 124 | doAssert typeInfo.recList.get() == nnkRecList.newTree( 125 | nnkIdentDefs.newTree( 126 | ident "id", 127 | bindSym "int", 128 | newEmptyNode() 129 | ), 130 | nnkIdentDefs.newTree( 131 | ident "text", 132 | bindSym "string", 133 | newEmptyNode() 134 | ) 135 | ), typeInfo.recList.get().treeRepr() 136 | 137 | block: 138 | let typeInfo = Typeinfo.fromTypeSym(Fourth.getTypeInst()) 139 | 140 | doAssert typeInfo.recList.isNone 141 | 142 | block: 143 | let 144 | firstType = Typeinfo.fromTypeSym(First.getTypeInst()) 145 | fourthType = Typeinfo.fromTypeSym(Fourth.getTypeInst()) 146 | 147 | doAssert firstType.genericParams.isNone 148 | assertKind fourthType.genericParams.get()[0], {nnkSym} 149 | 150 | block: 151 | let 152 | firstType = Typeinfo.fromTypeSym(First.getTypeInst()) 153 | fourthType = Typeinfo.fromTypeSym(Fourth.getTypeInst()) 154 | 155 | doAssert firstType.pragma.get()[0] == bindSym "test", firstType.pragma.get().treeRepr() 156 | doAssert fourthType.pragma.isNone 157 | 158 | block: 159 | let 160 | firstType = Typeinfo.fromTypeSym(First.getTypeInst()) 161 | fifthType = Typeinfo.fromTypeSym(Fifth.getTypeInst()) 162 | 163 | doAssert StructFeatures.fromPragma(firstType.pragma) == initEmptyStructFeatures() 164 | 165 | doAssert StructFeatures.fromPragma(fifthType.pragma) == initStructFeatures( 166 | onUnknownKeys=some bindSym "test", 167 | renameAll=some bindSym "CamelCase", 168 | skipPrivateSerializing=true, 169 | skipPrivateDeserializing=true, 170 | defaultValue=some newEmptyNode() 171 | ), $StructFeatures.fromPragma(fifthType.pragma) 172 | 173 | block: 174 | doAssertRaises(AssertionDefect): 175 | discard StructFeatures.fromPragma(some newEmptyNode()) 176 | 177 | run() -------------------------------------------------------------------------------- /tests/macroutils/ttypes.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | action: "compile" 3 | """ 4 | import std/[macros, options] 5 | 6 | import deser/macroutils/types 7 | import deser/pragmas 8 | 9 | 10 | type Test = object 11 | 12 | macro run = 13 | block: 14 | doAssertRaises(AssertionDefect): 15 | discard initStruct( 16 | typeSym=ident"Test", 17 | fields=newSeqOfCap[Field](0), 18 | features=initEmptyStructFeatures(), 19 | genericParams=none NimNode 20 | ) 21 | 22 | doAssertRaises(AssertionDefect): 23 | discard initStruct( 24 | typeSym=ident"Test", 25 | fields=newSeqOfCap[Field](0), 26 | features=initEmptyStructFeatures(), 27 | genericParams=some newStmtList() 28 | ) 29 | 30 | block: 31 | doAssertRaises(AssertionDefect): 32 | discard initField( 33 | nameIdent=newStmtList(), 34 | typeNode=bindSym"Test", 35 | features=initEmptyFieldFeatures(), 36 | public=false, 37 | isCase=false, 38 | branches=newSeqOfCap[FieldBranch](0) 39 | ) 40 | 41 | doAssertRaises(AssertionDefect): 42 | discard initField( 43 | nameIdent=ident"Test", 44 | typeNode=newStmtList(), 45 | features=initEmptyFieldFeatures(), 46 | public=false, 47 | isCase=false, 48 | branches=newSeqOfCap[FieldBranch](0) 49 | ) 50 | 51 | doAssertRaises(AssertionDefect): 52 | let field = initField( 53 | nameIdent=ident"Test", 54 | typeNode=bindSym"Test", 55 | features=initEmptyFieldFeatures(), 56 | public=false, 57 | isCase=false, 58 | branches=newSeqOfCap[FieldBranch](0) 59 | ) 60 | discard branches(field) 61 | 62 | block: 63 | var 64 | emptyBranch = initFieldBranch( 65 | fields=newSeqOfCap[Field](0), 66 | conditionOfBranch=some nnkOfBranch.newTree() 67 | ) 68 | 69 | nestedField = initField( 70 | nameIdent=ident"nested", 71 | typeNode=bindSym"Test", 72 | features=initEmptyFieldFeatures(), 73 | public=false, 74 | isCase=true, 75 | branches=(@[emptyBranch, emptyBranch]) 76 | ) 77 | 78 | firstFieldBranches = @[ 79 | emptyBranch, 80 | initFieldBranch( 81 | fields=(@[nestedField]), 82 | conditionOfBranch=some nnkOfBranch.newTree() 83 | ) 84 | ] 85 | 86 | firstField = initField( 87 | nameIdent=ident"first", 88 | typeNode=bindSym"Test", 89 | features=initEmptyFieldFeatures(), 90 | public=false, 91 | isCase=true, 92 | branches=firstFieldBranches 93 | ) 94 | 95 | secondField = initField( 96 | nameIdent=ident"second", 97 | typeNode=bindSym"Test", 98 | features=initEmptyFieldFeatures(), 99 | public=false, 100 | isCase=true, 101 | branches=(@[emptyBranch, emptyBranch]) 102 | ) 103 | 104 | firstField.merge(secondField) 105 | 106 | #[ 107 | BEFORE: 108 | 109 | type Some = object 110 | case first: Test 111 | of ...: 112 | discard 113 | of ...: 114 | case nested: Test 115 | of ...: 116 | discard 117 | of ...: 118 | discard 119 | 120 | case second: Test 121 | of ...: 122 | discard 123 | of ...: 124 | discard 125 | 126 | AFTER: 127 | 128 | type Some = object 129 | case first: Test 130 | of ...: 131 | case second: Test 132 | of ...: 133 | discard 134 | of ...: 135 | discard 136 | of ...: 137 | case nested: Test 138 | of ...: 139 | case second: Test 140 | of ...: 141 | discard 142 | of ...: 143 | discard 144 | of ...: 145 | case second: Test 146 | of ...: 147 | discard 148 | of ...: 149 | discard 150 | ]# 151 | doAssert firstField.branches[0].fields[0].nameIdent == ident"second" 152 | doAssert firstField.branches[1].fields.len == 1 153 | doAssert firstField.branches[1].fields[0].branches[0].fields[0].nameIdent == ident"second" 154 | doAssert firstField.branches[1].fields[0].branches[1].fields[0].nameIdent == ident"second" 155 | 156 | block: 157 | doAssertRaises(AssertionDefect): 158 | discard initFieldBranch( 159 | fields=newSeqOfCap[Field](0), 160 | conditionOfBranch=some newStmtList() 161 | ) 162 | 163 | doAssertRaises(AssertionDefect): 164 | let branch = initFieldBranch( 165 | fields=newSeqOfCap[Field](0), 166 | conditionOfBranch=none NimNode 167 | ) 168 | 169 | discard conditionOfBranch(branch) 170 | 171 | discard initFieldBranch( 172 | fields=newSeqOfCap[Field](0), 173 | conditionOfBranch=none NimNode 174 | ) 175 | 176 | block: 177 | doAssertRaises(AssertionDefect): 178 | discard initTypeInfo( 179 | typeSym=ident"Test", 180 | pragma=none NimNode, 181 | recList=none NimNode, 182 | genericParams=none NimNode 183 | ) 184 | 185 | doAssertRaises(AssertionDefect): 186 | discard initTypeInfo( 187 | typeSym=bindSym"Test", 188 | pragma=some newEmptyNode(), 189 | recList=none NimNode, 190 | genericParams=none NimNode 191 | ) 192 | 193 | doAssertRaises(AssertionDefect): 194 | discard initTypeInfo( 195 | typeSym=bindSym"Test", 196 | pragma=none NimNode, 197 | recList=some newEmptyNode(), 198 | genericParams=none NimNode 199 | ) 200 | 201 | doAssertRaises(AssertionDefect): 202 | discard initTypeInfo( 203 | typeSym=bindSym"Test", 204 | pragma=none NimNode, 205 | recList=none NimNode, 206 | genericParams=some newEmptyNode() 207 | ) 208 | 209 | discard initTypeInfo( 210 | typeSym=bindSym"Test", 211 | pragma=none NimNode, 212 | recList=none NimNode, 213 | genericParams=none NimNode 214 | ) 215 | 216 | block: 217 | let fields = @[ 218 | initField( 219 | nameIdent=ident "First", 220 | typeNode=bindSym "Test", 221 | features=initEmptyFieldFeatures(), 222 | public=false, 223 | isCase=false, 224 | branches=newSeqOfCap[FieldBranch](0) 225 | ), 226 | initField( 227 | nameIdent=ident "Second", 228 | typeNode=bindSym "Test", 229 | features=initEmptyFieldFeatures(), 230 | public=false, 231 | isCase=true, 232 | branches = @[ 233 | initFieldBranch( 234 | fields = @[ 235 | initField( 236 | nameIdent=ident "Third", 237 | typeNode=bindSym "Test", 238 | features=initEmptyFieldFeatures(), 239 | public=false, 240 | isCase=false, 241 | branches=newSeqOfCap[FieldBranch](0) 242 | ), 243 | initField( 244 | nameIdent=ident "Nope", 245 | typeNode=bindSym "Test", 246 | features=initFieldFeatures( 247 | skipSerializing=false, 248 | skipDeserializing=false, 249 | untagged=true, 250 | renameSerialize=none NimNode, 251 | renameDeserialize=none NimNode, 252 | skipSerializeIf=none NimNode, 253 | serializeWith=none NimNode, 254 | deserializeWith=none NimNode, 255 | defaultValue=none NimNode, 256 | aliases = @[], 257 | deserWith=none NimNode 258 | ), 259 | public=false, 260 | isCase=true, 261 | branches = @[ 262 | initFieldBranch( 263 | fields = @[ 264 | initField( 265 | nameIdent=ident "Fourth", 266 | typeNode=bindSym "Test", 267 | features=initEmptyFieldFeatures(), 268 | public=false, 269 | isCase=false, 270 | branches=newSeqOfCap[FieldBranch](0) 271 | ) 272 | ], 273 | conditionOfBranch=some nnkOfBranch.newTree() 274 | ) 275 | ] 276 | ) 277 | ], 278 | conditionOfBranch=some nnkOfBranch.newTree() 279 | ), 280 | initFieldBranch( 281 | fields = @[ 282 | initField( 283 | nameIdent=ident "Third", 284 | typeNode=bindSym "Test", 285 | features=initEmptyFieldFeatures(), 286 | public=false, 287 | isCase=false, 288 | branches=newSeqOfCap[FieldBranch](0) 289 | ), 290 | initField( 291 | nameIdent=ident "Nope", 292 | typeNode=bindSym "Test", 293 | features=initFieldFeatures( 294 | skipSerializing=false, 295 | skipDeserializing=false, 296 | untagged=true, 297 | renameSerialize=none NimNode, 298 | renameDeserialize=none NimNode, 299 | skipSerializeIf=none NimNode, 300 | serializeWith=none NimNode, 301 | deserializeWith=none NimNode, 302 | defaultValue=none NimNode, 303 | aliases = @[], 304 | deserWith=none NimNode 305 | ), 306 | public=false, 307 | isCase=true, 308 | branches = @[ 309 | initFieldBranch( 310 | fields = @[ 311 | initField( 312 | nameIdent=ident "Fourth", 313 | typeNode=bindSym "Test", 314 | features=initEmptyFieldFeatures(), 315 | public=false, 316 | isCase=false, 317 | branches=newSeqOfCap[FieldBranch](0) 318 | ) 319 | ], 320 | conditionOfBranch=some nnkOfBranch.newTree() 321 | ) 322 | ] 323 | ) 324 | ], 325 | conditionOfBranch=some nnkOfBranch.newTree() 326 | ) 327 | ] 328 | ) 329 | ] 330 | 331 | var fieldNames = @[ 332 | "Fourth", 333 | "Third", 334 | "Second", 335 | "First" 336 | ] 337 | 338 | for field in flatten fields: 339 | doAssert field.nameIdent.strVal == fieldNames.pop() 340 | 341 | block: 342 | let field = initField( 343 | nameIdent=ident "First", 344 | typeNode=bindSym "Test", 345 | features=initFieldFeatures( 346 | skipSerializing=false, 347 | skipDeserializing=false, 348 | untagged=false, 349 | renameSerialize=some newLit "Serialize", 350 | renameDeserialize=some newLit "Deserialize", 351 | skipSerializeIf=none NimNode, 352 | serializeWith=none NimNode, 353 | deserializeWith=none NimNode, 354 | defaultValue=none NimNode, 355 | aliases = @[], 356 | deserWith=none NimNode 357 | ), 358 | public=false, 359 | isCase=false, 360 | branches=newSeqOfCap[FieldBranch](0) 361 | ) 362 | 363 | doAssert serializeName(field) == "Serialize" 364 | doAssert deserializeName(field) == @["Deserialize"] 365 | 366 | block: 367 | let checkTable = [ 368 | (newLit "barFoo", "barFoo"), 369 | (ident "barFoo", "fooBar"), 370 | (bindSym "CamelCase", "fooBar"), 371 | (bindSym "CobolCase", "FOO-BAR"), 372 | (bindSym "KebabCase", "foo-bar"), 373 | (bindSym "PascalCase", "FooBar"), 374 | (bindSym "PathCase", "foo/bar"), 375 | (bindSym "SnakeCase", "foo_bar"), 376 | (bindSym "PlainCase", "foo bar"), 377 | (bindSym "TrainCase", "Foo-Bar"), 378 | (bindSym "UpperSnakeCase", "FOO_BAR"), 379 | ] 380 | for (renameValue, checkValue) in checkTable: 381 | let field = initField( 382 | nameIdent=ident "fooBar", 383 | typeNode=bindSym "Test", 384 | features=initFieldFeatures( 385 | skipSerializing=false, 386 | skipDeserializing=false, 387 | untagged=false, 388 | renameSerialize=some renameValue, 389 | renameDeserialize=some renameValue, 390 | skipSerializeIf=none NimNode, 391 | serializeWith=none NimNode, 392 | deserializeWith=none NimNode, 393 | defaultValue=none NimNode, 394 | aliases = @[], 395 | deserWith=none NimNode 396 | ), 397 | public=false, 398 | isCase=false, 399 | branches=newSeqOfCap[FieldBranch](0) 400 | ) 401 | doAssert field.serializeName == checkValue 402 | doAssert field.deserializeName == @[checkValue] 403 | 404 | let aliasCheckTable = [ 405 | (newLit "barFoo", @["barFoo", "fooBar"]), 406 | (ident "barFoo", @["fooBar"]), 407 | (bindSym "CamelCase", @["fooBar", "fooBar"]), 408 | (bindSym "CobolCase", @["FOO-BAR", "fooBar"]), 409 | (bindSym "KebabCase", @["foo-bar", "fooBar"]), 410 | (bindSym "PascalCase", @["FooBar", "fooBar"]), 411 | (bindSym "PathCase", @["foo/bar", "fooBar"]), 412 | (bindSym "SnakeCase", @["foo_bar", "fooBar"]), 413 | (bindSym "PlainCase", @["foo bar", "fooBar"]), 414 | (bindSym "TrainCase", @["Foo-Bar", "fooBar"]), 415 | (bindSym "UpperSnakeCase", @["FOO_BAR", "fooBar"]), 416 | ] 417 | 418 | for (renameValue, checkValue) in aliasCheckTable: 419 | let field = initField( 420 | nameIdent=ident "fooBar", 421 | typeNode=bindSym "Test", 422 | features=initFieldFeatures( 423 | skipSerializing=false, 424 | skipDeserializing=false, 425 | untagged=false, 426 | renameSerialize=none NimNode, 427 | renameDeserialize=none NimNode, 428 | skipSerializeIf=none NimNode, 429 | serializeWith=none NimNode, 430 | deserializeWith=none NimNode, 431 | defaultValue=none NimNode, 432 | aliases = @[renameValue], 433 | deserWith=none NimNode 434 | ), 435 | public=false, 436 | isCase=false, 437 | branches=newSeqOfCap[FieldBranch](0) 438 | ) 439 | doAssert field.deserializeName == checkValue 440 | 441 | run() -------------------------------------------------------------------------------- /tests/ser/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../../src") -------------------------------------------------------------------------------- /tests/ser/timpls.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | matrix: "; -d:release; --gc:orc; -d:release --gc:orc; --threads:on" 3 | """ 4 | import std/[ 5 | unittest, 6 | options, 7 | tables, 8 | sets 9 | ] 10 | 11 | import deser/[ 12 | ser, 13 | test 14 | ] 15 | 16 | 17 | suite "Serialize default impls": 18 | test "bool": 19 | assertSerTokens true, [initBoolToken(true)] 20 | assertSerTokens false, [initBoolToken(false)] 21 | 22 | test "int": 23 | assertSerTokens 0i8, [initI8Token(0)] 24 | assertSerTokens 0i16, [initI16Token(0)] 25 | assertSerTokens 0i32, [initI32Token(0)] 26 | assertSerTokens 0i64, [initI64Token(0)] 27 | assertSerTokens 0, [initI64Token(0)] 28 | 29 | test "float": 30 | assertSerTokens 0f32, [initF32Token(0.0)] 31 | assertSerTokens 0f64, [initF64Token(0.0)] 32 | assertSerTokens 0.0, [initF64Token(0.0)] 33 | 34 | test "char": 35 | assertSerTokens 'a', [initCharToken('a')] 36 | 37 | test "enum": 38 | type TestEnum = enum 39 | First 40 | 41 | assertSerTokens TestEnum.First, [initEnumToken()] 42 | 43 | test "bytes": 44 | assertSerTokens [byte(0)], [initBytesToken(@[byte(0)])] 45 | assertSerTokens @[byte(0)], [initBytesToken(@[byte(0)])] 46 | 47 | test "chars": 48 | assertSerTokens ['1', '2', '3'], [ 49 | initSeqToken(some 3), 50 | initCharToken('1'), 51 | initCharToken('2'), 52 | initCharToken('3'), 53 | initSeqEndToken() 54 | ] 55 | 56 | assertSerTokens @['1', '2', '3'], [ 57 | initSeqToken(some 3), 58 | initCharToken('1'), 59 | initCharToken('2'), 60 | initCharToken('3'), 61 | initSeqEndToken() 62 | ] 63 | 64 | test "set": 65 | assertSerTokens {1, 2, 3}, [ 66 | initSeqToken(some 3), 67 | initI64Token(1), 68 | initI64Token(2), 69 | initI64Token(3), 70 | initSeqEndToken() 71 | ] 72 | 73 | test "array": 74 | assertSerTokens [1, 2, 3], [ 75 | initSeqToken(some 3), 76 | initI64Token(1), 77 | initI64Token(2), 78 | initI64Token(3), 79 | initSeqEndToken() 80 | ] 81 | 82 | test "seq": 83 | assertSerTokens @[1, 2, 3], [ 84 | initSeqToken(some 3), 85 | initI64Token(1), 86 | initI64Token(2), 87 | initI64Token(3), 88 | initSeqEndToken() 89 | ] 90 | 91 | test "tuple": 92 | assertSerTokens (123, "123"), [ 93 | initArrayToken(some 2), 94 | initI64Token(123), 95 | initStringToken("123"), 96 | initArrayEndToken() 97 | ] 98 | 99 | test "named tuple": 100 | assertSerTokens (id: 123), [ 101 | initArrayToken(some 1), 102 | initI64Token(123), 103 | initArrayEndToken() 104 | ] 105 | 106 | test "option": 107 | assertSerTokens some 0, [ 108 | initSomeToken() 109 | ] 110 | 111 | assertSerTokens none int, [ 112 | initNoneToken() 113 | ] 114 | 115 | test "tables": 116 | # Table | TableRef | OrderedTable | OrderedTableRef 117 | assertSerTokens {1: "1"}.toTable, [ 118 | initMapToken(some 1), 119 | initI64Token(1), 120 | initStringToken("1"), 121 | initMapEndToken() 122 | ] 123 | 124 | assertSerTokens {1: "1"}.newTable, [ 125 | initMapToken(some 1), 126 | initI64Token(1), 127 | initStringToken("1"), 128 | initMapEndToken() 129 | ] 130 | 131 | assertSerTokens {1: "1"}.toOrderedTable, [ 132 | initMapToken(some 1), 133 | initI64Token(1), 134 | initStringToken("1"), 135 | initMapEndToken() 136 | ] 137 | 138 | assertSerTokens {1: "1"}.newOrderedTable, [ 139 | initMapToken(some 1), 140 | initI64Token(1), 141 | initStringToken("1"), 142 | initMapEndToken() 143 | ] 144 | 145 | test "sets": 146 | # HashSet | OrderedSet 147 | assertSerTokens [1].toHashSet, [ 148 | initSeqToken(some 1), 149 | initI64Token(1), 150 | initSeqEndToken() 151 | ] 152 | 153 | assertSerTokens [1].toOrderedSet, [ 154 | initSeqToken(some 1), 155 | initI64Token(1), 156 | initSeqEndToken() 157 | ] 158 | -------------------------------------------------------------------------------- /tests/ser/tmake.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | matrix: "; -d:release; --gc:orc; -d:release --gc:orc; --threads:on" 3 | """ 4 | import std/[ 5 | unittest, 6 | options, 7 | times 8 | ] 9 | 10 | import deser/[ 11 | ser, 12 | pragmas, 13 | test, 14 | helpers 15 | ] 16 | 17 | 18 | proc toTimestamp[Serializer](date: DateTime, serializer: var Serializer) = 19 | date.toTime.toUnix.serialize(serializer) 20 | 21 | 22 | type 23 | Object = object 24 | id*: int 25 | 26 | GenericObject[T] = object 27 | id: T 28 | 29 | ObjectWithRef = object 30 | id: ref int 31 | 32 | RefObject = ref object 33 | id: int 34 | 35 | InheritObject {.renameAll: SnakeCase.} = object of RootObj 36 | id* {.renamed: "i".}: int 37 | 38 | CaseObject = object 39 | case kind: bool 40 | of true: 41 | yes: string 42 | else: 43 | discard 44 | 45 | UntaggedCaseObject = object 46 | case kind {.untagged.}: bool 47 | of true: 48 | yes: string 49 | else: 50 | discard 51 | 52 | SkipIfObject = object 53 | alwaysSkipped {.skipped.}: int 54 | serializeSkipped {.skipSerializing.}: int 55 | text {.skipSerializeIf(isNone).}: Option[string] 56 | 57 | SerializeWithObject = object 58 | date {.serializeWith(toTimestamp).}: DateTime 59 | 60 | RenameObject = object 61 | name {.renameSerialize("fullname").}: string 62 | 63 | RenameAllObject {.renameAll(SnakeCase).} = object 64 | text: string 65 | firstName {.renameSerialize("firstName").}: string 66 | 67 | case kind: bool 68 | of true: 69 | lastName: string 70 | else: 71 | discard 72 | 73 | ChildObject = object of InheritObject 74 | text: string 75 | 76 | ChildGenericObject[T] = object of InheritObject 77 | text: T 78 | 79 | ChildGenericToObject = object of ChildGenericObject[string] 80 | 81 | ChildRefObject = ref object of InheritObject 82 | text: string 83 | 84 | ChildOfRefObject = object of ChildRefObject 85 | 86 | InfectedChild = object of InheritObject 87 | firstName: string 88 | 89 | SkipAllPrivateObject {.skipPrivate.} = object 90 | public*: int 91 | private: int 92 | 93 | SkipSerPrivateObject {.skipPrivateSerializing.} = object 94 | public*: int 95 | private: int 96 | 97 | MultiCaseObject = object 98 | case kind: bool 99 | of true: 100 | yes: string 101 | else: 102 | no: string 103 | 104 | case kind2: bool 105 | of true: 106 | yes2: string 107 | else: 108 | no2: string 109 | 110 | MultiCaseObjectUntagged = object 111 | case kind {.untagged.}: bool 112 | of true: 113 | yes: string 114 | of false: 115 | no: string 116 | 117 | case kind2: bool 118 | of true: 119 | yes2: string 120 | else: 121 | no2: string 122 | 123 | MultiCaseObjectAllUntagged = object 124 | case kind {.untagged.}: bool 125 | of true: 126 | yes: string 127 | of false: 128 | no: string 129 | 130 | case kind2 {.untagged.}: bool 131 | of true: 132 | yes2: string 133 | of false: 134 | no2: string 135 | 136 | RenameWithCase = object 137 | lolKek {.renameSerialize(SnakeCase).}: string 138 | kekLol {.renamed(SnakeCase).}: string 139 | 140 | CaseObjectMultiBranchKind = enum 141 | First, Second, Third, Fourth 142 | 143 | CaseObjectMultiBranch = object 144 | case kind: CaseObjectMultiBranchKind 145 | of First, Second: 146 | first: string 147 | of Third, Fourth: 148 | second: string 149 | 150 | Quotes = object 151 | `first`: string 152 | `second`*: string 153 | `third` {.skipped.}: string 154 | `fourth`* {.skipped.}: string 155 | 156 | DeserWith = object 157 | created {.deserWith(UnixTimeFormat).}: Time 158 | 159 | 160 | makeSerializable([ 161 | Object, 162 | GenericObject, 163 | ObjectWithRef, 164 | RefObject, 165 | InheritObject, 166 | CaseObject, 167 | UntaggedCaseObject, 168 | SkipIfObject, 169 | SerializeWithObject, 170 | RenameObject, 171 | RenameAllObject, 172 | ChildObject, 173 | ChildGenericObject, 174 | ChildGenericToObject, 175 | ChildRefObject, 176 | ChildOfRefObject, 177 | InfectedChild, 178 | SkipAllPrivateObject, 179 | SkipSerPrivateObject, 180 | MultiCaseObject, 181 | MultiCaseObjectUntagged, 182 | MultiCaseObjectAllUntagged, 183 | RenameWithCase, 184 | CaseObjectMultiBranch, 185 | Quotes, 186 | DeserWith 187 | ], public=true) 188 | 189 | 190 | suite "makeSerializable": 191 | test "Serialize at CT": 192 | static: 193 | assertSerTokens Object(id: 123), [ 194 | initMapToken(none int), 195 | initStringToken("id"), 196 | initI64Token(123), 197 | initMapEndToken() 198 | ] 199 | 200 | test "simple": 201 | assertSerTokens Object(id: 123), [ 202 | initMapToken(none int), 203 | initStringToken("id"), 204 | initI64Token(123), 205 | initMapEndToken() 206 | ] 207 | 208 | test "generic": 209 | assertSerTokens GenericObject[int](id: 123), [ 210 | initMapToken(none int), 211 | initStringToken("id"), 212 | initI64Token(123), 213 | initMapEndToken() 214 | ] 215 | 216 | test "with ref": 217 | let refInt = new int 218 | refInt[] = 123 219 | assertSerTokens ObjectWithRef(id: refInt), [ 220 | initMapToken(none int), 221 | initStringToken("id"), 222 | initI64Token(123), 223 | initMapEndToken() 224 | ] 225 | 226 | test "ref": 227 | assertSerTokens RefObject(id: 123), [ 228 | initMapToken(none int), 229 | initStringToken("id"), 230 | initI64Token(123), 231 | initMapEndToken() 232 | ] 233 | 234 | test "inherit": 235 | assertSerTokens InheritObject(id: 123), [ 236 | initMapToken(none int), 237 | initStringToken("i"), 238 | initI64Token(123), 239 | initMapEndToken() 240 | ] 241 | 242 | test "case": 243 | assertSerTokens CaseObject(kind: true, yes: "yes"), [ 244 | initMapToken(none int), 245 | initStringToken("kind"), 246 | initBoolToken(true), 247 | initStringToken("yes"), 248 | initStringToken("yes"), 249 | initMapEndToken() 250 | ] 251 | 252 | assertSerTokens CaseObject(kind: false), [ 253 | initMapToken(none int), 254 | initStringToken("kind"), 255 | initBoolToken(false), 256 | initMapEndToken() 257 | ] 258 | 259 | test "untagged case": 260 | assertSerTokens UntaggedCaseObject(kind: true, yes: "yes"), [ 261 | initMapToken(none int), 262 | initStringToken("yes"), 263 | initStringToken("yes"), 264 | initMapEndToken() 265 | ] 266 | 267 | assertSerTokens UntaggedCaseObject(kind: false), [ 268 | initMapToken(none int), 269 | initMapEndToken() 270 | ] 271 | 272 | test "skipSerializeIf": 273 | assertSerTokens SkipIfObject(text: some "text"), [ 274 | initMapToken(none int), 275 | initStringToken("text"), 276 | initSomeToken(), 277 | initMapEndToken() 278 | ] 279 | 280 | assertSerTokens SkipIfObject(text: none string), [ 281 | initMapToken(none int), 282 | initMapEndToken() 283 | ] 284 | 285 | test "serializeWith": 286 | let date = now() 287 | assertSerTokens SerializeWithObject(date: date), [ 288 | initMapToken(none int), 289 | initStringToken("date"), 290 | initI64Token(int(date.toTime.toUnix)), 291 | initMapEndToken() 292 | ] 293 | 294 | test "renameSerialize": 295 | assertSerTokens RenameObject(name: "Name"), [ 296 | initMapToken(none int), 297 | initStringToken("fullname"), 298 | initStringToken("Name"), 299 | initMapEndToken() 300 | ] 301 | 302 | test "RenameAllObject": 303 | assertSerTokens RenameAllObject(kind: true), [ 304 | initMapToken(none int), 305 | initStringToken("text"), 306 | initStringToken(""), 307 | initStringToken("firstName"), 308 | initStringToken(""), 309 | initStringToken("kind"), 310 | initBoolToken(true), 311 | initStringToken("last_name"), 312 | initStringToken(""), 313 | initMapEndToken() 314 | ] 315 | 316 | test "ChildObject": 317 | assertSerTokens ChildObject(id: 123, text: "123"), [ 318 | initMapToken(none int), 319 | initStringToken("i"), 320 | initI64Token(123), 321 | initStringToken("text"), 322 | initStringToken("123"), 323 | initMapEndToken() 324 | ] 325 | 326 | test "ChildGenericObject": 327 | assertSerTokens ChildGenericObject[string](id: 123, text: "123"), [ 328 | initMapToken(none int), 329 | initStringToken("i"), 330 | initI64Token(123), 331 | initStringToken("text"), 332 | initStringToken("123"), 333 | initMapEndToken() 334 | ] 335 | 336 | test "ChildRefObject": 337 | assertSerTokens ChildRefObject(id: 123, text: "123"), [ 338 | initMapToken(none int), 339 | initStringToken("i"), 340 | initI64Token(123), 341 | initStringToken("text"), 342 | initStringToken("123"), 343 | initMapEndToken() 344 | ] 345 | 346 | test "ChildGenericToObject": 347 | assertSerTokens ChildGenericToObject(id: 123, text: "123"), [ 348 | initMapToken(none int), 349 | initStringToken("i"), 350 | initI64Token(123), 351 | initStringToken("text"), 352 | initStringToken("123"), 353 | initMapEndToken() 354 | ] 355 | 356 | test "ChildOfRefObject": 357 | assertSerTokens ChildOfRefObject(id: 123, text: "123"), [ 358 | initMapToken(none int), 359 | initStringToken("i"), 360 | initI64Token(123), 361 | initStringToken("text"), 362 | initStringToken("123"), 363 | initMapEndToken() 364 | ] 365 | 366 | test "InfectedChild": 367 | assertSerTokens InfectedChild(id: 123, firstName: "123"), [ 368 | initMapToken(none int), 369 | initStringToken("i"), 370 | initI64Token(123), 371 | initStringToken("first_name"), 372 | initStringToken("123"), 373 | initMapEndToken() 374 | ] 375 | 376 | test "SkipAllPrivateObject": 377 | assertSerTokens SkipAllPrivateObject(public: 123), [ 378 | initMapToken(none int), 379 | initStringToken("public"), 380 | initI64Token(123), 381 | initMapEndToken() 382 | ] 383 | 384 | test "SkipDesPrivateObject": 385 | assertSerTokens SkipSerPrivateObject(public: 123), [ 386 | initMapToken(none int), 387 | initStringToken("public"), 388 | initI64Token(123), 389 | initMapEndToken() 390 | ] 391 | 392 | test "MultiCaseObject": 393 | assertSerTokens MultiCaseObject(kind: true, yes: "yes", kind2: false, no2: "no"), [ 394 | initMapToken(none int), 395 | initStringToken("kind"), 396 | initBoolToken(true), 397 | initStringToken("yes"), 398 | initStringToken("yes"), 399 | initStringToken("kind2"), 400 | initBoolToken(false), 401 | initStringToken("no2"), 402 | initStringToken("no"), 403 | initMapEndToken() 404 | ] 405 | 406 | test "MultiCaseObjectUntagged": 407 | assertSerTokens MultiCaseObjectUntagged(kind: true, yes: "yes", kind2: false, no2: "no"), [ 408 | initMapToken(none int), 409 | initStringToken("yes"), 410 | initStringToken("yes"), 411 | initStringToken("kind2"), 412 | initBoolToken(false), 413 | initStringToken("no2"), 414 | initStringToken("no"), 415 | initMapEndToken() 416 | ] 417 | 418 | test "MultiCaseObjectAllUntagged": 419 | assertSerTokens MultiCaseObjectAllUntagged(kind: true, yes: "yes", kind2: false, no2: "no"), [ 420 | initMapToken(none int), 421 | initStringToken("yes"), 422 | initStringToken("yes"), 423 | initStringToken("no2"), 424 | initStringToken("no"), 425 | initMapEndToken() 426 | ] 427 | 428 | test "RenameWithCase": 429 | assertSerTokens RenameWithCase(), [ 430 | initMapToken(none int), 431 | initStringToken("lol_kek"), 432 | initStringToken(""), 433 | initStringToken("kek_lol"), 434 | initStringToken(""), 435 | initMapEndToken() 436 | ] 437 | 438 | test "CaseObjectMultiBranch": 439 | assertSerTokens CaseObjectMultiBranch(kind: First, first: "123"), [ 440 | initMapToken(none int), 441 | initStringToken("kind"), 442 | initEnumToken(), 443 | initStringToken("first"), 444 | initStringToken("123"), 445 | initMapEndToken() 446 | ] 447 | 448 | assertSerTokens CaseObjectMultiBranch(kind: Third, second: "123"), [ 449 | initMapToken(none int), 450 | initStringToken("kind"), 451 | initEnumToken(), 452 | initStringToken("second"), 453 | initStringToken("123"), 454 | initMapEndToken() 455 | ] 456 | 457 | test "Serialize nil ref as none": 458 | # ref without generated `serialize` 459 | var temp: ref int 460 | assertSerTokens temp, [ 461 | initNoneToken() 462 | ] 463 | 464 | # ref with generated `serialize` 465 | var temp2: ChildRefObject 466 | assertSerTokens temp2, [ 467 | initNoneToken() 468 | ] 469 | 470 | test "Quotes": 471 | assertSerTokens Quotes(first: "1", second: "2"), [ 472 | initMapToken(none int), 473 | initStringToken("first"), 474 | initStringToken("1"), 475 | initStringToken("second"), 476 | initStringToken("2"), 477 | initMapEndToken() 478 | ] 479 | 480 | test "DeserWith": 481 | assertSerTokens DeserWith(created: fromUnix(123)), [ 482 | initMapToken(none int), 483 | initStringToken("created"), 484 | initI64Token(123), 485 | initMapEndToken() 486 | ] 487 | -------------------------------------------------------------------------------- /tests/thelpers.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | matrix: "; -d:release; --gc:orc; -d:release --gc:orc; --threads:on" 3 | """ 4 | import std/[ 5 | times, 6 | options 7 | ] 8 | 9 | import deser 10 | 11 | import deser/test 12 | 13 | type 14 | UnixTimeObject = object 15 | field {.deserWith(UnixTimeFormat).}: Time 16 | 17 | DateTimeFormatObject = object 18 | field {.deserWith(DateTimeWith(format: "yyyy-MM-dd")).}: DateTime 19 | 20 | 21 | template types: untyped = [ 22 | UnixTimeObject, 23 | DateTimeFormatObject 24 | ] 25 | 26 | template run(obj, tokens: untyped) = 27 | assertSerTokens obj, tokens 28 | assertDesTokens obj, tokens 29 | 30 | makeSerializable(types) 31 | makeDeserializable(types) 32 | 33 | run UnixTimeObject(field: fromUnix(123)), [ 34 | initMapToken(none int), 35 | initStringToken("field"), 36 | initI64Token(123), 37 | initMapEndToken() 38 | ] 39 | 40 | run DateTimeFormatObject(field: parse("2000-01-01", "yyyy-MM-dd")), [ 41 | initMapToken(none int), 42 | initStringToken("field"), 43 | initStringToken("2000-01-01"), 44 | initMapEndToken() 45 | ] 46 | --------------------------------------------------------------------------------