commit f1563c9890d31b66909fb250a50afff063ef0290
Author: divkit-github <111018801+divkit-github@users.noreply.github.com>
Date: Wed Aug 10 23:43:52 2022 +0300
chore(html-reporter): up version
Initial commit
Add notification for divkit
Allow overwrite for migration
[migration] browser/bitbucket/personal/booster/divkit
Note: mandatory check (NEED_CHECK) was skipped
DivKit files moved
dIVKIT-0: Fix ts builder build
update div-gallery visibility screenshots
update div-gallery visibility screens
a.yaml cleanup
updated codegen scripts
change gallery alignment
change internal types in expressions
removed copyrights
remove permissions
TemplatesSupportTests refactoring
Ничего не поменялось. Отрефакторил и почистил тесты.
Split package.json
send all public
removed yandex-team links
URL added type as ArgumentType
added camera permission description
safe area insets for errors count view
`EdgeInsets` for debug view with errors count. Needed for fullscreen divs. Naming corresponds to mapping `EdgeInsets` to div-variables.
Will be used here: https://nda.ya.ru/t/Lhr5Lxgb5KN6nX
fixed compiler warnings
fix DivKitComponents variables setup, add update func
fix DivKitComponents variables setup, add update func
authors clean up
Удалил коллег из инфраструктуры, тестирования и мессенджера. Половину людей я вообще тут не знаю. Список какой-то косячный, в нём, например, @booster и @askvortsov нет. Предлагаю заново вручную составить и записать туда тех, кто точно дивкитом занимался, а не просто мимо проходил.
Fix DivKit pods compilability
Fix DivKit pods compilability
use only english in demo app
translate divKit demo app russian text
Scroll when keyboard opened
Scroll when keyboard opened
Fix gallery scroll in a11y
Это копия пр-а, который я делал в репо алискита:
https://bitbucket.browser.yandex-team.ru/projects/ML/repos/mobile-alice-library-android/pull-requests/18808/overview
authors clean up2
SwiftGenerator
flake8 errors fix & ya.make edit
swift generation tests
swift references
swift generator implementation
init swift generator
code refactoring
1) rename to modeling
2) rename kind to PropertyType
3) delete apply_pluralization_rules
4) description default value is empty string
5) remove entity_type_field_alias, typename_alias, client_optional, profileEntities, ignore_properties, generate_plain_constructor
6) remove client prefix
Add XcodeBuild in ignore
Add XcodeBuild in ignore
introduce new global variable api
introduce new global variable api
Revert commit bdcbc0081395ebe44ed7fe24bf5f4b25569c6df0
LayoutKitSnapshotTests
Fix sandboxRelease dependencies.
support zero value for width and height
remove unused css variables
"CONTRIBUTING.md"
README draft
updated gitignore/arcignore
introduce color type in expressions
introduce color type in expressions
added xcconfig files
fixed swift formatting
fail tests if updating references
Чтобы уж точно не забыть вернуть False.
Layout changes
Fix expression parsing
don't change clickable state in a11y
don't change clickable state in a11y
Update items checks
Parse description references to translations
add licence file link
fixed expression properties generation
add ability to track requests to global variables
Добавляем в GlobalVariableController возможность подписаться на запросы к глобальным переменным.
Нужно дляОткрыт Накопительная палитра , а так же в целом полезная функциональность, через которую можно делать ленивый прогрев глобальных переменных
Changelog
Minor fixes
bump divkit version up to 3.0.0
fixed snapshot tests depdendencies
Иногда не запускалась сборка SnapshotTestsHostApp перед стартом тестов.
used new api-generator
deserialization refactoring
removed gitignore
samples text fixes
fixed samples text
remove theme button
remove theme button
demo activity redesign
demo activity redesign
habr sample app
change deleted images in screenshot tests
change deleted images in screenshot tests
new ic_launcher
new ic_launcher
more readme
Binding variable for div input
Biding variable for div input
Implement documentation generator
Rename max_lines to max_visible_lines
Rename max_lines to max_visible_lines
add habr and medium
fix div-functions doc
Fix link in description
Text selecting for demo app enabled
Text selecting for demo app enabled
KotlinGenerator
kotlin code generation
kotlin generation tests
kotlin generation references
add gradle wrapper to kotlin json builder
add gradle wrapper to kotlin json builder
DemoApp redesign
fixed codegen scripts
debugView safeAreaInsets
DebugView safe area insets
TypeScript Generator
ya.make changes
TypeScript generator
TypeScript tests
TypeScript test references
fix enumeration aliases
debugInfoParams fix compile
Fix black box flicker in video-custom
Fixed stub image showing by moving it away from PlayerView.
Increased stub loading speed by adding VideoViewModel warmup.
Fixed redundant redraws by combining stub visibility and
bitmap data.
PlayerView background was set to transparent.
DivExpression fix
DivExpression fix
Implement translations
Save & share
bind globalVariableController to divContext
bind variableController to divContext
used new TypeScript generator
Подключил генератор на питоне. Изменения в сгенерированных файлах связаны в основном с переводом описаний на английский язык.
moved LivePreview
Перенёс LivePreview из отдельного модуля в DivKitDemoApp. Добавил сканер кодов в UrlInputView. Удалил часть дублирующегося кода.
remove sms and phone_state permission
fix links interception
fix links interception
Fix link in documentation
remove site from "config.stable.json"
Support highlight_color and keyboardType in div input
Support highlight_color in div input
Action animation fix for iOS
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..a40967c1a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+.DS_Store
+.gradle
+.idea
+.swiftpm
+.vscode
+build/
+XcodeBuild/
+xcuserdata/
+
+local.properties
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 000000000..148f632c5
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,49 @@
+The following authors have created the source code of "DivKit" published and distributed by YANDEX LLC as the owner:
+4eb0da
+akhmetsafin
+bgubanov
+chris-benua
+edubinskaya
+geaden
+ggys
+grechka62
+gulevsky
+i-ts
+ivanovsky-v
+morevsavva
+nikita-kladov
+nkhodyunya
+pkurchatov
+rnkorneeva
+tayrinn
+timatifey
+ttok
+valeriahil
+ventywing
+yagamayunov
+yboyarinov
+
+The following authors have licensed their contributions to YANDEX LLC and everyone who uses "DivKit" under the licensing terms detailed in LICENSE available at https://github.com/divkit/divkit/blob/main/LICENSE.
+4eb0da <4eb0da@yandex-team.ru>
+akhmetsafin
+bgubanov
+chris-benua
+edubinskaya
+geaden
+ggys
+grechka62
+gulevsky
+i-ts
+ivanovsky-v
+morevsavva
+nikita-kladov
+nkhodyunya
+pkurchatov
+rnkorneeva
+tayrinn
+timatifey
+ttok
+valeriahil
+ventywing
+yagamayunov
+yboyarinov
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..267004efc
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,35 @@
+# Notice to external contributors
+
+
+## General info
+
+Hello! In order for us (YANDEX LLC) to accept patches and other contributions from you, you will have to adopt our Yandex Contributor License Agreement (the **CLA**). The current version of the CLA can be found here:
+1) https://yandex.ru/legal/cla/?lang=en (in English) and
+2) https://yandex.ru/legal/cla/?lang=ru (in Russian).
+
+By adopting the CLA, you state the following:
+
+* You obviously wish and are willingly licensing your contributions to us for our open source projects under the terms of the CLA,
+* You have read the terms and conditions of the CLA and agree with them in full,
+* You are legally able to provide and license your contributions as stated,
+* We may use your contributions for our open source projects and for any other our project too,
+* We rely on your assurances concerning the rights of third parties in relation to your contributions.
+
+If you agree with these principles, please read and adopt our CLA. By providing us your contributions, you hereby declare that you have already read and adopt our CLA, and we may freely merge your contributions with our corresponding open source project and use it in further in accordance with terms and conditions of the CLA.
+
+## Provide contributions
+
+If you have already adopted terms and conditions of the CLA, you are able to provide your contributions. When you submit your pull request, please add the following information into it:
+
+```
+I hereby agree to the terms of the CLA available at: [link].
+```
+
+Replace the bracketed text as follows:
+* [link] is the link to the current version of the CLA: https://yandex.ru/legal/cla/, https://yandex.ru/legal/cla/?lang=en (in English) or https://yandex.ru/legal/cla/?lang=ru (in Russian).
+
+It is enough to provide us such notification once.
+
+## Other questions
+
+If you have any questions, please mail us at opensource@yandex-team.ru.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 000000000..3a5274b25
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,400 @@
+Copyright 2022 YANDEX LLC
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+This software uses the following dependencies. Their respective licensing terms and copyright notices are given below. Some of the dependencies belong to the same rightsholders and are licensed under the same terms, thus, for convenience, such dependencies are grouped and have only one copyright notice and one license notice applicable to them all.
+
+androidx.appcompat:appcompat
+androidx.core:core
+androidx.core:core-ktx
+androidx.recyclerview:recyclerview
+androidx.test:core
+androidx.test:core-ktx
+androidx.collection:collection
+androidx.vectordrawable:vectordrawable
+androidx.exifinterface:exifinterface
+androidx.annotation:annotation
+androidx.transition:transition
+androidx.viewpager2:viewpager2
+androidx.lifecycle:lifecycle-runtime-ktx
+androidx.constraintlayout:constraintlayout
+androidx.emoji:emoji
+
+Copyright (c) 2022 Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License")
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+com.google.dagger:dagger
+com.google.dagger:dagger-compiler
+
+Copyright (c) 2016 Google Inc.
+Copyright (c) 2016 Square Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License")
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+javax.inject:javax.inject
+
+Copyright (c) 2009 The JSR-330 Expert Group
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+org.jetbrains.kotlin:kotlin-reflect
+
+Copyright (c)2010-2017 JetBrains s.r.o.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+org.json:json
+
+JSON License
+
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+com.squareup.okhttp3:okhttp
+
+Copyright (c) 2012 Square, Inc.
+Copyright (c) 2012 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+com.google.android.material:material
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+com.google.code.findbugs:jsr305
+
+Copyright (с) 2006 FindBugs
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+com.google.auto.service:auto-service
+
+Copyright (c) 2008 Google LLC
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+com.google.auto.service:auto-service-annotations
+
+Copyright (c) 2013 Google LLC
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+org.hamcrest:hamcrest-junit
+
+Copyright (c) 2014 Nat Pryce
+Copyright (c) 2015 Joseph Walton
+Copyright (c) 2015 Lars Vogel
+
+License: Eclipse Public License - v 1.0
+
+The source code is available at: https://github.com/hamcrest/hamcrest-junit
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+junit:junit
+
+Copyright (c) 2002-2021 JUnit
+
+License: Eclipse Public License - v 1.0
+
+The source code is available at: https://github.com/junit-team/junit5
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+org.robolectric:robolectric
+
+MIT License
+
+Copyright (c) 2010 Xtreme Labs, Pivotal Labs and Google Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+org.robolectric:shadows-supportv4
+
+MIT License
+
+Copyright (c) 2018 Nimble
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+org.easytesting:fest-reflect
+
+Copyright (c) 2009 Alex Ruiz
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+com.squareup.moshi:moshi
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+com.squareup:fest-android
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+org.mockito:mockito-core
+
+MIT License
+
+Copyright (c) 2007 Mockito contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+org.mockito.kotlin:mockito-kotlin
+
+MIT License
+
+Copyright (c) 2016 Niek Haarman
+Copyright (c) 2007 Mockito contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+io.strikt:strikt-core
+
+Copyright (c) 2014 Netflix
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+
+org.jetbrains.kotlinx:kotlinx-coroutines-core
+org.jetbrains.kotlinx:kotlinx-coroutines-test
+org.jetbrains.kotlinx:kotlinx-coroutines-android
+
+Copyright (c)2000-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..3070a6ddc
--- /dev/null
+++ b/README.md
@@ -0,0 +1,31 @@
+# DIVKIT
+
+**DivKit** 🐋 is an open source Server-Driven UI (SDUI) framework.
+SDUI is a an emerging technique that leverage the server to build the user interfaces of their mobile apps.
+
+The convenience of DivKit as a server-driven UI technology is that it doesn’t require much at the start. You can transfer a view or even a particular element (such as a card or button) to Divs. At the very beginning, even a server isn’t needed: DivKit is easy to use as a framework that facilitates development. You can include all data on the client-side, and when the server response mechanism becomes available, the app can start receiving updates right away.
+
+We’ve made a sandbox for you to experiment with. The DivKit web engine is connected to it, but you can also download iOS/Android app demos from stores and link them to the sandbox. The data will be updated live: the sandbox connects to the playground app via web sockets.
+
+[Documentation](https://divkit.tech/doc). [Medium](https://medium.com/p/cad519252f0f). [Habr](https://habr.com/ru/company/yandex/blog/683886/).
+
+Telegram: [News](https://t.me/divkit_news) | [English-speaking chat](https://t.me/divkit_community_en) | [Чат на русском](https://t.me/divkit_community_ru).
+
+[Twitter](https://twitter.com/divkit)
+
+Project structure:
+
+## Api Generator
+Used for generating general Api for all platforns from Schema.
+
+## Client
+Several libraries for rendering UIs on Android, iOS and Web.
+
+## Json-builder
+Libraries that build JSON in DivKit format.
+
+## Schema
+JSON schema that describes DivKit data format.
+
+## Test data
+Samples and test data.
diff --git a/api_generator/.arcignore b/api_generator/.arcignore
new file mode 100644
index 000000000..256041143
--- /dev/null
+++ b/api_generator/.arcignore
@@ -0,0 +1 @@
+/tests/output/
diff --git a/api_generator/.gitignore b/api_generator/.gitignore
new file mode 100644
index 000000000..256041143
--- /dev/null
+++ b/api_generator/.gitignore
@@ -0,0 +1 @@
+/tests/output/
diff --git a/api_generator/README.md b/api_generator/README.md
new file mode 100644
index 000000000..7d4d9e744
--- /dev/null
+++ b/api_generator/README.md
@@ -0,0 +1,49 @@
+# DivKit API Generator
+
+The script takes a JSON schema at the input and generates the code of objects described in the schema based on it and the config.
+
+## Config format:
+
+```json
+{
+ "lang": "[kotlin|kotlinDsl|swift|typescript|python|documentation]",
+ "header": "// Header of every generated file. Generated code. Do not modify.\nimport some.lib\nimport other.lib\n\n"
+}
+```
+
+## Commands
+
+### With ya make
+
+Build:
+```shell
+ya make
+```
+
+Run generator:
+```shell
+./api_generator -c /config/file/name.json -s /path/to/schema -o /output/path
+```
+
+Run tests:
+```shell
+ya make -t
+```
+
+### Without ya make
+
+Setup environment:
+```shell
+pip install -r requirements.txt
+pip install -r dev_requirements.txt
+```
+
+Run generator:
+```shell
+python -m api_generator -c /config/file/name.json -s /path/to/schema -o /output/path
+```
+
+Run tests:
+```shell
+python -m pytest
+```
diff --git a/api_generator/api_generator/__init__.py b/api_generator/api_generator/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/api_generator/api_generator/__main__.py b/api_generator/api_generator/__main__.py
new file mode 100644
index 000000000..5d17caf0c
--- /dev/null
+++ b/api_generator/api_generator/__main__.py
@@ -0,0 +1,51 @@
+from typing import Tuple
+from argparse import ArgumentParser
+import os
+
+from .generator import generate_api
+from .config import Config
+
+
+def __file_path(path) -> str:
+ if os.path.isfile(path):
+ return path
+ else:
+ raise FileNotFoundError(path)
+
+
+def __dir_path(path: str) -> str:
+ if os.path.isdir(path):
+ return path
+ else:
+ raise NotADirectoryError(path)
+
+
+def __dir_path_with_makedir(path: str) -> str:
+ try:
+ return __dir_path(path)
+ except NotADirectoryError:
+ os.makedirs(path)
+ return __dir_path(path)
+
+
+def __parse_arguments() -> Tuple[str, str, str]:
+ parser = ArgumentParser(description='The script takes a JSON schema at the input and generates the code of '
+ 'objects described in the schema based on it and the config.')
+ parser.add_argument('-c', '--config', type=__file_path, help='Path to config file', required=True)
+ parser.add_argument('-s', '--schema', type=__dir_path, help='Path to schema directory', required=True)
+ parser.add_argument('-o', '--output',
+ type=__dir_path_with_makedir,
+ help='Path to generator output directory',
+ required=True)
+ parsed_args = parser.parse_args()
+ return parsed_args.config, parsed_args.schema, parsed_args.output
+
+
+def main():
+ config_path, schema_path, output_path = __parse_arguments()
+ config = Config(config_path, schema_path, output_path)
+ generate_api(config)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/api_generator/api_generator/config.py b/api_generator/api_generator/config.py
new file mode 100644
index 000000000..20080b56b
--- /dev/null
+++ b/api_generator/api_generator/config.py
@@ -0,0 +1,66 @@
+from __future__ import annotations
+from typing import Text, List, Optional
+from pydantic import BaseModel, Field
+from enum import Enum, auto
+
+
+class Platform(str, Enum):
+ ANDROID = 'android'
+ IOS = 'ios'
+ WEB = 'web'
+
+
+class DescriptionLanguage(str, Enum):
+ EN = 'en'
+ RU = 'ru'
+
+
+class GeneratedLanguage(str, Enum):
+ SWIFT = 'swift'
+ KOTLIN = 'kotlin'
+ KOTLIN_DSL = 'kotlinDsl'
+ TYPE_SCRIPT = 'typescript'
+ PYTHON = 'python'
+ DOCUMENTATION = 'documentation'
+
+
+TEMPLATE_SUFFIX = '_template'
+
+
+class GenerationMode(Enum):
+ NORMAL_WITH_TEMPLATES = auto()
+ NORMAL_WITHOUT_TEMPLATES = auto()
+ TEMPLATE = auto()
+
+ @property
+ def is_template(self) -> bool:
+ if self in [GenerationMode.NORMAL_WITH_TEMPLATES, GenerationMode.NORMAL_WITHOUT_TEMPLATES]:
+ return False
+ elif self is GenerationMode.TEMPLATE:
+ return True
+
+ @property
+ def name_suffix(self) -> str:
+ if self in [GenerationMode.NORMAL_WITH_TEMPLATES, GenerationMode.NORMAL_WITHOUT_TEMPLATES]:
+ return ''
+ elif self is GenerationMode.TEMPLATE:
+ return TEMPLATE_SUFFIX
+
+ def protocol_name(self, lang: GeneratedLanguage, name: str) -> Optional[str]:
+ if self.is_template:
+ return 'TemplateValue' if lang is GeneratedLanguage.SWIFT else f'JsonTemplate<{name}>'
+ return None
+
+
+class Config:
+ class GenerationConfig(BaseModel):
+ lang: GeneratedLanguage
+ header: Text = ''
+ errors_collectors: List[str] = Field([], alias='errorsCollectors')
+ kotlin_annotations: List[str] = Field([], alias='kotlinAnnotations')
+ generate_equality: bool = Field(False, alias='generateEquality')
+
+ def __init__(self, config_path: str, schema_path: str, output_path: str):
+ self.schema_path: str = schema_path
+ self.output_path: str = output_path
+ self.generation: Config.GenerationConfig = Config.GenerationConfig.parse_file(path=config_path)
diff --git a/api_generator/api_generator/generator.py b/api_generator/api_generator/generator.py
new file mode 100644
index 000000000..830d71ff9
--- /dev/null
+++ b/api_generator/api_generator/generator.py
@@ -0,0 +1,26 @@
+from .config import Config, GeneratedLanguage
+from .schema.preprocessing import schema_preprocessing
+from .schema.modeling import build_objects
+from .generators import Generator, SwiftGenerator, KotlinGenerator, DocumentationGenerator, TypeScriptGenerator
+
+
+def __build_generator(config: Config) -> Generator:
+ lang = config.generation.lang
+ generator_dict = {
+ GeneratedLanguage.SWIFT: SwiftGenerator,
+ GeneratedLanguage.KOTLIN: KotlinGenerator,
+ GeneratedLanguage.DOCUMENTATION: DocumentationGenerator,
+ GeneratedLanguage.TYPE_SCRIPT: TypeScriptGenerator,
+ }
+ generator = generator_dict.get(lang, None)
+ if generator is None:
+ raise NotImplementedError
+ return generator(config)
+
+
+def generate_api(config: Config):
+ root_directory = schema_preprocessing(config)
+ objects = build_objects(root_directory, config.generation)
+
+ generator = __build_generator(config)
+ generator.generate(objects)
diff --git a/api_generator/api_generator/generators/__init__.py b/api_generator/api_generator/generators/__init__.py
new file mode 100644
index 000000000..091921fc4
--- /dev/null
+++ b/api_generator/api_generator/generators/__init__.py
@@ -0,0 +1,7 @@
+from .base import Generator
+from .swift import SwiftGenerator
+from .kotlin import KotlinGenerator
+from .documentation import DocumentationGenerator
+from .type_script import TypeScriptGenerator
+
+__all__ = [Generator, SwiftGenerator, KotlinGenerator, DocumentationGenerator, TypeScriptGenerator]
diff --git a/api_generator/api_generator/generators/base.py b/api_generator/api_generator/generators/base.py
new file mode 100644
index 000000000..efc1e799a
--- /dev/null
+++ b/api_generator/api_generator/generators/base.py
@@ -0,0 +1,110 @@
+from __future__ import annotations
+
+import os.path
+from abc import ABC, abstractmethod
+from typing import List
+
+from ..config import Config
+from ..schema.modeling.entities import (
+ Declarable,
+ Entity,
+ EntityEnumeration,
+ StringEnumeration,
+ Property,
+ String,
+ Array, Url, Int, Double,
+)
+from ..schema.modeling.text import Text
+from .. import utils
+
+
+def declaration_comment(p: Property, default_value_comment_fun) -> str:
+ comments = []
+ if isinstance(p.property_type, String):
+ string: String = p.property_type
+ if string.min_length > 0:
+ end = 's' if string.min_length > 1 else ''
+ comments.append(f'at least {string.min_length} char{end}')
+ if string.regex is not None:
+ comments.append(f'regex: {string.regex.pattern}')
+ elif isinstance(p.property_type, Array):
+ array: Array = p.property_type
+ if array.min_items > 0:
+ comments.append(f'at least {array.min_items} elements')
+ if array.strict_parsing:
+ comments.append('all received elements must be valid')
+ elif isinstance(p.property_type, Url) and p.property_type.schemes:
+ joined_schemes = ', '.join(p.property_type.schemes)
+ comments.append(f'valid schemes: [{joined_schemes}]')
+ elif isinstance(p.property_type, (Int, Double)):
+ constraint = p.property_type.constraint
+ if constraint is not None:
+ comments.append(f'constraint: {constraint}')
+
+ if p.default_value is not None:
+ comments.append(f'default value: {default_value_comment_fun(p)}')
+
+ if not comments:
+ return ''
+ joined_comments = '; '.join(comments)
+ return f' // {joined_comments}'
+
+
+class Generator(ABC):
+ def __init__(self, config: Config) -> None:
+ self._config = config
+ self._output_path = config.output_path
+
+ def generate(self, objects: List[Declarable]):
+ self._clear_output_directory()
+ self._generate_files(objects)
+
+ def _clear_output_directory(self):
+ utils.clear_content_of_directory(self._output_path)
+
+ def _generate_files(self, objects: List[Declarable]):
+ for obj in objects:
+ declaration = []
+ for line in str(self._main_declaration(obj)).strip().split('\n'):
+ if line.isspace():
+ declaration.append('')
+ else:
+ declaration.append(line)
+ declaration = '\n'.join(declaration).strip()
+ if not declaration:
+ continue
+ head_for_file = self._head_for_file + '\n' if self._head_for_file.strip() else ''
+ file_content = f'{head_for_file}{declaration}\n'
+ filename = os.path.join(self._output_path, self._filename(obj.name))
+ with open(filename, 'w') as file:
+ file.write(file_content)
+
+ @property
+ def _head_for_file(self) -> str:
+ return self._config.generation.header
+
+ def _main_declaration(self, obj: Declarable) -> Text:
+ if isinstance(obj, Entity):
+ return self._entity_declaration(obj)
+ elif isinstance(obj, EntityEnumeration):
+ return self._entity_enumeration_declaration(obj)
+ elif isinstance(obj, StringEnumeration):
+ return self._string_enumeration_declaration(obj)
+ else:
+ raise NotImplementedError
+
+ @abstractmethod
+ def _filename(self, name: str) -> str:
+ pass
+
+ @abstractmethod
+ def _entity_declaration(self, entity: Entity) -> Text:
+ pass
+
+ @abstractmethod
+ def _entity_enumeration_declaration(self, entity_enumeration: EntityEnumeration) -> Text:
+ pass
+
+ @abstractmethod
+ def _string_enumeration_declaration(self, string_enumeration: StringEnumeration) -> Text:
+ pass
diff --git a/api_generator/api_generator/generators/documentation/__init__.py b/api_generator/api_generator/generators/documentation/__init__.py
new file mode 100644
index 000000000..47bcbf5cd
--- /dev/null
+++ b/api_generator/api_generator/generators/documentation/__init__.py
@@ -0,0 +1,3 @@
+from .generator import DocumentationGenerator
+
+__all__ = [DocumentationGenerator]
diff --git a/api_generator/api_generator/generators/documentation/documentation_entities.py b/api_generator/api_generator/generators/documentation/documentation_entities.py
new file mode 100644
index 000000000..214a251a2
--- /dev/null
+++ b/api_generator/api_generator/generators/documentation/documentation_entities.py
@@ -0,0 +1,376 @@
+from abc import abstractmethod
+from typing import cast, Dict, List, Union
+
+from .utils import code, content_in_quotes
+from ...schema.modeling.entities import (
+ PropertyType,
+ Property,
+ Declarable,
+ Entity,
+ EntityEnumeration,
+ StringEnumeration,
+ Array,
+ Bool,
+ BoolInt,
+ Color,
+ Dictionary,
+ Double,
+ Int,
+ Object,
+ ObjectFormat,
+ StaticString,
+ String,
+ Url
+)
+from ...schema.modeling.text import Text
+
+
+class DocumentationPropertyType(PropertyType):
+
+ @property
+ @abstractmethod
+ def description(self) -> str:
+ pass
+
+ def json_description(self, obj_stack: List[Declarable], prefix: str, suffix: str) -> Text:
+ return content_in_quotes(prefix, self.description, suffix)
+
+ @abstractmethod
+ def constraints(self, dictionary: Dict[str, str]) -> str:
+ pass
+
+ def details(self, dictionary: Dict[str, str]) -> str:
+ return ''
+
+
+class DocumentationProperty(Property):
+
+ def update_base(self):
+ update_property_type_base(self.property_type)
+
+ @property
+ def property_type_doc(self) -> DocumentationPropertyType:
+ return cast(DocumentationPropertyType, self.property_type)
+
+ def json_description_prefix(self, parent: Entity, property_type: PropertyType) -> str:
+ if isinstance(property_type, Array):
+ return self.json_description_prefix(parent, property_type.property_type)
+ return f'{self.dict_field}{"" if self.optional else "*"}: '
+
+
+class DocumentationDeclarable(Declarable):
+ @abstractmethod
+ def json_description(self, obj_stack: List[Declarable], prefix: str = '', suffix: str = '') -> Text:
+ pass
+
+ @property
+ @abstractmethod
+ def include_in_menu_file(self) -> bool:
+ pass
+
+
+def update_declarable_base(obj: Declarable) -> DocumentationDeclarable:
+ if isinstance(obj, Entity):
+ obj.__class__ = DocumentationEntity
+ obj = cast(DocumentationEntity, obj)
+ obj.update_base()
+ return obj
+ elif isinstance(obj, EntityEnumeration):
+ obj.__class__ = DocumentationEntityEnumeration
+ return cast(DocumentationEntityEnumeration, obj)
+ elif isinstance(obj, StringEnumeration):
+ obj.__class__ = DocumentationStringEnumeration
+ return cast(DocumentationStringEnumeration, obj)
+ else:
+ raise NotImplementedError
+
+
+def update_property_type_base(property_type: PropertyType):
+ if isinstance(property_type, Array):
+ property_type.__class__ = DocumentationArray
+ property_type = cast(DocumentationArray, property_type)
+ property_type.update_base()
+ elif isinstance(property_type, Bool):
+ property_type.__class__ = DocumentationBool
+ elif isinstance(property_type, BoolInt):
+ property_type.__class__ = DocumentationBoolInt
+ elif isinstance(property_type, Color):
+ property_type.__class__ = DocumentationColor
+ elif isinstance(property_type, Dictionary):
+ property_type.__class__ = DocumentationDictionary
+ elif isinstance(property_type, Double):
+ property_type.__class__ = DocumentationDouble
+ elif isinstance(property_type, Int):
+ property_type.__class__ = DocumentationInt
+ elif isinstance(property_type, Object):
+ property_type.__class__ = DocumentationObject
+ property_type = cast(DocumentationObject, property_type)
+ property_type.update_base()
+ elif isinstance(property_type, StaticString):
+ property_type.__class__ = DocumentationStaticString
+ elif isinstance(property_type, String):
+ property_type.__class__ = DocumentationString
+ elif isinstance(property_type, Url):
+ property_type.__class__ = DocumentationUrl
+ else:
+ raise NotImplementedError
+
+
+class DocumentationEntity(Entity, DocumentationDeclarable):
+
+ def update_base(self):
+ for prop in self.properties:
+ prop.__class__ = DocumentationProperty
+ prop = cast(DocumentationProperty, prop)
+ prop.update_base()
+
+ @property
+ def display_name(self) -> str:
+ return self._display_name
+
+ @property
+ def is_deprecated(self) -> bool:
+ return self._is_deprecated
+
+ @property
+ def properties_doc(self) -> List[DocumentationProperty]:
+ return cast(List[DocumentationProperty], self.properties)
+
+ def json_description(self, obj_stack: List[Declarable], prefix: str = '', suffix: str = '') -> Text:
+ if self in obj_stack:
+ return Text(prefix + self.display_name + suffix)
+
+ result = Text(prefix + '{')
+ last_index = len(self.properties_doc) - 1
+ for index, prop in enumerate(sorted(
+ self.properties_doc,
+ key=lambda p: not isinstance(p.property_type, StaticString)
+ )):
+ result += prop.property_type_doc.json_description(
+ obj_stack=obj_stack + [self],
+ prefix=prop.json_description_prefix(parent=self, property_type=prop.property_type),
+ suffix='' if index == last_index else ','
+ ).indented()
+ result += '}' + suffix
+ return result
+
+ def include_in_menu_file(self):
+ return not self.generate_as_protocol
+
+
+class DocumentationEntityEnumeration(EntityEnumeration, DocumentationDeclarable):
+
+ def json_description(self, obj_stack: List[Declarable], prefix: str = '', suffix: str = '') -> Text:
+ return Text(prefix + self.name + suffix)
+
+ def include_in_menu_file(self) -> bool:
+ return True
+
+
+class DocumentationStringEnumeration(StringEnumeration, DocumentationDeclarable):
+
+ def json_description(self, obj_stack: List[Declarable], prefix: str = '', suffix: str = '') -> Text:
+ return content_in_quotes(prefix, 'string', suffix)
+
+ def include_in_menu_file(self) -> bool:
+ return False
+
+
+class DocumentationArray(Array, DocumentationPropertyType):
+
+ def update_base(self):
+ if not isinstance(self.property_type, DocumentationPropertyType):
+ update_property_type_base(self.property_type)
+
+ @property
+ def description(self) -> str:
+ return 'array'
+
+ @property
+ def __property_type_doc(self) -> DocumentationPropertyType:
+ return cast(DocumentationPropertyType, self.property_type)
+
+ def json_description(self,
+ obj_stack: List[Declarable],
+ prefix: str,
+ suffix: str) -> Text:
+ array_type_description = self.__property_type_doc.json_description(obj_stack=obj_stack, prefix='', suffix=',')
+ if len(array_type_description.lines) > 1:
+ result = Text(prefix + '[')
+ result += array_type_description.indented()
+ result += ' ...'
+ result += ']' + suffix
+ return result
+ return self.__property_type_doc.json_description(
+ obj_stack=obj_stack,
+ prefix=prefix + '[ ',
+ suffix=', ... ]' + suffix
+ )
+
+ def constraints(self, dictionary: Dict[str, str]) -> str:
+ result = Text()
+ min_items = self.min_items
+ if min_items == 1:
+ result += dictionary['div_generator_non_empty_array']
+ elif min_items > 1:
+ result += dictionary['div_generator_elements_min_number'].format(min_items)
+ if self.strict_parsing:
+ result += dictionary['div_generator_partial_parsing']
+ return str(result)
+
+ def details(self, dictionary: Dict[str, str]) -> str:
+ return self.__property_type_doc.details(dictionary)
+
+
+class DocumentationBool(Bool, DocumentationPropertyType):
+
+ @property
+ def description(self) -> str:
+ return 'bool'
+
+ def constraints(self, dictionary: Dict[str, str]) -> str:
+ return ''
+
+
+class DocumentationBoolInt(BoolInt, DocumentationPropertyType):
+
+ @property
+ def description(self) -> str:
+ return 'bool_int'
+
+ def constraints(self, dictionary: Dict[str, str]) -> str:
+ return ''
+
+
+class DocumentationColor(Color, DocumentationPropertyType):
+
+ @property
+ def description(self) -> str:
+ return 'string'
+
+ def constraints(self, dictionary: Dict[str, str]) -> str:
+ return dictionary['div_generator_valid_formats_color']
+
+
+class DocumentationDictionary(Dictionary, DocumentationPropertyType):
+
+ @property
+ def description(self) -> str:
+ return 'object'
+
+ def constraints(self, dictionary: Dict[str, str]) -> str:
+ return ''
+
+
+class DocumentationDouble(Double, DocumentationPropertyType):
+
+ @property
+ def description(self) -> str:
+ return 'number'
+
+ def constraints(self, dictionary: Dict[str, str]) -> str:
+ return constraints_for_numbers(self, dictionary)
+
+
+class DocumentationInt(Int, DocumentationPropertyType):
+
+ @property
+ def description(self) -> str:
+ return 'int'
+
+ def constraints(self, dictionary: Dict[str, str]) -> str:
+ return constraints_for_numbers(self, dictionary)
+
+
+def constraints_for_numbers(number: Union[Double, Int], dictionary: Dict[str, str]) -> str:
+ if number.constraint is None:
+ return ''
+ constraint_str = number.constraint.replace('number', 'x')
+ return dictionary['div_generator_value_restriction'].format(constraint_str)
+
+
+class DocumentationObject(Object, DocumentationPropertyType):
+
+ def update_base(self):
+ if not isinstance(self.object, DocumentationDeclarable):
+ update_declarable_base(self.object)
+
+ @property
+ def description(self) -> str:
+ if isinstance(self.object, StringEnumeration):
+ return 'string'
+ return 'string' if self.format == ObjectFormat.JSON_STRING else 'object'
+
+ @property
+ def __object_doc(self) -> DocumentationDeclarable:
+ return cast(DocumentationDeclarable, self.object)
+
+ def json_description(self, obj_stack: List[DocumentationDeclarable], prefix: str, suffix: str) -> Text:
+ return self.__object_doc.json_description(obj_stack, prefix, suffix)
+
+ def constraints(self, dictionary: Dict[str, str]) -> str:
+ obj = self.object
+ if isinstance(obj, StringEnumeration):
+ values = ', '.join(map(lambda case: code(case[0]), obj.cases))
+ return dictionary['div_generator_possible_values'].format(values)
+ result = Text(dictionary['div_generator_value_type'].format(obj.name))
+ if self.format == ObjectFormat.JSON_STRING:
+ result += dictionary['div_generator_value_json']
+ return str(result)
+
+ def details(self, dictionary: Dict[str, str]) -> str:
+ if not isinstance(self.object, EntityEnumeration):
+ return ''
+
+ list_header = dictionary['div_generator_values_list']
+ details = list(map(lambda obj: f'[{obj[0]}]({obj[0]}.md#{obj[0]})', self.object.entities))
+ details.insert(0, list_header)
+ return ''.join(details)
+
+
+class DocumentationStaticString(StaticString, DocumentationPropertyType):
+
+ @property
+ def description(self) -> str:
+ return 'string'
+
+ def json_description(self, obj_stack: List[Declarable], prefix: str, suffix: str) -> Text:
+ return content_in_quotes(prefix, self.value, suffix)
+
+ def constraints(self, dictionary: Dict[str, str]) -> str:
+ return dictionary['div_generator_value_must_be'].format(self.value)
+
+
+class DocumentationString(String, DocumentationPropertyType):
+
+ @property
+ def description(self) -> str:
+ return 'string'
+
+ def constraints(self, dictionary: Dict[str, str]) -> str:
+ result = Text()
+ min_length = self.min_length
+ if min_length == 1:
+ result += dictionary['div_generator_non_empty_string']
+ elif min_length > 1:
+ result += dictionary['div_generator_min_length'].format(min_length)
+ if self.formatted:
+ result += dictionary['div_generator_html_formatting']
+ if self.regex is not None:
+ result += dictionary['div_generator_value_regex'].format(self.regex.pattern)
+ return str(result)
+
+
+class DocumentationUrl(Url, DocumentationPropertyType):
+
+ @property
+ def description(self) -> str:
+ return 'string'
+
+ def constraints(self, dictionary: Dict[str, str]) -> str:
+ result = Text(dictionary['div_generator_value_url'])
+ schemes = self.schemes
+ if schemes is not None and len(schemes) > 0:
+ schemes_str = ', '.join(map(lambda s: code(s), schemes))
+ result += dictionary['div_generator_schemes'].format(schemes_str)
+ return str(result)
diff --git a/api_generator/api_generator/generators/documentation/generator.py b/api_generator/api_generator/generators/documentation/generator.py
new file mode 100644
index 000000000..1241358b9
--- /dev/null
+++ b/api_generator/api_generator/generators/documentation/generator.py
@@ -0,0 +1,174 @@
+import os
+from pathlib import Path
+from typing import List, Optional
+
+from .documentation_entities import (
+ update_declarable_base,
+ DocumentationDeclarable,
+ DocumentationEntity,
+ DocumentationProperty
+)
+from .translations import translations
+from .utils import bold, code, paragraph
+from ..base import Generator
+from ...config import Config, Platform, DescriptionLanguage
+from ...schema.modeling.entities import (
+ Declarable,
+ Entity,
+ EntityEnumeration,
+ StringEnumeration,
+ Property
+)
+from ...schema.modeling.text import Text
+
+
+class DocumentationGenerator(Generator):
+
+ def __init__(self, config: Config) -> None:
+ super().__init__(config)
+ self.__lang = DescriptionLanguage.EN
+ self.__translations = {}
+
+ def generate(self, objects: List[Declarable]):
+ self._clear_output_directory()
+ objects_doc: List[DocumentationDeclarable] = list(map(lambda obj: update_declarable_base(obj), objects))
+ for lang in DescriptionLanguage:
+ self.__lang = lang
+ self._output_path = self._config.output_path + '/' + lang
+ self.__translations = translations(lang)
+ Path(self._output_path).mkdir(parents=True, exist_ok=True)
+ self._generate_files(objects_doc)
+ self.__make_menu_file(objects_doc)
+
+ def __make_menu_file(self, objects: List[DocumentationDeclarable]):
+ filename = os.path.join(self._config.output_path, 'toc.yaml')
+ with open(filename, 'w') as file:
+ file.write(self.__menu_content(objects))
+
+ def __menu_content(self, objects: List[DocumentationDeclarable]) -> str:
+ items = '\n'.join(map(
+ lambda obj: self.__menu_item(obj),
+ sorted(
+ filter(lambda obj: obj.include_in_menu_file, objects),
+ key=lambda obj: obj.name < obj.name
+ )
+ ))
+ return '\n'.join([
+ 'title:',
+ 'href:',
+ 'items:',
+ items
+ ])
+
+ @staticmethod
+ def __menu_item(obj: Declarable) -> str:
+ toc = '' if obj.include_in_documentation_toc else '\n hidden: true'
+ return '\n'.join([
+ f' -name: {obj.name}',
+ f' href: concepts/divs/{obj.name}.md{toc}'
+ ])
+
+ def _filename(self, name: str) -> str:
+ return name + '.md'
+
+ def _entity_declaration(self, entity: DocumentationEntity) -> Text:
+ deprecation = self.__translations['div_generator_type_deprecated'] if entity.is_deprecated else ''
+ header = '\n'.join(filter(
+ lambda text: text.strip(),
+ [entity.display_name, self.__description(entity), deprecation]
+ ))
+ params_header = self.__translations['div_generator_parameters']
+ return Text([
+ f'# {header}',
+ '',
+ '## JSON',
+ '```json',
+ str(entity.json_description(obj_stack=[])),
+ '```',
+ '',
+ f'## {params_header}',
+ self.__fields_table(entity)
+ ])
+
+ def __description(self, entity: DocumentationEntity) -> str:
+ description = entity.description_doc(self.__lang)
+ return description if description else self.__translations['div_generator_no_description']
+
+ def __fields_table(self, entity: DocumentationEntity, table_name: Optional[str] = None) -> str:
+ header = ('### ' + table_name + '\n') if table_name is not None else ''
+ params_header = self.__translations['div_generator_parameters']
+ description_header = self.__translations['div_generator_description']
+
+ properties_table_content = Text(list(map(
+ lambda p: f'| {code(p.dict_field)} | {self.__table_description(p)} |',
+ entity.properties_doc
+ )))
+
+ inner_types_tables = '\n\n'.join(map(
+ lambda inner_entity: self.__fields_table(entity=inner_entity, table_name=inner_entity.name),
+ filter(lambda inner_type: isinstance(inner_type, Entity), entity.inner_types)
+ ))
+ inner_types_tables = ('\n\n' + inner_types_tables) if inner_types_tables.strip() else ''
+
+ if len(properties_table_content.lines) == 0:
+ return inner_types_tables
+
+ return '\n'.join([
+ f'{header}| {params_header} | {description_header} |',
+ '| --- | --- |',
+ f'{properties_table_content}{inner_types_tables}'
+ ])
+
+ def __table_description(self, prop: DocumentationProperty) -> str:
+ property_type = prop.property_type_doc
+ optionality = '' if prop.optional else self.__translations['div_generator_required_parameter']
+ deprecation = self.__translations['div_generator_parameter_deprecated'] if prop.is_deprecated else ''
+ default_value = '' if prop.default_value is None else \
+ self.__translations['div_generator_default_value'].format(prop.default_value)
+
+ return ''.join(map(
+ lambda s: paragraph(s),
+ map(
+ lambda t: t.replace('\n', '
'),
+ filter(None, [
+ bold(property_type.description),
+ optionality,
+ deprecation,
+ prop.description_doc(self.__lang),
+ property_type.constraints(self.__translations),
+ default_value,
+ self.__platforms(prop),
+ property_type.details(self.__translations)
+ ])
+ )
+ ))
+
+ def __platforms(self, prop: Property) -> str:
+ if prop.platforms is None:
+ return ''
+ if len(prop.platforms) == 0:
+ return self.__translations['div_generator_in_progress']
+ platform_names = ', '.join(map(lambda p: self.__platform_name(p), prop.platforms))
+ return self.__translations['div_generator_platforms'].format(platform_names)
+
+ def __platform_name(self, platform: Platform) -> str:
+ if platform == Platform.ANDROID:
+ return self.__translations['div_generator_android']
+ if platform == Platform.IOS:
+ return self.__translations['div_generator_ios']
+ if platform == Platform.WEB:
+ return self.__translations['div_generator_web']
+ raise NotImplementedError
+
+ def _entity_enumeration_declaration(self, entity_enumeration: EntityEnumeration) -> Text:
+ header = self.__translations['div_generator_entity_enumeration']
+ enumeration = '\n'.join(map(lambda entity: f'* [{entity[0]}]({entity[0]}.md#{entity[0]})',
+ entity_enumeration.entities))
+ return Text([
+ f'# {entity_enumeration.name}',
+ header,
+ enumeration
+ ])
+
+ def _string_enumeration_declaration(self, string_enumeration: StringEnumeration) -> Text:
+ return Text()
diff --git a/api_generator/api_generator/generators/documentation/translations.py b/api_generator/api_generator/generators/documentation/translations.py
new file mode 100644
index 000000000..233181dd4
--- /dev/null
+++ b/api_generator/api_generator/generators/documentation/translations.py
@@ -0,0 +1,129 @@
+from typing import Dict
+from ...config import DescriptionLanguage
+
+__full_translations: Dict[str, Dict[str, str]] = {
+ "div_generator_entity_enumeration": {
+ "en": "It can be one of the following types:",
+ "ru": "Может быть одним из следующих типов:"
+ },
+ "div_generator_values_list": {
+ "en": "List of possible values:",
+ "ru": "Список возможных значений:"
+ },
+ "div_generator_non_empty_array": {
+ "en": "An array must not be empty.",
+ "ru": "Массив должен быть непустым."
+ },
+ "div_generator_elements_min_number": {
+ "en": "The minimum number of elements is {}.",
+ "ru": "Минимальное количество элементов — {}."
+ },
+ "div_generator_partial_parsing": {
+ "en": "Partial parsing is not allowed.",
+ "ru": "Не допускается частичный парсинг."
+ },
+ "div_generator_valid_formats_color": {
+ "en": "Valid formats: `#RGB`, `#ARGB`, `#RRGGBB`, `#AARRGGBB`.",
+ "ru": "Допустимые форматы: `#RGB`, `#ARGB`, `#RRGGBB`, `#AARRGGBB`."
+ },
+ "div_generator_value_restriction": {
+ "en": "Restriction for the value `x`: `{}`.",
+ "ru": "Ограничение для значения `x`: `{}`."
+ },
+ "div_generator_possible_values": {
+ "en": "Possible values: {}.",
+ "ru": "Возможные значения: {}."
+ },
+ "div_generator_value_type": {
+ "en": "The value has the type `{}`.",
+ "ru": "Значение имеет тип `{}`."
+ },
+ "div_generator_value_json": {
+ "en": "**Attention:** the value is represented as a JSON string, not an object.",
+ "ru": "**Внимание:** значение представлено в виде JSON-строки, а не объекта."
+ },
+ "div_generator_value_must_be": {
+ "en": "The value must always be `{}`.",
+ "ru": "Значение всегда должно равняться `{}`."
+ },
+ "div_generator_value_url": {
+ "en": "The value must be a valid URL.",
+ "ru": "Значение должно быть валидным URL."
+ },
+ "div_generator_schemes": {
+ "en": "Allowed schemes: {}.",
+ "ru": "Разрешенные схемы: {}."
+ },
+ "div_generator_non_empty_string": {
+ "en": "A string must not be empty.",
+ "ru": "Строка должна быть непустой."
+ },
+ "div_generator_min_length": {
+ "en": "Minimum length {}.",
+ "ru": "Минимальная длина {}."
+ },
+ "div_generator_html_formatting": {
+ "en": "Limited HTML formatting is allowed.",
+ "ru": "Допускается ограниченное HTML-форматирование."
+ },
+ "div_generator_value_regex": {
+ "en": "The value must match the regular expression `{}`.",
+ "ru": "Значение должно удовлетворять регулярному выражению `{}`."
+ },
+ "div_generator_default_value": {
+ "en": "Default value: `{}`.",
+ "ru": "Значение по умолчанию: `{}`."
+ },
+ "div_generator_required_parameter": {
+ "en": "Required parameter.",
+ "ru": "Обязательный параметр."
+ },
+ "div_generator_parameter_deprecated": {
+ "en": "Parameter is deprecated",
+ "ru": "Параметр устарел."
+ },
+ "div_generator_in_progress": {
+ "en": "The functionality is under development.",
+ "ru": "Функциональность находится в разработке."
+ },
+ "div_generator_platforms": {
+ "en": "Available platforms: {}.",
+ "ru": "Доступные платформы: {}."
+ },
+ "div_generator_android": {
+ "en": "Android",
+ "ru": "Android"
+ },
+ "div_generator_ios": {
+ "en": "iOS",
+ "ru": "iOS"
+ },
+ "div_generator_web": {
+ "en": "browser",
+ "ru": "браузер"
+ },
+ "div_generator_parameters": {
+ "en": "Parameters",
+ "ru": "Параметры"
+ },
+ "div_generator_type_deprecated": {
+ "en": "Type is deprecated.",
+ "ru": "Тип устарел."
+ },
+ "div_generator_description": {
+ "en": "Description",
+ "ru": "Описание"
+ },
+ "div_generator_no_description": {
+ "en": "There is no description yet.",
+ "ru": "Описания пока нет"
+ }
+}
+
+
+def translations(lang: DescriptionLanguage) -> Dict[str, str]:
+ result = dict()
+ lang_value = lang.value
+ for key, values in __full_translations.items():
+ result[key] = values[lang_value]
+ return result
diff --git a/api_generator/api_generator/generators/documentation/utils.py b/api_generator/api_generator/generators/documentation/utils.py
new file mode 100644
index 000000000..a68872380
--- /dev/null
+++ b/api_generator/api_generator/generators/documentation/utils.py
@@ -0,0 +1,17 @@
+from ...schema.modeling.text import Text
+
+
+def bold(content: str) -> str:
+ return f'**{content}**'
+
+
+def code(content: str) -> str:
+ return f'`{content}`'
+
+
+def content_in_quotes(prefix: str, content: str, suffix: str) -> Text:
+ return Text(f'{prefix}"{content}"{suffix}')
+
+
+def paragraph(content: str) -> str:
+ return f'
{content}
'
diff --git a/api_generator/api_generator/generators/kotlin/__init__.py b/api_generator/api_generator/generators/kotlin/__init__.py
new file mode 100644
index 000000000..0af3b8d21
--- /dev/null
+++ b/api_generator/api_generator/generators/kotlin/__init__.py
@@ -0,0 +1,3 @@
+from .generator import KotlinGenerator
+
+__all__ = [KotlinGenerator]
diff --git a/api_generator/api_generator/generators/kotlin/generator.py b/api_generator/api_generator/generators/kotlin/generator.py
new file mode 100644
index 000000000..e1ff35bc0
--- /dev/null
+++ b/api_generator/api_generator/generators/kotlin/generator.py
@@ -0,0 +1,322 @@
+from typing import cast
+
+from .kotlin_entities import (
+ KotlinEntity,
+ KotlinProperty,
+ KotlinEntityEnumeration,
+ PARSING_ERRORS_PROP_NAME,
+ ENTITY_STATIC_CREATOR
+)
+from ..base import Generator
+from ... import utils
+from ...config import GenerationMode, GeneratedLanguage, TEMPLATE_SUFFIX
+from ...schema.modeling.entities import (
+ StringEnumeration,
+ EntityEnumeration,
+ Entity,
+ Object,
+ ObjectFormat,
+)
+from ...schema.modeling.text import Text, EMPTY
+
+
+class KotlinGenerator(Generator):
+ def __init__(self, config):
+ super(KotlinGenerator, self).__init__(config)
+ self.kotlin_annotations = config.generation.kotlin_annotations
+ self._error_collectors = config.generation.errors_collectors
+ self._generate_equality = config.generation.generate_equality
+
+ def _filename(self, name: str) -> str:
+ return f'{utils.capitalize_camel_case(name)}.kt'
+
+ def _entity_declaration(self, entity: Entity) -> Text:
+ entity: KotlinEntity = cast(KotlinEntity, entity)
+ entity.__class__ = KotlinEntity
+ entity.eval_errors_collector_enabled(self._error_collectors)
+ entity.update_bases()
+
+ if entity.generate_as_protocol:
+ return self.__declaration_as_interface(entity)
+ result: Text = self.__main_declaration_header(entity)
+
+ is_template = entity.generation_mode.is_template
+
+ if is_template:
+ result += EMPTY
+ result += ' constructor ('
+ result += ' env: ParsingEnvironment,'
+ result += f' parent: {utils.capitalize_camel_case(entity.name)}? = null,'
+ result += ' topLevel: Boolean = false,'
+ result += ' json: JSONObject'
+ result += ' ) {'
+ result += ' val logger = env.logger'
+ constructor = entity.constructor_body(with_commas=False).indented(indent_width=8)
+ if constructor.lines:
+ result += constructor
+ result += ' }'
+ result += EMPTY
+ result += entity.value_resolving_declaration.indented(indent_width=4)
+
+ result += EMPTY
+ result += entity.serialization_declaration.indented(indent_width=4)
+
+ if not is_template and self._generate_equality and not entity.instance_properties:
+ result += EMPTY
+ result += self.__manual_equals_hash_code_declaration.indented(indent_width=4)
+
+ static_declarations = entity.static_declarations
+ if static_declarations.lines:
+ result += EMPTY
+ result += ' companion object {'
+ result += static_declarations.indented(indent_width=8)
+ result += ' }'
+ result += EMPTY
+
+ if entity.inner_types:
+ for inner_type in filter(lambda t: not isinstance(t, StringEnumeration) or not is_template,
+ entity.inner_types):
+ result += EMPTY
+ result += self._main_declaration(inner_type).indented(indent_width=4)
+
+ result += '}'
+
+ return result
+
+ @staticmethod
+ def __declaration_as_interface(entity: KotlinEntity) -> Text:
+ result = Text(f'interface {utils.capitalize_camel_case(entity.name)} {{')
+ for prop in entity.instance_properties_kotlin:
+ result += prop.declaration(overridden=False,
+ in_interface=True,
+ with_comma=False,
+ with_default=False).indented(indent_width=4)
+ result += '}'
+ return result
+
+ def __main_declaration_header(self, entity: KotlinEntity) -> Text:
+ result = Text()
+ for annotation in self.kotlin_annotations:
+ result += annotation
+ data_prefix = 'data '
+ if entity.generation_mode.is_template or not self._generate_equality or not entity.instance_properties:
+ data_prefix = ''
+ prefix = f'{data_prefix}class {utils.capitalize_camel_case(entity.name)}'
+
+ interfaces = 'JSONSerializable'
+ protocol_plus_super_entities = entity.protocol_plus_super_entities
+ if protocol_plus_super_entities is not None:
+ interfaces += f', {protocol_plus_super_entities}'
+ suffix = f' : {interfaces} {{'
+
+ def add_instance_properties(text: Text, is_template: bool) -> Text:
+ mixed_properties = entity.instance_properties_kotlin
+ if entity.errors_collector_enabled:
+ mixed_properties.append(KotlinProperty(
+ name=PARSING_ERRORS_PROP_NAME,
+ description='',
+ description_translations={},
+ dict_field='',
+ property_type=Object(name='List', object=None, format=ObjectFormat.DEFAULT),
+ optional=True,
+ is_deprecated=False,
+ mode=GenerationMode.NORMAL_WITHOUT_TEMPLATES,
+ supports_expressions_flag=False,
+ default_value=None,
+ platforms=None
+ ))
+ for prop in mixed_properties:
+ overridden = False
+ if entity.implemented_protocol is not None:
+ overridden = any(p.name == prop.name for p in entity.implemented_protocol.properties)
+ text += prop.declaration(
+ overridden=overridden,
+ in_interface=False,
+ with_comma=not is_template,
+ with_default=not is_template
+ ).indented(indent_width=4)
+ return text
+
+ if entity.generation_mode.is_template:
+ result += prefix + suffix
+ if entity.instance_properties:
+ result = add_instance_properties(text=result, is_template=True)
+ else:
+ if not entity.instance_properties:
+ result += f'{prefix}(){suffix}'
+ else:
+ result += f'{prefix}('
+ result = add_instance_properties(text=result, is_template=False)
+ result += f'){suffix}'
+
+ return result
+
+ @property
+ def __manual_equals_hash_code_declaration(self) -> Text:
+ result = Text('override fun equals(other: Any?) = javaClass == other?.javaClass')
+ result += EMPTY
+ result += 'override fun hashCode() = javaClass.hashCode()'
+ return result
+
+ def _entity_enumeration_declaration(self, entity_enumeration: EntityEnumeration) -> Text:
+ entity_enumeration: KotlinEntityEnumeration = cast(KotlinEntityEnumeration, entity_enumeration)
+ entity_enumeration.__class__ = KotlinEntityEnumeration
+ declaration_name = utils.capitalize_camel_case(entity_enumeration.name)
+ entity_declarations = list(map(utils.capitalize_camel_case, entity_enumeration.entity_names))
+ naming = entity_enumeration.case_naming
+ result = Text()
+ for annotation in self.kotlin_annotations:
+ result += annotation
+ interfaces = ['JSONSerializable', entity_enumeration.mode.protocol_name(
+ lang=GeneratedLanguage.KOTLIN,
+ name=entity_enumeration.resolved_prefixed_declaration)]
+ interfaces = ', '.join(filter(None, interfaces))
+
+ result += f'sealed class {declaration_name} : {interfaces} {{'
+ for decl in entity_declarations:
+ decl = f'class {naming.format_case_name(decl)}(val value: {decl}) : {declaration_name}()'
+ result += Text(indent_width=4, init_lines=decl)
+ result += EMPTY
+
+ result += f' fun value(): {entity_enumeration.common_interface(GeneratedLanguage.KOTLIN) or "Any"} {{'
+ result += ' return when (this) {'
+ for decl in entity_declarations:
+ decl = f'is {naming.format_case_name(decl)} -> value'
+ result += Text(indent_width=12, init_lines=decl)
+ result += ' }'
+ result += ' }'
+ result += EMPTY
+
+ result += ' override fun writeToJSON(): JSONObject {'
+ result += ' return when (this) {'
+ for decl in entity_declarations:
+ decl = f'is {naming.format_case_name(decl)} -> value.writeToJSON()'
+ result += Text(indent_width=12, init_lines=decl)
+ result += ' }'
+ result += ' }'
+ result += EMPTY
+
+ if entity_enumeration.mode.is_template:
+ self_name = entity_enumeration.resolved_prefixed_declaration
+
+ result += f' override fun resolve(env: ParsingEnvironment, data: JSONObject): {self_name} {{'
+ result += ' return when (this) {'
+ for decl in entity_declarations:
+ case_name = naming.format_case_name(decl)
+ line = f'is {case_name} -> {self_name}.{case_name}(value.resolve(env, data))'
+ result += Text(indent_width=12, init_lines=line)
+ result += ' }'
+ result += ' }'
+ result += EMPTY
+
+ result += ' val type: String'
+ result += ' get() {'
+ result += ' return when (this) {'
+ for decl in entity_declarations:
+ line = f'is {naming.format_case_name(decl)} -> {decl}.TYPE'
+ result += Text(indent_width=16, init_lines=line)
+ result += ' }'
+ result += ' }'
+ result += EMPTY
+ elif self._generate_equality:
+ result += ' override fun equals(other: Any?): Boolean {'
+ result += ' if (this === other) { return true }'
+ result += f' if (other is {declaration_name}) {{'
+ result += ' return value().equals(other.value())'
+ result += ' }'
+ result += ' return false'
+ result += ' }'
+ result += EMPTY
+
+ result += ' companion object {'
+ result += ' @Throws(ParsingException::class)'
+
+ source_name = 'json'
+ source_type = 'JSONObject'
+ read_type_expr = 'json.read("type", logger = logger, env = env)'
+ throwing_expr = 'throw typeMismatch(json = json, key = "type", value = type)'
+
+ if entity_enumeration.mode.is_template:
+ def deserialization_args(s):
+ return f'env, parent?.value() as {s}?, topLevel, {source_name}'
+ result += ' operator fun invoke('
+ result += ' env: ParsingEnvironment,'
+ result += ' topLevel: Boolean = false,'
+ result += f' {source_name}: {source_type}'
+ result += f' ): {declaration_name} {{'
+ result += ' val logger = env.logger'
+ result += f' val receivedType: String = {read_type_expr}'
+ result += f' val parent = env.templates[receivedType] as? {declaration_name}'
+ result += ' val type = parent?.type ?: receivedType'
+ else:
+ def deserialization_args(s):
+ return f'env, {source_name}'
+ result += ' @JvmStatic'
+ result += ' @JvmName("fromJson")'
+ args = f'env: ParsingEnvironment, {source_name}: {source_type}'
+ result += f' operator fun invoke({args}): {declaration_name} {{'
+ result += ' val logger = env.logger'
+ result += f' val type: String = {read_type_expr}'
+ result += ' when (type) {'
+ for decl in entity_declarations:
+ line = f'{decl}.TYPE -> return {naming.format_case_name(decl)}({decl}({deserialization_args(decl)}))'
+ result += Text(indent_width=16, init_lines=line)
+
+ if entity_enumeration.mode is GenerationMode.NORMAL_WITH_TEMPLATES:
+ result += ' }'
+ name = utils.capitalize_camel_case(entity_enumeration.name + TEMPLATE_SUFFIX)
+ template_type = entity_enumeration.template_declaration_prefix + name
+ result += f' val template = env.templates.getOrThrow(type, json) as? {template_type}'
+ result += ' if (template != null) {'
+ result += f' return template.resolve(env, {source_name})'
+ result += ' } else {'
+ result += f' {throwing_expr}'
+ result += ' }'
+ else:
+ result += f' else -> {throwing_expr}'
+ result += ' }'
+ result += ' }'
+
+ static_creator_lambda = f'env: ParsingEnvironment, it: JSONObject -> {declaration_name}(env, json = it)'
+ result += f' val {ENTITY_STATIC_CREATOR} = {{ {static_creator_lambda} }}'
+ result += ' }'
+ result += '}'
+ return result
+
+ def _string_enumeration_declaration(self, string_enumeration: StringEnumeration) -> Text:
+ declaration_name = utils.capitalize_camel_case(string_enumeration.name)
+ cases_declarations = list(map(lambda s: Text(indent_width=16, init_lines=f'{s}.value -> {s}'),
+ map(lambda s: utils.fixing_first_digit(utils.constant_upper_case(s[0])),
+ string_enumeration.cases)))
+ result = Text(f'enum class {declaration_name}(private val value: String) {{')
+ for ind, case in enumerate(string_enumeration.cases):
+ terminal = ',' if ind != (len(cases_declarations) - 1) else ';'
+ name = utils.fixing_first_digit(utils.constant_upper_case(case[0]))
+ value = case[1]
+ result += Text(indent_width=4, init_lines=f'{name}("{value}"){terminal}')
+ result += EMPTY
+
+ result += ' companion object Converter {'
+ result += f' fun toString(obj: {declaration_name}): String {{'
+ result += ' return obj.value'
+ result += ' }'
+ result += EMPTY
+
+ result += f' fun fromString(string: String): {declaration_name}? {{'
+ result += ' return when (string) {'
+ result += cases_declarations
+ result += ' else -> null'
+ result += ' }'
+ result += ' }'
+ result += EMPTY
+
+ result += ' val FROM_STRING = { string: String ->'
+ result += ' when (string) {'
+ result += cases_declarations
+ result += ' else -> null'
+ result += ' }'
+ result += ' }'
+ result += ' }'
+ result += '}'
+
+ return result
diff --git a/api_generator/api_generator/generators/kotlin/kotlin_entities.py b/api_generator/api_generator/generators/kotlin/kotlin_entities.py
new file mode 100644
index 000000000..c515e4323
--- /dev/null
+++ b/api_generator/api_generator/generators/kotlin/kotlin_entities.py
@@ -0,0 +1,784 @@
+from __future__ import annotations
+from functools import reduce
+import dataclasses
+from typing import List, Optional, cast
+
+from ..base import declaration_comment
+from ...schema.modeling.entities import (
+ Entity,
+ EntityEnumeration,
+ StringEnumeration,
+ Property,
+ PropertyType,
+ Int,
+ Bool,
+ BoolInt,
+ Double,
+ StaticString,
+ Object,
+ Array,
+ Url,
+ Color,
+ String,
+ Dictionary,
+ ObjectFormat
+)
+from ...config import GenerationMode
+from ... import utils
+from ...schema.modeling.text import Text, EMPTY
+
+EXPRESSION_TYPE_NAME = 'Expression'
+EXPRESSION_LIST_TYPE_NAME = 'ExpressionsList'
+PARSING_ERRORS_PROP_NAME = "parsingErrors"
+ENTITY_STATIC_CREATOR = 'CREATOR'
+
+
+def _number_validator_decl(type: str, constraint: Optional[str]) -> Optional[str]:
+ if constraint is None:
+ return None
+ return f'{{ it: {type} -> {constraint.replace("number", "it")} }}'
+
+
+def _kotlin_default_value_declaration_comment(p: Property) -> str:
+ if isinstance(p.property_type, Object) and not isinstance(p.property_type.object, StringEnumeration):
+ property_type = cast(KotlinPropertyType, Object(name='',
+ object=p.property_type.object,
+ format=ObjectFormat.DEFAULT))
+ comment_value = property_type.declaration_by_default_value(p.default_value, True)
+ else:
+ comment_value = p.default_value
+ return comment_value
+
+
+class KotlinEntity(Entity):
+ errors_collector_enabled: bool
+
+ def update_bases(self):
+ Int.__bases__ = (KotlinPropertyType, PropertyType,)
+ Bool.__bases__ = (KotlinPropertyType, PropertyType,)
+ BoolInt.__bases__ = (KotlinPropertyType, PropertyType,)
+ Double.__bases__ = (KotlinPropertyType, PropertyType,)
+ StaticString.__bases__ = (KotlinPropertyType, PropertyType,)
+ Object.__bases__ = (KotlinPropertyType, PropertyType,)
+ Array.__bases__ = (KotlinPropertyType, PropertyType,)
+ Url.__bases__ = (KotlinPropertyType, PropertyType,)
+ Color.__bases__ = (KotlinPropertyType, PropertyType,)
+ String.__bases__ = (KotlinPropertyType, PropertyType,)
+ Dictionary.__bases__ = (KotlinPropertyType, PropertyType,)
+ for prop in self.properties:
+ prop.__class__ = KotlinProperty
+
+ @property
+ def properties_kotlin(self) -> List[KotlinProperty]:
+ result = []
+ for prop in self.properties:
+ prop.__class__ = KotlinProperty
+ result.append(cast(KotlinProperty, prop))
+ return result
+
+ @property
+ def instance_properties_kotlin(self) -> List[KotlinProperty]:
+ result = []
+ for prop in self.instance_properties:
+ prop.__class__ = KotlinProperty
+ result.append(cast(KotlinProperty, prop))
+ return result
+
+ def eval_errors_collector_enabled(self, errors_collectors: List[str]):
+ self.errors_collector_enabled = not self.generation_mode.is_template and self.original_name in errors_collectors
+
+ def constructor_body(self, with_commas: bool, extra_properties: List[str] = None) -> Text:
+ if extra_properties is None:
+ extra_properties = []
+ if not self.instance_properties and not extra_properties:
+ return Text()
+ expressions = []
+ for prop in self.instance_properties_kotlin:
+ expressions.append(prop.deserialization_declaration(mode=self.generation_mode))
+ expressions.extend(extra_properties)
+ result = Text()
+ for ind, expr in enumerate(expressions):
+ comma = ''
+ if with_commas and ind != (len(expressions) - 1):
+ comma = ','
+ result += f'{expr}{comma}'
+ return result
+
+ @property
+ def value_resolving_declaration(self) -> Text:
+ args = 'env: ParsingEnvironment, data: JSONObject'
+ result = Text(f'override fun resolve({args}): {self.resolved_prefixed_declaration} {{')
+ if not self.instance_properties:
+ result += f' return {self.resolved_prefixed_declaration}()'
+ else:
+ result += f' return {self.resolved_prefixed_declaration}('
+ prop_decl = Text()
+ props = self.instance_properties_kotlin
+ for ind, p in enumerate(props):
+ ending = ',' if ind != (len(props) - 1) else ''
+ template_deserialization = p.make_template_deserialization(dict_field=p.dict_field,
+ value_override=None)
+ prop_decl += f'{template_deserialization}{ending}'
+ result += prop_decl.indented(indent_width=8)
+ result += ' )'
+ result += '}'
+ return result
+
+ @property
+ def serialization_declaration(self) -> Text:
+ result = Text('override fun writeToJSON(): JSONObject {')
+ result += ' val json = JSONObject()'
+ for prop in self.properties_kotlin:
+ result += prop.serialization_declaration.indented(indent_width=4)
+ result += ' return json'
+ result += '}'
+ return result
+
+ @property
+ def static_declarations(self) -> Text:
+ properties_kotlin = self.properties_kotlin
+ is_template = self.generation_mode.is_template
+ name = utils.capitalize_camel_case(self.name)
+ static_properties = list(filter(lambda p: isinstance(p.property_type, StaticString), properties_kotlin))
+ default_values = []
+ instance_properties_kotlin = self.instance_properties_kotlin
+ for p in instance_properties_kotlin:
+ decl = p.default_value_declaration
+ if decl is not None:
+ default_values.append(decl)
+ type_helpers = []
+ for p in filter(lambda p: cast(KotlinPropertyType, p.property_type).is_enum_of_expressions,
+ instance_properties_kotlin):
+ type_helpers.append(p.type_helper_declaration)
+ groups = []
+ if static_properties:
+ static_decl = Text()
+ for static in static_properties:
+ static_decl += static.declaration(overridden=False,
+ in_interface=False,
+ with_comma=False,
+ with_default=False)
+ groups.append(static_decl)
+ if default_values:
+ groups.append(Text(default_values))
+
+ if type_helpers:
+ groups.append(Text(type_helpers))
+
+ if not is_template:
+ constructor = Text()
+ constructor += '@JvmStatic'
+ constructor += '@JvmName("fromJson")'
+ constructor += f'operator fun invoke(env: ParsingEnvironment, json: JSONObject): {name} {{'
+
+ if self.errors_collector_enabled:
+ constructor += ' val env = env.withErrorsCollector()'
+
+ constructor += ' val logger = env.logger'
+ constructor += f' return {name}('
+ extra_properties = []
+ if self.errors_collector_enabled:
+ extra_properties.append(f'{PARSING_ERRORS_PROP_NAME} = env.collectErrors()')
+
+ constructor_body = self.constructor_body(with_commas=True,
+ extra_properties=extra_properties).indented(indent_width=8)
+ if constructor_body.lines:
+ constructor += constructor_body
+ constructor += utils.indented(')', indent_width=4)
+ constructor += '}'
+ groups.append(constructor)
+
+ validators = Text()
+ for p in properties_kotlin:
+ validator_or_empty = p.static_validator_expression(with_template_validators=is_template)
+ if not str(validator_or_empty):
+ continue
+ validators += validator_or_empty
+ if validators.lines:
+ groups.append(validators)
+
+ if is_template:
+ readers = Text()
+ for p in properties_kotlin:
+ readers += p.static_reader_deserialization_expression
+ groups.append(readers)
+ static_creator_lambda = f'env: ParsingEnvironment, it: JSONObject -> {name}(env, json = it)'
+ groups.append(Text(f'val {ENTITY_STATIC_CREATOR} = {{ {static_creator_lambda} }}'))
+
+ result = Text()
+ for ind, group in enumerate(groups):
+ result += group
+ if ind != (len(groups) - 1):
+ result += EMPTY
+ return result
+
+
+class KotlinProperty(Property):
+ @property
+ def declaration_name(self) -> str:
+ if isinstance(self.property_type, StaticString):
+ name = utils.constant_upper_case(self.name)
+ else:
+ name = utils.lower_camel_case(self.name)
+ return utils.fixing_first_digit(name)
+
+ @property
+ def default_value_var_name(self) -> str:
+ return f'{utils.constant_upper_case(self.declaration_name)}_DEFAULT_VALUE'
+
+ @property
+ def default_value_declaration(self) -> Optional[str]:
+ default_value_definition = self.default_value_definition
+ if default_value_definition is not None:
+ return f'private val {self.default_value_var_name} = {default_value_definition}'
+ return None
+
+ @property
+ def default_value_definition(self) -> Optional[str]:
+ if self.default_value is not None:
+ declaration = cast(KotlinPropertyType, self.property_type).declaration_by_default_value(
+ default_value=self.default_value,
+ string_enum_prefixed=self.mode.is_template
+ )
+ if declaration is not None:
+ return declaration
+ empty_dict_deserialization = cast(KotlinPropertyType, self.property_type).empty_dict_deserialization
+ if empty_dict_deserialization is not None:
+ return empty_dict_deserialization
+ return None
+
+ @property
+ def should_be_optional(self) -> bool:
+ prop_type = cast(KotlinPropertyType, self.property_type)
+ if self.mode.is_template:
+ return False
+ return self.optional and (self.default_value is None) and prop_type.empty_dict_deserialization is None
+
+ @property
+ def parsed_value_is_optional(self) -> bool:
+ prop_type = cast(KotlinPropertyType, self.property_type)
+ return self.optional or self.default_value is not None or prop_type.empty_dict_deserialization is not None
+
+ @property
+ def use_expression_type(self) -> bool:
+ prop = cast(KotlinPropertyType, self.property_type)
+ if prop.is_array_of_expressions:
+ return False
+ return self.supports_expressions or prop.is_enum_of_expressions
+
+ @property
+ def type_declaration(self) -> str:
+ if self.mode.is_template:
+ if self.use_expression_type:
+ prefix = f'Field<{EXPRESSION_TYPE_NAME}<'
+ suffix = '>>'
+ else:
+ prefix = 'Field<'
+ suffix = '>'
+ else:
+ if self.use_expression_type:
+ prefix = f'{EXPRESSION_TYPE_NAME}<'
+ suffix = '>'
+ else:
+ prefix = ''
+ suffix = ''
+ type_decl = cast(KotlinPropertyType, self.property_type).declaration(self.mode)
+ return f'{prefix}{type_decl}{suffix}{"?" if self.should_be_optional else ""}'
+
+ def declaration(self, overridden: bool, in_interface: bool, with_comma: bool, with_default: bool) -> Text:
+ if isinstance(self.property_type, StaticString):
+ assert not overridden and not with_comma
+ return Text(f'const val {self.declaration_name} = "{self.property_type.value}"')
+ if overridden:
+ prefix = 'override '
+ assert not in_interface
+ elif not in_interface:
+ prefix = '@JvmField final '
+ else:
+ prefix = ''
+ comma = ',' if with_comma else ''
+ default_assignment = ''
+ if with_default:
+ if self.should_be_optional:
+ default_assignment = ' = null'
+ elif self.default_value_declaration is not None:
+ default_assignment = f' = {self.default_value_var_name}'
+ comment = declaration_comment(self, _kotlin_default_value_declaration_comment)
+ return Text(f'{prefix}val {self.declaration_name}: {self.type_declaration}{default_assignment}{comma}{comment}')
+
+ def deserialization_declaration(self, mode: GenerationMode) -> str:
+ deserialization_expr = self.deserialization_expression(mode=mode, reuse_logger_instance=True)
+ return f'{self.declaration_name} = {deserialization_expr}{self.default_value_coalescing(mode)}'
+
+ def deserialization_expression(self, mode: GenerationMode, reuse_logger_instance: bool) -> str:
+ if isinstance(self.property_type, Array):
+ strict = 'Strict' if self.property_type.strict_parsing else ''
+ if self.supports_expressions and cast(KotlinPropertyType, self.property_type).is_array_of_expressions:
+ list_or_empty = f'{strict}{EXPRESSION_LIST_TYPE_NAME}'
+ else:
+ list_or_empty = f'{strict}List'
+ expression_or_empty = ''
+ expression_suffix_or_empty = ''
+ else:
+ if self.supports_expressions or cast(KotlinPropertyType, self.property_type).is_enum_of_expressions:
+ expression_or_empty = EXPRESSION_TYPE_NAME
+ expression_suffix_or_empty = 'WithExpression'
+ else:
+ expression_or_empty = ''
+ expression_suffix_or_empty = ''
+ list_or_empty = ''
+ optionality = 'Optional' if self.parsed_value_is_optional else ''
+ if reuse_logger_instance:
+ logger_arg = 'logger'
+ else:
+ logger_arg = 'env.logger'
+ if mode.is_template:
+ method_name = f'read{optionality}{list_or_empty}Field{expression_suffix_or_empty}'
+ key_value = f'"{self.dict_field}"'
+ template_args = f'topLevel, parent?.{self.declaration_name}'
+ else:
+ method_name = f'read{optionality}{expression_or_empty}{list_or_empty}'
+ key_value = 'key' if self.mode.is_template else f'"{self.dict_field}"'
+ template_args = ''
+ creator = self.creator_declaration(mode=mode) or ''
+ kotlin_type = cast(KotlinPropertyType, self.property_type)
+ transform = kotlin_type.deserialization_transform(
+ string_enum_prefixed=self.mode.is_template
+ )
+ arg_list = ['json', key_value, template_args, creator, transform,
+ self.validator_arg(with_template_validators=mode.is_template), logger_arg, 'env']
+
+ if self.supports_expressions and not mode.is_template and self.default_value_definition is not None:
+ arg_list.append(self.default_value_var_name)
+
+ if self.supports_expressions or kotlin_type.is_array_of_expressions or kotlin_type.is_enum_of_expressions:
+ arg_list.append(kotlin_type.type_helper_reference(self))
+
+ args = ', '.join(filter(lambda s: s, arg_list))
+ receiver = 'JsonTemplateParser.' if mode.is_template else 'JsonParser.'
+ return f'{receiver}{method_name}({args})'
+
+ def creator_declaration(self, mode: GenerationMode) -> Optional[str]:
+ creator_type = None
+ if isinstance(self.property_type, Object) and not isinstance(self.property_type.object, StringEnumeration):
+ creator_type = self.property_type
+ elif isinstance(self.property_type, Array):
+ item = self.property_type.property_type
+ if isinstance(item, Object) and not isinstance(item.object, StringEnumeration):
+ creator_type = item
+
+ if creator_type is None:
+ return None
+
+ type_decl = cast(KotlinPropertyType, creator_type).declaration_by_prefixed(
+ prefixed=self.mode.is_template and self.mode != mode,
+ mode=mode
+ )
+
+ return f'{type_decl}.{ENTITY_STATIC_CREATOR}'
+
+ def validator_arg(self, with_template_validators: bool) -> str:
+ validator_definition = self.validator_definition
+ if validator_definition is None:
+ return ''
+ return self.validator_instance_name(with_template_validators)
+
+ def validator_instance_name(self, with_templates: bool) -> str:
+ name = utils.constant_upper_case(self.name)
+ return f'{name}_TEMPLATE_VALIDATOR' if with_templates else f'{name}_VALIDATOR'
+
+ @property
+ def validator_definition(self) -> Optional[str]:
+ if isinstance(self.property_type, Array) and self.property_type.min_items > 0:
+ return f'{{ it: List<*> -> it.size >= {self.property_type.min_items} }}'
+ elif isinstance(self.property_type, String) and (
+ self.property_type.min_length > 0 or
+ self.optional or self.property_type.regex is not None):
+ expressions = []
+ length_field = 'rawLength' if self.property_type.formatted else 'length'
+ min_length = self.property_type.min_length
+ actual_min_length = 1 if min_length == 0 and self.optional else min_length
+ if actual_min_length > 0:
+ expressions.append(f'it.{length_field} >= {actual_min_length}')
+ regex = self.property_type.regex
+ if regex is not None:
+ escaped_pattern = regex.pattern.replace('\\', '\\\\')
+ expressions.append(f'it: String -> it.doesMatch("{escaped_pattern}")')
+ if not expressions:
+ return ''
+ return f'{{ it: String -> {" && ".join(expressions)} }}'
+ elif isinstance(self.property_type, Url) and self.property_type.schemes is not None:
+ scheme_list = ', '.join(map(lambda s: f'"{s}"', self.property_type.schemes))
+ return f'{{ it.hasScheme(listOf({scheme_list})) }}'
+ elif isinstance(self.property_type, Int):
+ return _number_validator_decl('Int', self.property_type.constraint)
+ elif isinstance(self.property_type, Double):
+ return _number_validator_decl('Double', self.property_type.constraint)
+ else:
+ return None
+
+ def default_value_coalescing(self, mode: GenerationMode) -> str:
+ if not mode.is_template and self.default_value_definition is not None:
+ return f' ?: {self.default_value_var_name}'
+ return ''
+
+ def make_template_deserialization(self, dict_field: str, value_override: Optional[str]) -> str:
+ optionality = 'Optional' if self.optional else ''
+ plain_or_empty = 'Template' if self.property_type.can_be_templated else ''
+ if value_override is None:
+ value_override = ''
+ else:
+ value_override = f', valueOverride = {value_override}'
+ reader = self.reader_declaration_name
+ if isinstance(self.property_type, Array):
+ kotlin_prop = cast(KotlinPropertyType, self.property_type)
+ if kotlin_prop.is_array_of_expressions:
+ list_or_empty = 'ExpressionList'
+ validator = ''
+ expression_or_empty = ''
+ else:
+ list_or_empty = 'List'
+ validator = f', {self.validator_arg(with_template_validators=False)}'
+ expression_or_empty = ''
+ if self.supports_expressions and not kotlin_prop.is_enum_of_expressions:
+ expression_or_empty = EXPRESSION_TYPE_NAME
+ else:
+ list_or_empty = ''
+ validator = ''
+ expression_or_empty = ''
+ method_name = f'.resolve{optionality}{plain_or_empty}{expression_or_empty}{list_or_empty}'
+ method_args = f'env = env, key = "{dict_field}", data = data{validator}{value_override}, reader = {reader}'
+ def_val = self.default_value_coalescing(mode=GenerationMode.NORMAL_WITHOUT_TEMPLATES)
+ return f'{self.declaration_name} = {self.declaration_name}{method_name}({method_args}){def_val}'
+
+ @property
+ def reader_declaration_name(self) -> str:
+ return f'{utils.constant_upper_case(self.name)}_READER'
+
+ @property
+ def serialization_declaration(self) -> Text:
+ is_field = self.mode.is_template and not isinstance(self.property_type, StaticString)
+ suffix = 'Field' if is_field else ''
+ value_arg = 'field' if is_field else 'value'
+ if self.use_expression_type:
+ if is_field:
+ expression_prefix_or_empty = ''
+ expression_suffix_or_empty = 'WithExpression'
+ else:
+ expression_prefix_or_empty = EXPRESSION_TYPE_NAME
+ expression_suffix_or_empty = ''
+ else:
+ expression_prefix_or_empty = ''
+ if cast(KotlinPropertyType, self.property_type).is_array_of_expressions:
+ expression_prefix_or_empty = EXPRESSION_LIST_TYPE_NAME
+ expression_suffix_or_empty = ''
+ serialization_transform = cast(KotlinPropertyType, self.property_type).serialization_transform(
+ string_enum_prefixed=self.mode.is_template
+ )
+ args = f'key = "{self.dict_field}", {value_arg} = {self.declaration_name}{serialization_transform}'
+ return Text(f'json.write{expression_prefix_or_empty}{suffix}{expression_suffix_or_empty}({args})')
+
+ @property
+ def type_helper_declaration(self) -> str:
+ type_decl = cast(KotlinPropertyType, self.property_type).prefixed_declaration(
+ mode=GenerationMode.NORMAL_WITHOUT_TEMPLATES)
+ definition = f'TypeHelper.from(default = {type_decl}.values().first()) {{ it is {type_decl} }}'
+ return f'private val {cast(KotlinPropertyType, self.property_type).type_helper_reference(self)} = {definition}'
+
+ def static_validator_expression(self, with_template_validators: bool) -> Text:
+ result = Text()
+ definition = self.validator_definition or ''
+ if not definition:
+ return result
+
+ if isinstance(self.property_type, Array) and self.property_type.min_items > 0:
+ item_type = cast(KotlinPropertyType, self.property_type.property_type)
+ list_type = item_type.prefixed_declaration(
+ GenerationMode.NORMAL_WITHOUT_TEMPLATES)
+ validator_instance_name = self.validator_instance_name(with_templates=False)
+ result += f'private val {validator_instance_name} = ListValidator<{list_type}> {definition}'
+
+ if with_template_validators:
+ templated_list_type = item_type.prefixed_declaration(GenerationMode.TEMPLATE)
+ validator_instance_name = self.validator_instance_name(with_templates=True)
+ result += f'private val {validator_instance_name} = ListValidator<{templated_list_type}> {definition}'
+ else:
+ prop_type = cast(KotlinPropertyType, self.property_type)
+ validator_type = prop_type.prefixed_declaration(GenerationMode.NORMAL_WITH_TEMPLATES)
+ validator_instance_name_with = self.validator_instance_name(with_templates=True)
+ validator_instance_name_without = self.validator_instance_name(with_templates=False)
+ result += f'private val {validator_instance_name_with} = ValueValidator<{validator_type}> {definition}'
+ result += f'private val {validator_instance_name_without} = ValueValidator<{validator_type}> {definition}'
+
+ return result
+
+ @property
+ def static_reader_deserialization_expression(self) -> str:
+ lambda_val = 'key, json, env -> ' + self.deserialization_expression(mode=GenerationMode.NORMAL_WITH_TEMPLATES,
+ reuse_logger_instance=False)
+ reader_type = cast(KotlinPropertyType, self.property_type).declaration_by_prefixed(
+ prefixed=True,
+ mode=GenerationMode.NORMAL_WITH_TEMPLATES
+ )
+ if self.use_expression_type:
+ reader_type = f'{EXPRESSION_TYPE_NAME}<{reader_type}>'
+
+ optional = '?' if self.parsed_value_is_optional else ''
+ return f'val {self.reader_declaration_name}: Reader<{reader_type}{optional}> = {{ {lambda_val} }}'
+
+
+class KotlinPropertyType(PropertyType):
+ @property
+ def is_array_of_expressions(self) -> bool:
+ if isinstance(self, Array):
+ return self.property_type.supports_expressions and \
+ not cast(KotlinPropertyType, self.property_type).is_enum_of_expressions
+ return False
+
+ @property
+ def is_enum_of_expressions(self) -> bool:
+ return isinstance(self, Object) and isinstance(self.object, StringEnumeration)
+
+ @property
+ def empty_dict_deserialization(self) -> Optional[str]:
+ if isinstance(self, Object) and isinstance(self.object, Entity) and \
+ self.object.all_properties_are_optional_except_default_values:
+ return f'{self.object.resolved_prefixed_declaration}()'
+ return None
+
+ def declaration_by_default_value(self, default_value: str, string_enum_prefixed: bool) -> Optional[str]:
+ def wrap(value: str) -> str:
+ if self.supports_expressions and not self.is_array_of_expressions and not self.is_enum_of_expressions:
+ return f'{EXPRESSION_TYPE_NAME}.constant({value})'
+ return value
+
+ if isinstance(self, (Int, Bool, BoolInt)):
+ return wrap(default_value)
+ elif isinstance(self, Double):
+ return wrap(str(default_value) if '.' in str(default_value) else f'{default_value}.0')
+ elif isinstance(self, Color):
+ return wrap(f'{default_value.replace("#", "0x")}.toInt()')
+ elif isinstance(self, String):
+ escaping = default_value.replace("\"", "\\\"")
+ return wrap(f'"{escaping}"')
+ elif isinstance(self, Url):
+ return wrap(f'Uri.parse("{default_value}")')
+ elif isinstance(self, Array):
+ without_whitespaces = default_value.replace(' ', '').replace('\n', '')
+ if not without_whitespaces.startswith('[') or not without_whitespaces.endswith(']'):
+ return None
+ if without_whitespaces == '[]':
+ values = []
+ else:
+ values = without_whitespaces[1:-1].split(',')
+ item_type = cast(KotlinPropertyType, self.property_type)
+ declarations = list(filter(None, map(
+ lambda value: item_type.declaration_by_default_value(value, string_enum_prefixed),
+ values)))
+ if len(values) != len(declarations):
+ return None
+ joined = ', '.join(declarations)
+ return f'listOf({joined})'
+ elif isinstance(self, Object):
+ if self.object is None:
+ return None
+
+ if isinstance(self.object, StringEnumeration):
+ if string_enum_prefixed:
+ name = self.object.resolved_prefixed_declaration
+ else:
+ name = utils.capitalize_camel_case(self.object.name)
+ def_val = utils.fixing_first_digit(utils.constant_upper_case(default_value))
+ return wrap(f'Expression.constant({name}.{def_val})')
+
+ default_value_dict = utils.json_dict(default_value)
+ if isinstance(self.object, EntityEnumeration):
+ type_val = default_value_dict.get('type')
+ enum_case = None
+ for case in self.object.entities:
+ ent = case[1]
+ if isinstance(ent, Entity) and ent.static_type == type_val:
+ enum_case = case
+ if enum_case is None:
+ raise ValueError(type_val)
+ obj = cast(KotlinPropertyType, Object(name='', object=enum_case[1], format=ObjectFormat.DEFAULT))
+ case_constructor: Optional[str] = obj.declaration_by_default_value(default_value, True)
+ if case_constructor is None:
+ return None
+ obj_name = self.object.resolved_prefixed_declaration
+ self.object.__class__ = KotlinEntityEnumeration
+ case_name = cast(KotlinEntityEnumeration, self.object).case_naming.format_case_name(
+ utils.capitalize_camel_case(enum_case[0])
+ )
+ return wrap(f'{obj_name}.{case_name}({case_constructor})')
+
+ entity: KotlinEntity = cast(KotlinEntity, self.object)
+ entity.__class__ = KotlinEntity
+ args = []
+ for prop in entity.instance_properties_kotlin:
+ str_type = default_value_dict.get(prop.dict_field)
+ if str_type is None:
+ continue
+ declaration = cast(KotlinPropertyType, prop.property_type).declaration_by_default_value(str_type, True)
+ args.append(f'{prop.declaration_name} = {declaration}')
+ args = ', '.join(args)
+ return wrap(f'{entity.resolved_prefixed_declaration}({args})')
+ else:
+ return None
+
+ def declaration(self, mode: GenerationMode) -> str:
+ return self.declaration_by_prefixed(prefixed=False, mode=mode)
+
+ def prefixed_declaration(self, mode: GenerationMode) -> str:
+ return self.declaration_by_prefixed(prefixed=True, mode=mode)
+
+ def declaration_by_prefixed(self, prefixed: bool, mode: GenerationMode) -> str:
+ if isinstance(self, (Int, Color)):
+ return 'Int'
+ elif isinstance(self, Double):
+ return 'Double'
+ elif isinstance(self, (Bool, BoolInt)):
+ return 'Boolean'
+ elif isinstance(self, String):
+ return 'CharSequence' if self.formatted else 'String'
+ elif isinstance(self, Dictionary):
+ return 'JSONObject'
+ elif isinstance(self, StaticString):
+ return 'String'
+ elif isinstance(self, Url):
+ return 'Uri'
+ elif isinstance(self, Array):
+ item_type = cast(KotlinPropertyType, self.property_type)
+ item_decl = item_type.declaration_by_prefixed(prefixed, mode)
+ if item_type.supports_expressions and not item_type.is_enum_of_expressions:
+ return f'{EXPRESSION_LIST_TYPE_NAME}<{item_decl}>'
+ return f'List<{item_decl}>'
+ elif isinstance(self, Object):
+ if self.name.startswith('$predefined_'):
+ return self.name.replace('$predefined_', '')
+ obj_name = None
+ if mode.is_template:
+ if isinstance(self.object, StringEnumeration):
+ string_enum: StringEnumeration = self.object
+ return string_enum.resolved_prefixed_declaration
+ else:
+ obj_name = utils.capitalize_camel_case(self.object.resolved_name + mode.name_suffix)
+ elif self.object is not None:
+ obj_name = utils.capitalize_camel_case(self.object.resolved_name)
+ prefix = ''
+ if prefixed:
+ prefix = self.object.declaration_prefix if mode.is_template else self.object.resolved_declaration_prefix
+ return f'{prefix}{obj_name or utils.capitalize_camel_case(self.name)}'
+
+ def deserialization_transform(self, string_enum_prefixed: bool) -> str:
+ if isinstance(self, Url):
+ return 'STRING_TO_URI'
+ elif isinstance(self, Color):
+ return 'STRING_TO_COLOR_INT'
+ elif isinstance(self, (Bool, BoolInt)):
+ return 'NUMBER_TO_BOOLEAN'
+ elif isinstance(self, Object) and isinstance(self.object, StringEnumeration):
+ if string_enum_prefixed:
+ typename = self.object.resolved_prefixed_declaration
+ else:
+ typename = utils.capitalize_camel_case(self.object.name)
+ return f'{typename}.Converter.FROM_STRING'
+ elif isinstance(self, Double):
+ return 'NUMBER_TO_DOUBLE'
+ elif isinstance(self, Int):
+ return 'NUMBER_TO_INT'
+ elif isinstance(self, String):
+ return '::HtmlString' if self.formatted else ''
+ elif isinstance(self, Array):
+ return cast(KotlinPropertyType, self.property_type).deserialization_transform(string_enum_prefixed)
+ else:
+ return ''
+
+ def type_helper_reference(self, p: KotlinProperty) -> str:
+ prefix = 'TYPE_HELPER_'
+ if isinstance(self, (Bool, BoolInt)):
+ return f'{prefix}BOOLEAN'
+ elif isinstance(self, String):
+ return f'{prefix}STRING'
+ elif isinstance(self, Url):
+ return f'{prefix}URI'
+ elif isinstance(self, Color):
+ return f'{prefix}COLOR'
+ elif isinstance(self, Int):
+ return f'{prefix}INT'
+ elif isinstance(self, Double):
+ return f'{prefix}DOUBLE'
+ elif isinstance(self, Array):
+ if self.property_type.supports_expressions:
+ return cast(KotlinPropertyType, self.property_type).type_helper_reference(p)
+ return ''
+ elif isinstance(self, Object):
+ return f'{prefix}{utils.constant_upper_case(p.name)}'
+ else:
+ return ''
+
+ def serialization_transform(self, string_enum_prefixed: bool) -> str:
+ prefix = ', converter = '
+ if isinstance(self, (Bool, BoolInt)):
+ return f'{prefix}BOOLEAN_TO_INT'
+ elif isinstance(self, String):
+ return f'{prefix}SPANNED_TO_HTML' if self.formatted else ''
+ elif isinstance(self, Url):
+ return f'{prefix}URI_TO_STRING'
+ elif isinstance(self, Object) and isinstance(self.object, StringEnumeration):
+ if string_enum_prefixed:
+ typename = self.object.resolved_prefixed_declaration
+ else:
+ typename = utils.capitalize_camel_case(self.object.name)
+ return f'{prefix}{{ v: {typename} -> {typename}.toString(v) }}'
+ elif isinstance(self, Color):
+ return f'{prefix}COLOR_INT_TO_STRING'
+ elif isinstance(self, Array):
+ return cast(KotlinPropertyType, self.property_type).serialization_transform(string_enum_prefixed)
+ else:
+ return ''
+
+
+class CaseNaming:
+ def format_case_name(self, name: str) -> str:
+ if isinstance(self, Suffix):
+ return name + 'Case'
+ elif isinstance(self, RemoveCommonPart):
+ return name[len(self.prefix):len(name) - len(self.suffix)]
+
+
+class Suffix(CaseNaming):
+ pass
+
+
+@dataclasses.dataclass
+class RemoveCommonPart(CaseNaming):
+ prefix: str
+ suffix: str
+
+
+class KotlinEntityEnumeration(EntityEnumeration):
+ @property
+ def case_naming(self) -> CaseNaming:
+ components = utils.name_components(self.name)
+ entity_name_components = list(map(utils.name_components, self.entity_names))
+
+ def find_common_prefix(a: List[str], b: List[str]) -> List[str]:
+ common = []
+ for a_el, b_el in zip(a, b):
+ if a_el == b_el:
+ common.append(a_el)
+ else:
+ break
+ return common
+
+ names = [components] + entity_name_components
+ common_prefix = reduce(find_common_prefix, names)
+ for element in names:
+ element.reverse()
+ common_suffix = reduce(find_common_prefix, names)
+ if common_prefix or common_suffix:
+ return RemoveCommonPart(prefix=utils.lower_camel_case('_'.join(common_prefix)),
+ suffix=utils.lower_camel_case('_'.join(common_suffix)))
+ else:
+ return Suffix()
diff --git a/api_generator/api_generator/generators/swift/__init__.py b/api_generator/api_generator/generators/swift/__init__.py
new file mode 100644
index 000000000..690222c0d
--- /dev/null
+++ b/api_generator/api_generator/generators/swift/__init__.py
@@ -0,0 +1,3 @@
+from .generator import SwiftGenerator
+
+__all__ = [SwiftGenerator]
diff --git a/api_generator/api_generator/generators/swift/generator.py b/api_generator/api_generator/generators/swift/generator.py
new file mode 100644
index 000000000..ac0f644cd
--- /dev/null
+++ b/api_generator/api_generator/generators/swift/generator.py
@@ -0,0 +1,332 @@
+from typing import cast, List, Optional
+
+from .swift_entities import (
+ SwiftEntity,
+ SwiftPropertyType,
+ SwiftProperty,
+ SwiftEntityEnumeration,
+ SwiftAccessLevel,
+ swift_template_deserializable_args_decl,
+ swift_template_deserializable_args,
+ _swift_default_value_declaration_comment
+)
+from ..base import Generator, declaration_comment
+from ... import utils
+from ...schema.modeling.entities import (
+ StringEnumeration,
+ EntityEnumeration,
+ Entity,
+ Declarable,
+ Property,
+ String
+)
+from ...schema.modeling.text import Text, EMPTY
+from ...config import Config, GenerationMode, GeneratedLanguage
+from . import utils as swift_utils
+
+
+def _make_equatable_func_body(properties: List[SwiftProperty]) -> Text:
+ if not properties:
+ return Text(' return true')
+ equatable_func_body = Text()
+ max_guard_block_length = 3 # Splitting guard conditions by chunks of maxGuardBlockLength to improve build time
+
+ def chunks(lst, n):
+ for i in range(0, len(lst), n):
+ yield lst[i:i + n]
+
+ def make_equatable_guard_block(field_names: List[str]) -> Text:
+ result = Text()
+ if not field_names:
+ return result
+ result += 'guard'
+ comparisons = list(map(lambda field_name: f' lhs.{field_name} == rhs.{field_name}', field_names))
+ result += ',\n'.join(comparisons)
+ result += 'else {'
+ result += ' return false'
+ result += '}'
+ return result
+
+ for guard_group in chunks(list(map(lambda p: p.declaration_name, properties)), max_guard_block_length):
+ equatable_func_body += make_equatable_guard_block(guard_group).indented(indent_width=4)
+ equatable_func_body += ' return true'
+ return equatable_func_body
+
+
+def _string_id_property(obj: Declarable) -> Optional[Property]:
+ if not isinstance(obj, Entity):
+ return None
+ p = next((p for p in obj.properties if p.name == 'id'), None)
+ if p is None:
+ return None
+ if isinstance(p.property_type, String):
+ return p
+ return None
+
+
+class SwiftGenerator(Generator):
+ def __init__(self, config: Config):
+ super(SwiftGenerator, self).__init__(config)
+ self._access_level = SwiftAccessLevel.PUBLIC
+
+ def _filename(self, name: str) -> str:
+ return f'{utils.capitalize_camel_case(name)}.swift'
+
+ def _main_declaration(self, obj: Declarable) -> Text:
+ main_decl = super()._main_declaration(obj)
+ extension = '\n\n'.join(self.__extensions_declaration(obj))
+ if extension:
+ return main_decl + EMPTY + extension
+ else:
+ return main_decl
+
+ def __extensions_declaration(self, obj: Declarable) -> List[str]:
+ if isinstance(obj, Entity):
+ return self.__entity_extensions_declaration(cast(SwiftEntity, obj))
+ elif isinstance(obj, EntityEnumeration):
+ return self.__entity_enumeration_extensions_declaration(obj)
+ else:
+ return []
+
+ def __entity_extensions_declaration(self, entity: SwiftEntity) -> List[str]:
+ self_extensions = []
+ if not entity.generate_as_protocol and not entity.generation_mode.is_template:
+ equatable_extension = Text('#if DEBUG')
+ props = entity.instance_properties_swift
+ equatable_properties = list(filter(lambda p: cast(SwiftPropertyType, p.property_type).is_equatable,
+ props))
+ if len(equatable_properties) != len(props):
+ equatable_extension += '// WARNING: this == is incomplete because of [String: Any] in class fields'
+ pref_decl = entity.prefixed_declaration
+ equatable_extension += f'extension {pref_decl}: Equatable {{'
+ access_modifier = self._access_level.value
+ equatable_extension += f' {access_modifier}static func ==(lhs: {pref_decl}, rhs: {pref_decl}) -> Bool {{'
+ equatable_extension += _make_equatable_func_body(equatable_properties)
+ equatable_extension += ' }'
+ equatable_extension += '}'
+ equatable_extension += '#endif'
+ self_extensions.append(str(equatable_extension))
+
+ extension_prefix = f'extension {entity.prefixed_declaration}'
+ serialization = Text(f'{extension_prefix}: Serializable {{')
+ serialization += ' public func toDictionary() -> [String: ValidSerializationValue] {'
+ serialization += ' var result: [String: ValidSerializationValue] = [:]'
+ if entity.has_static_type:
+ serialization += ' result["type"] = Self.type'
+ for prop in props:
+ serialization += Text(prop.serialization_declaration).indented(indent_width=4)
+ serialization += ' return result'
+ serialization += ' }'
+ serialization += '}'
+ self_extensions.append(str(serialization))
+
+ inner_types_extensions = []
+ for inner_type in entity.inner_types:
+ inner_types_extensions += self.__extensions_declaration(inner_type)
+
+ return self_extensions + sorted(inner_types_extensions)
+
+ def __entity_enumeration_extensions_declaration(self, entity_enumeration: EntityEnumeration) -> List[str]:
+ protocol = swift_utils.implemented_swift_protocol(entity_enumeration.mode) or ''
+ access_modifier = self._access_level.value
+ args_decl = swift_template_deserializable_args_decl(entity_enumeration.mode)
+ deserializable_extension = Text(f'extension {entity_enumeration.prefixed_declaration}: {protocol} {{')
+ deserializable_extension += f' {access_modifier}init(dictionary: [String: Any]{args_decl}) throws {{'
+ body = Text()
+ if entity_enumeration.mode.is_template:
+ body += 'let receivedType = try dictionary.getField("type") as String'
+ body += 'let blockType = templateToType[receivedType] ?? receivedType'
+ else:
+ body += 'let blockType = try dictionary.getField("type") as String'
+ body += 'switch blockType {'
+ entity_names = entity_enumeration.entity_names
+ for entity, name in zip(entity_enumeration.entities, entity_names):
+ obj_t = entity[1].prefixed_declaration if entity[1] is not None else utils.capitalize_camel_case(entity[0])
+ low_name = utils.lower_camel_case(name)
+ args = swift_template_deserializable_args(entity_enumeration.mode)
+ body += f'case {obj_t}.type:'
+ body += f' self = .{low_name}(try {obj_t}(dictionary: dictionary{args}))'
+ body += 'default:'
+ args = f'field: "{entity_enumeration.name}", representation: dictionary'
+ body += f' throw DeserializationError.invalidFieldRepresentation({args})'
+ body += '}'
+ deserializable_extension += body.indented(level=2)
+ deserializable_extension += ' }'
+ deserializable_extension += '}'
+ deserializable_extension = str(deserializable_extension)
+
+ equatable_extension = Text('#if DEBUG')
+ pref = entity_enumeration.prefixed_declaration
+ equatable_extension += f'extension {pref}: Equatable {{'
+ equatable_extension += f' {access_modifier}static func ==(lhs: {pref}, rhs: {pref}) -> Bool {{'
+ equatable_extension += ' switch (lhs, rhs) {'
+ for name in entity_names:
+ low_name = utils.lower_camel_case(name)
+ equatable_extension += f' case let (.{low_name}(l), .{low_name}(r)):'
+ equatable_extension += ' return l == r'
+ if len(entity_names) > 1:
+ equatable_extension += ' default:'
+ equatable_extension += ' return false'
+ equatable_extension += ' }'
+ equatable_extension += ' }'
+ equatable_extension += '}'
+ equatable_extension += '#endif'
+ equatable_extension = str(equatable_extension)
+
+ if entity_enumeration.mode is GenerationMode.NORMAL_WITHOUT_TEMPLATES:
+ result = [deserializable_extension, equatable_extension]
+ elif entity_enumeration.mode.is_template:
+ result = [deserializable_extension]
+ else:
+ result = [equatable_extension]
+
+ if not entity_enumeration.mode.is_template:
+ serialization_extension = Text(f'extension {entity_enumeration.prefixed_declaration}: Serializable {{')
+ serialization_extension += f' {access_modifier}func toDictionary() -> [String: ValidSerializationValue] {{'
+ serialization_extension += ' return value.toDictionary()'
+ serialization_extension += ' }'
+ serialization_extension += '}'
+ result.append(str(serialization_extension))
+
+ return result
+
+ def _entity_declaration(self, entity: Entity) -> Text:
+ entity: SwiftEntity = cast(SwiftEntity, entity)
+ entity.__class__ = SwiftEntity
+ entity.update_bases()
+
+ if entity.generate_as_protocol:
+ return self.__declaration_as_protocol(entity)
+ result: Text = Text(self.__main_declaration_header(entity))
+
+ if entity.inner_types:
+ inner_type_declarations = []
+ for inner_type in entity.inner_types:
+ inner_type_declarations.append(str(super()._main_declaration(inner_type).indented()))
+ result += '\n\n'.join(sorted(inner_type_declarations))
+ result += EMPTY
+
+ properties_to_declare = entity.properties_to_declare_swift
+ if properties_to_declare:
+ for prop in properties_to_declare:
+ result += prop.declaration(self._access_level).indented()
+ result += EMPTY
+
+ filtered_props = list(filter(
+ lambda p: p.supports_expressions and not p.mode.is_template,
+ properties_to_declare))
+ for prop in filtered_props:
+ result += prop.expression_resolving_method(self._access_level).indented()
+
+ for prop in entity.instance_properties_swift:
+ validator_decl = prop.validator_declaration
+ if validator_decl is not None:
+ result += validator_decl.indented()
+ result += EMPTY
+
+ if entity.generation_mode is GenerationMode.NORMAL_WITHOUT_TEMPLATES or \
+ entity.generation_mode.is_template:
+ result += entity.deserializing_constructor_declaration.indented()
+ result += EMPTY
+
+ result += entity.plain_constructor_declaration.indented()
+
+ if entity.generation_mode.is_template:
+ result += EMPTY
+ result += entity.resolve_value_only_by_links_body.indented()
+ result += EMPTY
+ result += entity.resolve_value_declaration.indented()
+ result += EMPTY
+ result += entity.resolve_template_declaration.indented()
+
+ result += '}'
+
+ return result
+
+ def __declaration_as_protocol(self, entity: SwiftEntity) -> Text:
+ super_protocol = ''
+ if entity.swift_super_protocol is not None:
+ super_protocol = f': {entity.swift_super_protocol}'
+ access_modifier = self._access_level.value
+ result = Text(f'{access_modifier}protocol {utils.capitalize_camel_case(entity.name)}{super_protocol} {{')
+ props = entity.instance_properties_swift
+ for prop in props:
+ name = prop.declaration_name
+ type_decl = prop.type_declaration
+ comment = declaration_comment(prop, _swift_default_value_declaration_comment)
+ result += Text(f'var {name}: {type_decl} {{ get }}{comment}').indented()
+ for prop in filter(lambda p: p.supports_expressions and not p.mode.is_template, props):
+ result += Text(prop.expression_resolving_method_declaration(SwiftAccessLevel.INTERNAL)).indented()
+ result += '}'
+ return result
+
+ def __main_declaration_header(self, entity: SwiftEntity) -> str:
+ protocols = list(filter(None, [entity.protocol_plus_super_entities,
+ swift_utils.implemented_swift_protocol(entity.generation_mode)]))
+ conformance = '' if not protocols else f': {", ".join(protocols)}'
+ access_modifier = self._access_level.value
+ return f'{access_modifier}final class {utils.capitalize_camel_case(entity.name)}{conformance} {{'
+
+ def _entity_enumeration_declaration(self, entity_enumeration: EntityEnumeration) -> Text:
+ entity_enumeration = cast(SwiftEntityEnumeration, entity_enumeration)
+ entity_enumeration.__class__ = SwiftEntityEnumeration
+ access_modifier = self._access_level.value
+ name = utils.capitalize_camel_case(entity_enumeration.name)
+ protocol = ''
+ if entity_enumeration.mode.is_template:
+ protocol = ': TemplateValue'
+ header = f'{access_modifier}enum {name}{protocol} {{'
+ result = Text(header)
+
+ for name in entity_enumeration.entity_names:
+ result += Text(f'case {utils.lower_camel_case(name)}({utils.capitalize_camel_case(name)})').indented()
+ result += EMPTY
+ if entity_enumeration.mode.is_template:
+ value_type = 'Any'
+ else:
+ value_type = entity_enumeration.common_interface(GeneratedLanguage.SWIFT)
+ if value_type is None:
+ value_type = 'Any'
+ result += f' {access_modifier}var value: {value_type} {{'
+ result += ' switch self {'
+ for name in entity_enumeration.entity_names:
+ result += f' case let .{utils.lower_camel_case(name)}(value):'
+ result += ' return value'
+ result += ' }'
+ result += ' }'
+
+ if entity_enumeration.mode.is_template:
+ result += EMPTY
+ result += entity_enumeration.resolve_parent_implementation(self._access_level).indented()
+ result += EMPTY
+ result += entity_enumeration.resolve_value_implementation(self._access_level).indented()
+ result += EMPTY
+ result += entity_enumeration.resolve_unknown_value_implementation.indented()
+ else:
+ id_properties = list(filter(None, map(lambda x: _string_id_property(x[1]), entity_enumeration.entities)))
+ if len(id_properties) == len(entity_enumeration.entities):
+ id_optionality = '?' if any(p.optional for p in id_properties) else ''
+ result += EMPTY
+ result += f' {access_modifier}var id: String{id_optionality} {{'
+ result += ' switch self {'
+ for name in entity_enumeration.entity_names:
+ result += f' case let .{utils.lower_camel_case(name)}(value):'
+ result += ' return value.id'
+ result += ' }'
+ result += ' }'
+
+ result += '}'
+ return result
+
+ def _string_enumeration_declaration(self, string_enumeration: StringEnumeration) -> Text:
+ access_modifier = self._access_level.value
+ formatted_name = utils.capitalize_camel_case(string_enumeration.name)
+ if string_enumeration.parent is not None and string_enumeration.parent.generation_mode.is_template:
+ prefix = string_enumeration.resolved_declaration_prefix
+ return Text(f'{access_modifier}typealias {formatted_name} = {prefix}{formatted_name}')
+ result = Text(f'{access_modifier}enum {formatted_name}: String, CaseIterable {{')
+ for case in string_enumeration.cases:
+ case_name = swift_utils.fixing_keywords(utils.fixing_first_digit(utils.lower_camel_case(case[0])))
+ result += f' case {case_name} = "{case[1]}"'
+ result += '}'
+ return result
diff --git a/api_generator/api_generator/generators/swift/swift_entities.py b/api_generator/api_generator/generators/swift/swift_entities.py
new file mode 100644
index 000000000..d0c3aa838
--- /dev/null
+++ b/api_generator/api_generator/generators/swift/swift_entities.py
@@ -0,0 +1,915 @@
+from __future__ import annotations
+from typing import List, Optional, cast, Tuple
+from enum import Enum
+from .utils import fixing_keywords
+from ..base import declaration_comment
+from ...schema.modeling.entities import (
+ Entity,
+ EntityEnumeration,
+ StringEnumeration,
+ Property,
+ PropertyType,
+ Int,
+ Bool,
+ BoolInt,
+ Double,
+ StaticString,
+ Object,
+ Array,
+ Url,
+ Color,
+ String,
+ Dictionary,
+ ObjectFormat
+)
+from ...config import GenerationMode
+from ... import utils
+from ...schema.modeling.text import Text, EMPTY
+
+PARENT_PROPERTY = Property(
+ name='parent',
+ description=None,
+ description_translations={},
+ dict_field='type',
+ property_type=String(
+ min_length=1,
+ formatted=False,
+ regex=None,
+ enable_optimization=False
+ ),
+ optional=True,
+ is_deprecated=False,
+ mode=GenerationMode.NORMAL_WITHOUT_TEMPLATES,
+ supports_expressions_flag=False,
+ default_value=None,
+ platforms=None
+)
+
+
+class SwiftAccessLevel(str, Enum):
+ PUBLIC = 'public '
+ INTERNAL = ''
+
+
+def swift_template_deserializable_args_decl(mode: GenerationMode):
+ return ', templateToType: TemplateToType' if mode.is_template else ''
+
+
+def swift_template_deserializable_args(mode: GenerationMode):
+ return ', templateToType: templateToType' if mode.is_template else ''
+
+
+def _swift_default_value_declaration_comment(p: Property) -> str:
+ if isinstance(p.property_type, Object) and not isinstance(p.property_type.object, StringEnumeration):
+ property_type = cast(SwiftPropertyType, Object(name='',
+ object=p.property_type.object,
+ format=ObjectFormat.DEFAULT))
+ comment_value = property_type.internal_declaration(p.default_value)
+ else:
+ comment_value = p.default_value
+ return comment_value
+
+
+class SwiftEntity(Entity):
+ def update_bases(self):
+ Int.__bases__ = (SwiftPropertyType, PropertyType,)
+ Bool.__bases__ = (SwiftPropertyType, PropertyType,)
+ BoolInt.__bases__ = (SwiftPropertyType, PropertyType,)
+ Double.__bases__ = (SwiftPropertyType, PropertyType,)
+ StaticString.__bases__ = (SwiftPropertyType, PropertyType,)
+ Object.__bases__ = (SwiftPropertyType, PropertyType,)
+ Array.__bases__ = (SwiftPropertyType, PropertyType,)
+ Url.__bases__ = (SwiftPropertyType, PropertyType,)
+ Color.__bases__ = (SwiftPropertyType, PropertyType,)
+ String.__bases__ = (SwiftPropertyType, PropertyType,)
+ Dictionary.__bases__ = (SwiftPropertyType, PropertyType,)
+ for prop in self.properties:
+ prop.__class__ = SwiftProperty
+
+ @property
+ def has_static_type(self) -> bool:
+ return any(p.name == 'type' and isinstance(p.property_type, StaticString) for p in self.properties)
+
+ @property
+ def has_parent_property(self) -> bool:
+ return self.has_static_type
+
+ @property
+ def properties_swift(self) -> List[SwiftProperty]:
+ self.update_bases()
+ if self.generation_mode.is_template and self.has_parent_property:
+ PARENT_PROPERTY.__class__ = SwiftProperty
+ return cast(List[SwiftProperty], [PARENT_PROPERTY] + self.properties)
+ else:
+ return cast(List[SwiftProperty], self.properties)
+
+ @property
+ def properties_to_declare_swift(self) -> List[SwiftProperty]:
+ return sorted(self.properties_swift, key=lambda p: not isinstance(p.property_type, StaticString))
+
+ @property
+ def instance_properties_swift(self) -> List[SwiftProperty]:
+ return list(filter(lambda p: not isinstance(p.property_type, StaticString), self.properties_swift))
+
+ @property
+ def deserializing_constructor_declaration(self) -> Text:
+ props = self.instance_properties_swift
+ args_decl = swift_template_deserializable_args_decl(self.generation_mode)
+ if not props:
+ return Text(f'public init(dictionary: [String: Any]{args_decl}) throws {{}}')
+ result = Text(f'public convenience init(dictionary: [String: Any]{args_decl}) throws {{')
+ tails = [','] * (len(props) - 1) + ['']
+ properties_deserialization = [Text('self.init(')]
+ for tail, prop in zip(tails, props):
+ line = f'{prop.declaration_name}: try dictionary.{prop.deserialization_expression(False)}{tail}'
+ properties_deserialization += [Text(indent_width=2, init_lines=line)]
+ properties_deserialization += [Text(')')]
+ if not any(not p.optional for p in props):
+ result += list(map(lambda t: t.indented(), properties_deserialization))
+ else:
+ result += ' do {'
+ result += list(map(lambda t: t.indented(indent_width=4), properties_deserialization))
+ result += ' } catch let DeserializationError.invalidFieldRepresentation(field: field, representation: representation) {'
+ result += f' throw DeserializationError.invalidFieldRepresentation(field: "{self.name}." + field, representation: representation)'
+ result += ' }'
+ result += '}'
+ return result
+
+ @property
+ def plain_constructor_declaration(self) -> Text:
+ props = self.instance_properties_swift
+ if not props:
+ return Text('init() {}')
+
+ result = Text('init(')
+ tails = [','] * (len(props) - 1) + ['']
+ for prop, tail in zip(props, tails):
+ if not self.generation_mode.is_template:
+ if prop.parsed_value_is_optional:
+ nullability = '' if prop.should_be_optional else '?'
+ optionality = ' = nil' if self._generate_swift_optional_args else ''
+ else:
+ nullability = ''
+ optionality = ''
+ else:
+ nullability = ''
+ optionality = ' = nil' if prop.mode.is_template else ''
+ result += f' {prop.declaration_name}: {prop.type_declaration}{nullability}{optionality}{tail}'
+ result += ') {'
+
+ for prop in props:
+ decl = f'self.{prop.declaration_name} = {fixing_keywords(prop.declaration_name)}'
+ if self.generation_mode.is_template:
+ res = decl
+ else:
+ res = prop.add_default_value_to(decl)
+ result += Text(indent_width=2, init_lines=res)
+ result += '}'
+
+ return result
+
+ @property
+ def resolve_value_only_by_links_body(self) -> Text:
+ result = Text()
+
+ template_props = list(filter(lambda p: p.mode.is_template, self.instance_properties_swift))
+ required_props = list(filter(lambda p: not p.parsed_value_is_optional, template_props))
+ return_type = self.resolved_declaration_prefix + utils.capitalize_camel_case(self.resolved_name)
+ fun_name = 'private static func resolveOnlyLinks'
+ name = utils.capitalize_camel_case(self.name)
+ result += f'{fun_name}(context: Context, parent: {name}?) -> DeserializationResult<{return_type}> {{'
+
+ if not template_props:
+ result += f' return .success({return_type}())'
+ result += '}'
+ return result
+
+ for prop in template_props:
+ var_name = prop.value_resolving_local_var_name
+ result += f' let {var_name} = parent?.{prop.declaration_name}?.{prop.resolve_value_expression} ?? .noValue'
+
+ result += f' {"let" if not required_props else "var"} errors = mergeErrors('
+ for index, prop in enumerate(template_props):
+ level = '.warning' if prop.parsed_value_is_optional else '.error'
+ separator = '' if index == (len(template_props) - 1) else ','
+ map_body = f'.right($0.asError(deserializing: "{prop.dict_field}", level: {level}))'
+ var_name = prop.value_resolving_local_var_name
+ result += Text(f' {var_name}.errorsOrWarnings?.map {{ {map_body} }}{separator}')
+ result += ' )'
+
+ for prop in required_props:
+ result += f' if case .noValue = {prop.value_resolving_local_var_name} {{'
+ field_error = f'FieldError(fieldName: "{prop.dict_field}", level: .error, error: .requiredFieldIsMissing)'
+ result += f' errors.append(.right({field_error}))'
+ result += ' }'
+
+ if required_props:
+ result += ' guard'
+ for prop in required_props[:-1]:
+ result += f' let {prop.declaration_name}NonNil = {prop.value_resolving_local_var_name}.value,'
+ last_prop = required_props[-1]
+ result += f' let {last_prop.declaration_name}NonNil = {last_prop.value_resolving_local_var_name}.value'
+ result += ' else {'
+ result += ' return .failure(NonEmptyArray(errors)!)'
+ result += ' }'
+
+ result += f' let result = {return_type}('
+ for index, prop in enumerate(template_props):
+ prop_name = prop.declaration_name
+ suffix = 'Value.value' if prop.parsed_value_is_optional else 'NonNil'
+ separator = '' if index == (len(template_props) - 1) else ','
+ result += Text(f' {prop_name}: {prop_name}{suffix}{separator}')
+ result += ' )'
+ partial_success = '.partialSuccess(result, warnings: NonEmptyArray(errors)!)'
+ result += f' return errors.isEmpty ? .success(result) : {partial_success}'
+ result += '}'
+
+ return result
+
+ @property
+ def resolve_value_declaration(self) -> Text:
+ result = Text()
+ return_type = self.resolved_declaration_prefix + utils.capitalize_camel_case(self.resolved_name)
+ name = utils.capitalize_camel_case(self.name)
+ fun_name = 'public static func resolveValue'
+ params = f'context: Context, parent: {name}?, useOnlyLinks: Bool'
+ result += f'{fun_name}({params}) -> DeserializationResult<{return_type}> {{'
+
+ template_props = list(filter(lambda p: p.mode.is_template, self.instance_properties_swift))
+ if not template_props:
+ result += f' return .success({return_type}())'
+ result += '}'
+ return result
+
+ result += ' if useOnlyLinks {'
+ result += ' return resolveOnlyLinks(context: context, parent: parent)'
+ result += ' }'
+
+ for prop in template_props:
+ if prop.property_type.can_be_templated:
+ initial_value = ' = .noValue'
+ else:
+ name = prop.declaration_name
+ if prop.internal_validator_declaration(GenerationMode.NORMAL_WITH_TEMPLATES) is None or \
+ prop.supports_expressions:
+ value = ''
+ else:
+ value = f'validatedBy: ResolvedValue.{prop.validator_var_name}'
+ initial_value = f' = parent?.{name}?.value({value}) ?? .noValue'
+ mode = SwiftProperty.SwiftMode(value=GenerationMode.NORMAL_WITH_TEMPLATES,
+ use_expressions=prop.supports_expressions)
+ val_type = cast(SwiftPropertyType, prop.property_type).prefixed_declaration(mode)
+ result += f' var {prop.value_resolving_local_var_name}: DeserializationResult<{val_type}>{initial_value}'
+
+ result += ' context.templateData.forEach { key, __dictValue in'
+ result += ' switch key {'
+ for prop in template_props:
+ result += f' case "{prop.dict_field}":'
+ local_var_name = prop.value_resolving_local_var_name
+ deserialize = cast(SwiftProperty, prop).deserialize_from_value_expression
+ result += f' {local_var_name} = {deserialize}.merged(with: {local_var_name})'
+ for prop in template_props:
+ result += f' case parent?.{prop.declaration_name}?.link:'
+ local_var_name = prop.value_resolving_local_var_name
+ deserialize = cast(SwiftProperty, prop).deserialize_from_value_expression
+ result += f' {local_var_name} = {local_var_name}.merged(with: {deserialize})'
+ result += ' default: break'
+ result += ' }'
+ result += ' }'
+
+ templateable_props = list(filter(lambda p: p.property_type.can_be_templated, template_props))
+ if templateable_props:
+ result += ' if let parent = parent {'
+ for prop in templateable_props:
+ local_var_name = prop.value_resolving_local_var_name
+ merged_with = f'parent.{prop.declaration_name}?.{prop.resolve_value_expression}'
+ result += f' {local_var_name} = {local_var_name}.merged(with: {merged_with})'
+ result += ' }'
+
+ required_props = list(filter(lambda p: not p.parsed_value_is_optional, template_props))
+ result += f' {"let" if not required_props else "var"} errors = mergeErrors('
+ for index, prop in enumerate(template_props):
+ level = '.warning' if prop.parsed_value_is_optional else '.error'
+ separator = '' if index == (len(template_props) - 1) else ','
+ map_body = f'Either.right($0.asError(deserializing: "{prop.dict_field}", level: {level}))'
+ var_name = prop.value_resolving_local_var_name
+ result += f' {var_name}.errorsOrWarnings?.map {{ {map_body} }}{separator}'
+ result += ' )'
+
+ for prop in required_props:
+ result += f' if case .noValue = {prop.value_resolving_local_var_name} {{'
+ field_error = f'FieldError(fieldName: "{prop.dict_field}", level: .error, error: .requiredFieldIsMissing)'
+ result += f' errors.append(.right({field_error}))'
+ result += ' }'
+
+ if required_props:
+ result += ' guard'
+ for prop in required_props[:-1]:
+ result += f' let {prop.declaration_name}NonNil = {prop.value_resolving_local_var_name}.value,'
+ last_prop = required_props[-1]
+ result += f' let {last_prop.declaration_name}NonNil = {last_prop.value_resolving_local_var_name}.value'
+ result += ' else {'
+ result += ' return .failure(NonEmptyArray(errors)!)'
+ result += ' }'
+
+ result += f' let result = {return_type}('
+ for index, prop in enumerate(template_props):
+ name = prop.declaration_name
+ suffix = 'Value.value' if prop.parsed_value_is_optional else 'NonNil'
+ separator = '' if index == (len(template_props) - 1) else ','
+ result += f' {name}: {name}{suffix}{separator}'
+ result += ' )'
+ partial_success = '.partialSuccess(result, warnings: NonEmptyArray(errors)!'
+ result += f' return errors.isEmpty ? .success(result) : {partial_success})'
+ result += '}'
+
+ return result
+
+ @property
+ def resolve_template_declaration(self) -> Text:
+ self_type = utils.capitalize_camel_case(self.name)
+
+ result = Text(f'private func mergedWithParent(templates: Templates) throws -> {self_type} {{')
+ props = cast(List[SwiftProperty], self.instance_properties)
+ if self.has_parent_property and props:
+ compare_with_self_type = ''
+ if self.static_type is not None:
+ compare_with_self_type = ', parent != Self.type'
+
+ result += f' guard let parent = parent{compare_with_self_type} else {{ return self }}'
+ result += f' guard let parentTemplate = templates[parent] as? {self_type} else {{'
+ result += ' throw DeserializationError.unknownType(type: parent)'
+ result += ' }'
+ result += ' let mergedParent = try parentTemplate.mergedWithParent(templates: templates)'
+ result += EMPTY
+ result += f' return {self_type}('
+ result += ' parent: nil,'
+ for prop in props[:-1]:
+ result += Text(f'{prop.parent_template_constructor_line},').indented(indent_width=4)
+ result += f' {props[-1].parent_template_constructor_line}'
+ result += ' )'
+ else:
+ result += ' return self'
+ result += '}'
+ result += EMPTY
+ result += f'public func resolveParent(templates: Templates) throws -> {self_type} {{'
+ if any(p.property_type.can_be_templated for p in props):
+ result += ' let merged = try mergedWithParent(templates: templates)'
+ result += EMPTY
+ result += f' return {self_type}('
+ if self.has_parent_property:
+ result += ' parent: nil,'
+ tails = [','] * (len(props) - 1) + ['']
+ for prop, tail in zip(props, tails):
+ name = f'merged.{prop.declaration_name}'
+ if prop.property_type.can_be_templated:
+ try_directive = '' if prop.parsed_value_is_optional else 'try '
+ resolve_parent_function = 'tryResolveParent' if prop.parsed_value_is_optional else 'resolveParent'
+ validator_decl = prop.validator_declaration
+ validator = '' if validator_decl is None else f', validator: Self.{prop.validator_var_name}'
+ expr = f'{try_directive}{name}?.{resolve_parent_function}(templates: templates{validator})'
+ else:
+ expr = name
+ result += Text(indent_width=4, init_lines=f'{prop.declaration_name}: {expr}{tail}')
+ result += ' )'
+ elif props:
+ result += ' return try mergedWithParent(templates: templates)'
+ else:
+ result += ' return self'
+
+ result += '}'
+ return result
+
+
+class SwiftProperty(Property):
+ class SwiftMode:
+ def __init__(self, value: GenerationMode, use_expressions: bool):
+ self.value: GenerationMode = value
+ self.use_expressions: bool = use_expressions
+
+ @property
+ def swift_mode(self) -> SwiftMode:
+ return SwiftProperty.SwiftMode(value=self.mode, use_expressions=self.supports_expressions)
+
+ @property
+ def declaration_name(self) -> str:
+ return utils.fixing_first_digit(utils.lower_camel_case(self.name))
+
+ @property
+ def value_resolving_local_var_name(self) -> str:
+ return self.declaration_name + 'Value'
+
+ @property
+ def resolve_value_expression(self) -> str:
+ optional_or_empty = 'Optional' if self.parsed_value_is_optional else ''
+ resolved_validator_arg = self.validator_arg(entity_name='ResolvedValue',
+ mode=GenerationMode.NORMAL_WITH_TEMPLATES)
+ use_only_links_arg = ', useOnlyLinks: true' if self.property_type.can_be_templated else ''
+ transformed = cast(SwiftPropertyType, self.property_type).transform_arg
+ expr = f'context: context{transformed}{resolved_validator_arg}{use_only_links_arg}'
+ return f'resolve{optional_or_empty}Value({expr})'
+
+ @property
+ def should_be_optional(self) -> bool:
+ prop_type = cast(SwiftPropertyType, self.property_type)
+ return self.optional and (self.default_value is None) and prop_type.empty_constructor is None
+
+ def declaration(self, access_level: SwiftAccessLevel) -> Text:
+ if isinstance(self.property_type, StaticString):
+ name = utils.lower_camel_case(self.name)
+ value = self.property_type.value
+ return Text(f'{access_level.value}static let {name}: String = "{value}"')
+ else:
+ name = fixing_keywords(self.declaration_name)
+ type_declaration = self.type_declaration
+ comment = declaration_comment(self, _swift_default_value_declaration_comment)
+ return Text(f'{access_level.value}let {name}: {type_declaration}{comment}')
+
+ def expression_resolving_method(self, access_level: SwiftAccessLevel) -> Text:
+ result = Text(f'{self.expression_resolving_method_declaration(access_level)} {{')
+ if isinstance(self.property_type, Array):
+ item_type = cast(SwiftPropertyType, self.property_type.property_type)
+ method, initializer = self.expression_resolving_method_parts(item_type)
+ map_text = f'resolver.{method}(expression: $0{initializer})'
+ result += Text(f'{self.declaration_name}.map {{ {map_text} }}.compactMap {{ $0 }}').indented()
+ else:
+ prop_type = cast(SwiftPropertyType, self.property_type)
+ method, initializer = self.expression_resolving_method_parts(prop_type)
+ default_value = self.expression_resolving_method_default_value(prop_type)
+ expression_str = f'expression: {self.declaration_name}{initializer}'
+ result += Text(f'resolver.{method}({expression_str}){default_value}').indented()
+ result += '}'
+ result += EMPTY
+ return result
+
+ def expression_resolving_method_parts(self, property_type: SwiftPropertyType) -> Tuple[str, str]:
+ resolve_string_based_value = 'resolveStringBasedValue'
+ initializer_prefix = ', initializer: '
+ if isinstance(property_type, Color):
+ method = resolve_string_based_value
+ initializer = f'{initializer_prefix}Color.color(withHexString:)'
+ elif isinstance(property_type, String):
+ method = resolve_string_based_value
+ optimized = 'as CFString' if property_type.enable_optimization else ''
+ initializer = f'{initializer_prefix}{{ $0 {optimized}}}'
+ elif isinstance(property_type, Url):
+ method = resolve_string_based_value
+ initializer = f'{initializer_prefix}URL.init(string:)'
+ elif isinstance(property_type, (Int, Double, Bool, BoolInt)):
+ method = 'resolveNumericValue'
+ initializer = ''
+ elif isinstance(property_type, Object) and isinstance(property_type.object, StringEnumeration):
+ method = resolve_string_based_value
+ initializer = f'{initializer_prefix}{self.expression_declaration(optionality=False)}.init(rawValue:)'
+ elif isinstance(property_type, Array):
+ return self.expression_resolving_method_parts(cast(SwiftPropertyType, property_type.property_type))
+ else:
+ raise NotImplementedError
+ return method, initializer
+
+ @property
+ def type_declaration(self) -> str:
+ if self.mode in [GenerationMode.NORMAL_WITH_TEMPLATES, GenerationMode.NORMAL_WITHOUT_TEMPLATES]:
+ optionality = '?' if self.should_be_optional else ''
+ return cast(SwiftPropertyType, self.property_type).declaration(self.swift_mode) + optionality
+ elif self.mode is GenerationMode.TEMPLATE:
+ return f'Field<{cast(SwiftPropertyType, self.property_type).declaration(self.swift_mode)}>?'
+
+ def expression_declaration(self, optionality: bool = False) -> str:
+ mode = SwiftProperty.SwiftMode(value=self.swift_mode.value, use_expressions=False)
+ return cast(SwiftPropertyType, self.property_type).declaration(mode) + ('?' if optionality else '')
+
+ def expression_resolving_method_declaration(self, access_level: SwiftAccessLevel) -> str:
+ result = self.expression_declaration(optionality=self.default_value_declaration is None)
+ access = access_level.value
+ name = utils.capitalize_camel_case(self.name)
+ return f'{access}func resolve{name}(_ resolver: ExpressionResolver) -> {result}'
+
+ @property
+ def default_value_declaration(self) -> Optional[str]:
+ if self.default_value is None:
+ return None
+ return cast(SwiftPropertyType, self.property_type).internal_declaration(self.default_value)
+
+ def expression_resolving_method_default_value(self, prop_type: SwiftPropertyType) -> str:
+ default_value_declaration = self.default_value_declaration
+ if default_value_declaration is None:
+ return ''
+ prefix = ''
+ if isinstance(prop_type, Object) and isinstance(prop_type.object, StringEnumeration):
+ prefix = self.expression_declaration(optionality=False)
+ return f' ?? {prefix}{default_value_declaration}'
+
+ @property
+ def validator_var_name(self) -> str:
+ return f'{self.declaration_name}Validator'
+
+ @property
+ def validator_var_type(self) -> str:
+ if isinstance(self.property_type, Array):
+ array_or_empty = 'Array'
+ item_type = cast(SwiftPropertyType, self.property_type.property_type)
+ generic_arg = item_type.prefixed_declaration(self.swift_mode)
+ else:
+ array_or_empty = ''
+ prop_type = cast(SwiftPropertyType, self.property_type)
+ generic_arg = prop_type.prefixed_declaration(SwiftProperty.SwiftMode(value=self.swift_mode.value,
+ use_expressions=False))
+ return f'Any{array_or_empty}ValueValidator<{generic_arg}>'
+
+ @property
+ def validator_declaration(self) -> Optional[Text]:
+ validator = self.internal_validator_declaration(mode=self.mode)
+ if validator is None:
+ return validator
+ result = Text(f'static let {self.validator_var_name}: {self.validator_var_type} =')
+ result += utils.indented(validator)
+ return result
+
+ def internal_validator_declaration(self, mode: GenerationMode) -> Optional[str]:
+ if mode is GenerationMode.TEMPLATE:
+ if isinstance(self.property_type, Array) and self.property_type.strict_parsing:
+ pass
+ else:
+ return None
+ if isinstance(self.property_type, Array) and \
+ (self.property_type.min_items > 0 or self.property_type.strict_parsing):
+ validator_type = 'Strict' if self.property_type.strict_parsing else ''
+ validator_name = f'make{validator_type}ArrayValidator'
+ validator_args = [f'minItems: {self.property_type.min_items}']
+ elif isinstance(self.property_type, String) and \
+ (self.property_type.min_length > 0 or self.optional or self.property_type.regex is not None):
+ optimized = 'CFString' if self.property_type.enable_optimization else 'String'
+ validator_name = f'make{optimized}Validator'
+ min_length = self.property_type.min_length
+ actual_min_length = 1 if min_length == 0 and self.optional else min_length
+ validator_args = []
+ if actual_min_length > 0:
+ validator_args.append(f'minLength: {actual_min_length}')
+ if self.property_type.regex is not None:
+ escaped_pattern = self.property_type.regex.pattern.replace('\\', '\\\\')
+ validator_args.append(f'regex: "{escaped_pattern}"')
+ elif isinstance(self.property_type, Url) and self.property_type.schemes is not None:
+ validator_name = 'makeURLValidator'
+ joined = ', '.join(map(lambda val: f'"{val}"', self.property_type.schemes))
+ validator_args = [f'schemes: [{joined}]']
+ elif isinstance(self.property_type, (Int, Double)):
+ constraint = self.property_type.constraint
+ if constraint is None:
+ return None
+ constraint = constraint.replace('number', '$0')
+ validator_name = 'makeValueValidator'
+ validator_args = [f'valueValidator: {{ {constraint} }}']
+ elif isinstance(self.property_type, Array) and self.optional:
+ validator_name = 'makeNoOpArrayValidator'
+ validator_args = []
+ else:
+ if self.optional:
+ validator_name = 'makeNoOpValueValidator'
+ validator_args = []
+ else:
+ return None
+
+ prefix = f'{validator_name}('
+ separator = f',\n{" " * len(prefix)}'
+ return f'{prefix}{separator.join(validator_args)})'
+
+ def validator_arg(self, entity_name: str, mode: GenerationMode) -> str:
+ if self.internal_validator_declaration(mode) is None:
+ return ''
+ return f', validator: {entity_name}.{self.validator_var_name}'
+
+ @property
+ def parsed_value_is_optional(self) -> bool:
+ return self.optional or \
+ self.default_value is not None or \
+ cast(SwiftPropertyType, self.property_type).empty_constructor is not None
+
+ def deserialization_expression(self, in_value_resolving: bool) -> str:
+ mode = SwiftProperty.SwiftMode(GenerationMode.TEMPLATE, self.supports_expressions)
+ if isinstance(self.property_type, Array):
+ type_suffix = 'Array'
+ item_type = cast(SwiftPropertyType, self.property_type.property_type)
+ type_str = item_type.declaration(mode)
+ else:
+ type_suffix = 'Field'
+ item_type = cast(SwiftPropertyType, self.property_type)
+ type_str = item_type.declaration(mode)
+ include_type = in_value_resolving and self.property_type.can_be_templated
+ type_arg = f', type: {type_str}.self' if include_type else ''
+ if not include_type and self.property_type.can_be_templated:
+ template_to_type_arg = swift_template_deserializable_args(self.mode)
+ else:
+ template_to_type_arg = ''
+ optional_suffix = 'Optional' if self.parsed_value_is_optional or self.mode.is_template else ''
+ expression_suffix = 'Expression' if self.supports_expressions else ''
+ if in_value_resolving:
+ validator_arg_string = self.validator_arg(entity_name='ResolvedValue',
+ mode=GenerationMode.NORMAL_WITH_TEMPLATES)
+ else:
+ validator_arg_string = self.validator_arg(entity_name='Self',
+ mode=self.mode)
+ transformed = cast(SwiftPropertyType, self.property_type).transform_arg
+ expr = f'"{self.dict_field}"{template_to_type_arg}{transformed}{validator_arg_string}{type_arg}'
+ return f'get{optional_suffix}{expression_suffix}{type_suffix}({expr})'
+
+ def add_default_value_to(self, declaration: str) -> str:
+ prop_type = cast(SwiftPropertyType, self.property_type)
+ if self.default_value is not None:
+ default_value_declaration_to_use = prop_type.internal_declaration(self.default_value)
+ elif (empty_dict_deserialization := prop_type.empty_constructor) is not None:
+ default_value_declaration_to_use = empty_dict_deserialization
+ else:
+ return declaration
+
+ if self.supports_expressions:
+ default_value_declaration_to_use = f'.value({default_value_declaration_to_use})'
+
+ return f'{declaration} ?? {default_value_declaration_to_use}'
+
+ @property
+ def deserialize_from_value_expression(self) -> str:
+ template_args = ''
+ if self.property_type.can_be_templated:
+ template_args = ', templates: context.templates, templateToType: context.templateToType'
+ validator_arg_str = self.validator_arg(entity_name='ResolvedValue',
+ mode=GenerationMode.NORMAL_WITH_TEMPLATES)
+ type_arg = ''
+ if self.property_type.can_be_templated:
+ if isinstance(self.property_type, Array):
+ prop = cast(SwiftPropertyType, self.property_type.property_type)
+ else:
+ prop = cast(SwiftPropertyType, self.property_type)
+ type_arg = f', type: {prop.prefixed_declaration(self.swift_mode)}.self'
+ transform = cast(SwiftPropertyType, self.property_type).transform_arg
+ return f'deserialize(__dictValue{template_args}{transform}{validator_arg_str}{type_arg})'
+
+ @property
+ def parent_template_constructor_line(self) -> str:
+ return f'{self.declaration_name}: {self.declaration_name} ?? mergedParent.{self.declaration_name}'
+
+ @property
+ def serialization_declaration(self) -> str:
+ prefix = f'result["{self.dict_field}"] = '
+ prop = cast(SwiftPropertyType, self.property_type)
+ suffix = prop.serialization_suffix(self.supports_expressions)
+ return f'{prefix}{self.declaration_name}{"?" if self.should_be_optional and suffix else ""}{suffix}'
+
+
+class SwiftPropertyType(PropertyType):
+ @property
+ def empty_constructor(self) -> Optional[str]:
+ if isinstance(self, Object) and isinstance(self.object, Entity) and \
+ self.object.all_properties_are_optional_except_default_values:
+ return f'{self.object.prefixed_declaration}()'
+ return None
+
+ def declaration(self, mode: SwiftProperty.SwiftMode) -> str:
+ return self._declaration(prefixed=False, mode=mode)
+
+ def prefixed_declaration(self, mode: SwiftProperty.SwiftMode) -> str:
+ return self._declaration(prefixed=True, mode=mode)
+
+ def _declaration(self, prefixed: bool, mode: SwiftProperty.SwiftMode) -> str:
+ result = self.__declaration(prefixed, mode)
+ if mode.use_expressions and not isinstance(self, Array):
+ return f'Expression<{result}>'
+ else:
+ return result
+
+ def __declaration(self, prefixed: bool, mode: SwiftProperty.SwiftMode) -> str:
+ if isinstance(self, Int):
+ return 'Int'
+ elif isinstance(self, Double):
+ return 'Double'
+ elif isinstance(self, (Bool, BoolInt)):
+ return 'Bool'
+ elif isinstance(self, String):
+ return 'CFString' if self.enable_optimization else 'String'
+ elif isinstance(self, StaticString):
+ raise TypeError
+ elif isinstance(self, Url):
+ return 'URL'
+ elif isinstance(self, Color):
+ return 'Color'
+ elif isinstance(self, Dictionary):
+ return '[String: Any]'
+ elif isinstance(self, Object):
+ if self.name.startswith('$predefined_'):
+ return self.name.replace('$predefined_', '')
+ prefix: str = ''
+ if prefixed:
+ obj = self.object
+ if obj is not None:
+ if mode.value in [GenerationMode.NORMAL_WITH_TEMPLATES,
+ GenerationMode.NORMAL_WITHOUT_TEMPLATES]:
+ prefix = obj.resolved_declaration_prefix
+ elif mode.value is GenerationMode.TEMPLATE:
+ prefix = obj.template_declaration_prefix
+ name_to_use: str
+ obj = self.object
+ if isinstance(obj, Entity):
+ name_to_use = obj.resolved_name + mode.value.name_suffix
+ elif isinstance(obj, EntityEnumeration):
+ name_to_use = obj.resolved_name + mode.value.name_suffix
+ elif isinstance(obj, StringEnumeration):
+ name_to_use = obj.name
+ else:
+ raise TypeError
+ return prefix + utils.capitalize_camel_case(name_to_use)
+ elif isinstance(self, Array):
+ element_type = cast(SwiftPropertyType, self.property_type)
+ return f'[{element_type._declaration(prefixed, mode)}]'
+
+ def internal_declaration(self, default_value: str) -> Optional[str]:
+ if isinstance(self, (Int, Bool, BoolInt, Double)):
+ return default_value
+ elif isinstance(self, String):
+ value_with_escaping_quotes = default_value.replace('"', '\"')
+ return f'"{value_with_escaping_quotes}"'
+ elif isinstance(self, Url):
+ return f'URL(string: "{default_value}")!'
+ elif isinstance(self, Color):
+ color_value = default_value[1::].upper()
+ if len(color_value) == 3:
+ joined = ''.join(c + c for c in color_value)
+ color_argb_hex = f'FF{joined}'
+ elif len(color_value) == 4:
+ color_argb_hex = ''.join(c + c for c in color_value)
+ elif len(color_value) == 6:
+ color_argb_hex = f'FF{color_value}'
+ elif len(color_value) == 8:
+ color_argb_hex = color_value
+ else:
+ raise ValueError
+ return f'Color.colorWithARGBHexCode(0x{color_argb_hex})'
+ elif isinstance(self, Array):
+ without_whitespaces = default_value.replace(' ', '').replace('\n', '')
+ if not without_whitespaces.startswith('[') or not without_whitespaces.endswith(']'):
+ return None
+ if without_whitespaces == '[]':
+ values = []
+ else:
+ values = without_whitespaces[1:-1].split(',')
+ item_type = cast(SwiftPropertyType, self.property_type)
+ declarations = list(filter(None, map(lambda value: item_type.internal_declaration(value), values)))
+ if len(values) != len(declarations):
+ return None
+ joined = ', '.join(declarations)
+ return f'[{joined}]'
+ elif isinstance(self, Object):
+ if self.object is None:
+ return None
+
+ if isinstance(self.object, StringEnumeration):
+ enum_case = next((case for case in self.object.cases if case[1] == default_value), None)
+ if enum_case is None:
+ raise ValueError(default_value)
+ return f'.{utils.fixing_first_digit(utils.lower_camel_case(enum_case[0]))}'
+ default_value_dict = utils.json_dict(default_value)
+
+ if isinstance(self.object, EntityEnumeration):
+ type_val = default_value_dict.get('type')
+ enum_case = None
+ for case in self.object.entities:
+ ent = case[1]
+ if isinstance(ent, Entity) and ent.static_type == type_val:
+ enum_case = case
+ if enum_case is None:
+ raise ValueError(type_val)
+ obj = cast(SwiftPropertyType, Object(name='', object=enum_case[1], format=ObjectFormat.DEFAULT))
+ case_constructor: Optional[str] = obj.internal_declaration(default_value)
+ if case_constructor is None:
+ return None
+ return f'.{utils.lower_camel_case(enum_case[0])}({case_constructor})'
+
+ entity: SwiftEntity = cast(SwiftEntity, self.object)
+ entity.__class__ = SwiftEntity
+ args = []
+ for prop in entity.instance_properties_swift:
+ str_type = default_value_dict.get(prop.dict_field)
+ if str_type is None:
+ continue
+ declaration = cast(SwiftPropertyType, prop.property_type).internal_declaration(str_type)
+ default_value = f'.value({declaration})' if prop.supports_expressions else declaration
+ args.append(f'{prop.declaration_name}: {default_value}')
+ args = ', '.join(args)
+ return f'{entity.declaration_prefix}{utils.capitalize_camel_case(entity.original_name)}({args})'
+ else:
+ return None
+
+ @property
+ def transform_arg(self) -> str:
+ prefix = ', transform: '
+ if isinstance(self, Url):
+ return f'{prefix}URL.init(string:)'
+ elif isinstance(self, Color):
+ return f'{prefix}Color.color(withHexString:)'
+ elif isinstance(self, Object):
+ if self.format is ObjectFormat.DEFAULT:
+ return ''
+ return f'{prefix}{utils.capitalize_camel_case(self.name)}.init(JSONString:)'
+ elif isinstance(self, Array):
+ return cast(SwiftPropertyType, self.property_type).transform_arg
+ else:
+ return ''
+
+ @property
+ def is_equatable(self) -> bool:
+ if isinstance(self, Dictionary):
+ return False
+ elif isinstance(self, Array):
+ return cast(SwiftPropertyType, self.property_type).is_equatable
+ else:
+ return True
+
+ def serialization_suffix(self, use_expressions: bool) -> str:
+ if isinstance(self, Dictionary):
+ return ''
+ elif isinstance(self, (String, Int, Double, Bool, BoolInt)):
+ return '.toValidSerializationValue()' if use_expressions else ''
+ elif isinstance(self, Object):
+ if isinstance(self.object, StringEnumeration):
+ return '.toValidSerializationValue()' if use_expressions else '.rawValue'
+ else:
+ return '.toDictionary()'
+ elif isinstance(self, Url):
+ return '.toValidSerializationValue()' if use_expressions else '.absoluteString'
+ elif isinstance(self, Color):
+ return '.toValidSerializationValue()' if use_expressions else '.hexString'
+ elif isinstance(self, Array):
+ suffix = cast(SwiftPropertyType, self.property_type).serialization_suffix(use_expressions)
+ return '' if not suffix else f'.map {{ $0{suffix} }}'
+ elif isinstance(self, StaticString):
+ raise TypeError('Can\'t serialize static value')
+
+
+class SwiftEntityEnumeration(EntityEnumeration):
+ def resolve_parent_implementation(self, access_level: SwiftAccessLevel) -> Text:
+ access = access_level.value
+ name = utils.capitalize_camel_case(self.name)
+ result = Text(f'{access}func resolveParent(templates: Templates) throws -> {name} {{')
+ result += ' switch self {'
+ for name in self._resolved_entity_names:
+ lower_name = utils.lower_camel_case(name)
+ result += f' case let .{lower_name}Template(value):'
+ result += f' return .{lower_name}Template(try value.resolveParent(templates: templates))'
+ result += ' }'
+ result += '}'
+ return result
+
+ def resolve_value_implementation(self, access_level: SwiftAccessLevel) -> Text:
+ access = access_level.value
+ name = utils.capitalize_camel_case(self.name)
+ params = f'context: Context, parent: {name}?, useOnlyLinks: Bool'
+ return_type = f'DeserializationResult<{self.resolved_prefixed_declaration}>'
+ result = Text(f'{access}static func resolveValue({params}) -> {return_type} {{')
+ result += ' guard let parent = parent else {'
+ result += ' if useOnlyLinks {'
+ result += ' return .failure(NonEmptyArray(.missingType(representation: context.templateData)))'
+ result += ' } else {'
+ result += ' return resolveUnknownValue(context: context, useOnlyLinks: useOnlyLinks)'
+ result += ' }'
+ result += ' }'
+ result += EMPTY
+ result += ' switch parent {'
+ for name in self._resolved_entity_names:
+ lower_name = utils.lower_camel_case(name)
+ result += f' case let .{lower_name}Template(value):'
+ result += ' let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)'
+ result += ' switch result {'
+ result += f' case let .success(value): return .success(.{lower_name}(value))'
+ result += f' case let .partialSuccess(value, warnings): return .partialSuccess(.{lower_name}(value), warnings: warnings)'
+ result += ' case let .failure(errors): return .failure(errors)'
+ result += ' case .noValue: return .noValue'
+ result += ' }'
+ result += ' }'
+ result += '}'
+ return result
+
+ @property
+ def resolve_unknown_value_implementation(self) -> Text:
+ params = 'context: Context, useOnlyLinks: Bool'
+ return_type = f'DeserializationResult<{self.resolved_prefixed_declaration}>'
+ result = Text(f'private static func resolveUnknownValue({params}) -> {return_type} {{')
+ result += ' guard let type = (context.templateData["type"] as? String).flatMap({ context.templateToType[$0] ?? $0 }) else {'
+ result += ' return .failure(NonEmptyArray(FieldError(fieldName: "type", level: .error, error: .requiredFieldIsMissing)))'
+ result += ' }'
+ result += EMPTY
+ result += ' switch type {'
+ for name in self._resolved_entity_names:
+ upper_name = utils.capitalize_camel_case(name)
+ lower_name = utils.lower_camel_case(name)
+ result += f' case {upper_name}.type:'
+ result += f' let result = {upper_name}Template.resolveValue(context: context, useOnlyLinks: useOnlyLinks)'
+ result += ' switch result {'
+ result += f' case let .success(value): return .success(.{lower_name}(value))'
+ result += f' case let .partialSuccess(value, warnings): return .partialSuccess(.{lower_name}(value), warnings: warnings)'
+ result += ' case let .failure(errors): return .failure(errors)'
+ result += ' case .noValue: return .noValue'
+ result += ' }'
+ result += ' default:'
+ result += ' return .failure(NonEmptyArray(FieldError(fieldName: "type", level: .error, error: .requiredFieldIsMissing)))'
+ result += ' }'
+ result += '}'
+ return result
diff --git a/api_generator/api_generator/generators/swift/utils.py b/api_generator/api_generator/generators/swift/utils.py
new file mode 100644
index 000000000..6969368b9
--- /dev/null
+++ b/api_generator/api_generator/generators/swift/utils.py
@@ -0,0 +1,17 @@
+from ...config import GenerationMode
+from typing import Optional
+
+
+def fixing_keywords(string: str) -> str:
+ if string == 'default':
+ return '`default`'
+ return string
+
+
+def implemented_swift_protocol(mode: GenerationMode) -> Optional[str]:
+ if mode is GenerationMode.NORMAL_WITHOUT_TEMPLATES:
+ return 'Deserializable'
+ elif mode is GenerationMode.TEMPLATE:
+ return 'TemplateDeserializable'
+ else:
+ return None
diff --git a/api_generator/api_generator/generators/type_script/__init__.py b/api_generator/api_generator/generators/type_script/__init__.py
new file mode 100644
index 000000000..b8b2e71b5
--- /dev/null
+++ b/api_generator/api_generator/generators/type_script/__init__.py
@@ -0,0 +1,3 @@
+from .generator import TypeScriptGenerator
+
+__all__ = [TypeScriptGenerator]
diff --git a/api_generator/api_generator/generators/type_script/generator.py b/api_generator/api_generator/generators/type_script/generator.py
new file mode 100644
index 000000000..856224336
--- /dev/null
+++ b/api_generator/api_generator/generators/type_script/generator.py
@@ -0,0 +1,102 @@
+from typing import cast, List
+
+from .typescript_entities import (
+ TypeScriptEntity,
+ _type_script_full_name,
+ _make_type_script_imports
+)
+from ..base import Generator
+from ... import utils
+from ...schema.modeling.entities import (
+ StringEnumeration,
+ EntityEnumeration,
+ Entity,
+ Declarable,
+)
+from ...schema.modeling.text import Text, EMPTY
+
+
+class TypeScriptGenerator(Generator):
+ def _filename(self, name: str) -> str:
+ return f'{utils.capitalize_camel_case(name)}.ts'
+
+ def generate(self, objects: List[Declarable]):
+ super(TypeScriptGenerator, self).generate(objects)
+ self.__generate_index_file(objects)
+
+ def __generate_index_file(self, objects: List[Declarable]):
+ file_content = Text('// Generated code. Do not modify.')
+ file_content += EMPTY
+ object_names = sorted(map(lambda o: utils.capitalize_camel_case(o.name), objects))
+ for obj_name in object_names:
+ file_content += f"export * from './{obj_name}';"
+ with open(f'{self._config.output_path}/index.ts', 'w') as file:
+ file.write(str(file_content))
+
+ def _entity_declaration(self, entity: Entity) -> Text:
+ result = Text()
+ entity.__class__ = TypeScriptEntity
+ entity = cast(TypeScriptEntity, entity)
+ imports = entity.imports
+ if imports is not None:
+ result += imports
+ result += self.__entity_declaration_without_imports(entity)
+ return result
+
+ def __entity_declaration_without_imports(self, entity: TypeScriptEntity) -> Text:
+ if entity.should_generate_as_type_script_class:
+ result = entity.class_declaration
+ else:
+ result = entity.interface_declaration
+ if entity.inner_types:
+ result += EMPTY
+ result += self.__inner_types_declaration(entity)
+ return result
+
+ def __inner_types_declaration(self, entity: TypeScriptEntity) -> Text:
+ def declaration_of(decl_type, decl_method) -> Text:
+ def sort_predicate(d: Declarable):
+ return d.name
+ inner_types_decl = Text()
+ inner_types = sorted(filter(lambda t: isinstance(t, decl_type), entity.inner_types), key=sort_predicate)
+ for ind, p in enumerate(inner_types):
+ if isinstance(p, decl_type):
+ inner_types_decl += decl_method(p)
+ inner_types_decl += EMPTY
+ return inner_types_decl
+ declarations = [declaration_of(StringEnumeration, self._string_enumeration_declaration),
+ declaration_of(EntityEnumeration, self.__entity_enumeration_declaration_without_imports),
+ declaration_of(Entity, self.__entity_declaration_without_imports)]
+ result = Text()
+ for decl in declarations:
+ if decl.lines:
+ result += decl
+
+ return result
+
+ def _entity_enumeration_declaration(self, entity_enumeration: EntityEnumeration) -> Text:
+ imports = sorted(filter(None, map(lambda e: _type_script_full_name(e[1]) if e[1] is not None else None,
+ entity_enumeration.entities)))
+ result = _make_type_script_imports(items=imports)
+ result += self.__entity_enumeration_declaration_without_imports(entity_enumeration)
+ return result
+
+ @staticmethod
+ def __entity_enumeration_declaration_without_imports(entity_enumeration: EntityEnumeration) -> Text:
+ result = Text(f'export type {_type_script_full_name(entity_enumeration)} =')
+ names = list(filter(None, map(lambda e: _type_script_full_name(e[1]) if e[1] is not None else None,
+ entity_enumeration.entities)))
+ if entity_enumeration.generate_case_for_templates:
+ names.append('TemplateBlock')
+ for ind, name in enumerate(names):
+ ending = ';' if ind == (len(names) - 1) else ''
+ result += f' | {name}{ending}'
+ return result
+
+ def _string_enumeration_declaration(self, string_enumeration: StringEnumeration) -> Text:
+ result = Text(f'export type {_type_script_full_name(string_enumeration)} =')
+ values = list(map(lambda case: case[1], string_enumeration.cases))
+ for ind, value in enumerate(values):
+ ending = ';' if ind == (len(values) - 1) else ''
+ result += f" | '{value}'{ending}"
+ return result
diff --git a/api_generator/api_generator/generators/type_script/typescript_entities.py b/api_generator/api_generator/generators/type_script/typescript_entities.py
new file mode 100644
index 000000000..de159f03e
--- /dev/null
+++ b/api_generator/api_generator/generators/type_script/typescript_entities.py
@@ -0,0 +1,246 @@
+from typing import List, cast, Optional
+
+from ... import utils
+from ...schema.modeling.entities import (
+ Entity,
+ Property,
+ PropertyType,
+ Int,
+ Bool,
+ BoolInt,
+ Double,
+ StaticString,
+ Object,
+ Array,
+ Url,
+ Color,
+ String,
+ Dictionary,
+ Declarable
+)
+from ...schema.modeling.text import Text, EMPTY
+
+
+def _as_type_script_commentary_content(s: str) -> Text:
+ result = Text()
+ words = s.replace('', '').replace('', '').split(' ')
+ line = ''
+ for word in words:
+ if not line:
+ line = str(word)
+ elif (len(line) + len(word)) > 93:
+ result += f' * {line}'
+ line = str(word)
+ else:
+ line += f' {word}'
+ result += f' * {line}'
+ return result
+
+
+def _as_type_script_commentary(s: str) -> Text:
+ if not s:
+ return EMPTY
+ return Text('/**') + _as_type_script_commentary_content(s) + Text(' */')
+
+
+def _type_script_full_name(d: Declarable) -> str:
+ full_name = utils.capitalize_camel_case(d.name)
+ if d.parent is not None:
+ full_name = utils.capitalize_camel_case(d.parent.name) + full_name
+ if isinstance(d, Entity):
+ d.__class__ = TypeScriptEntity
+ if not cast(TypeScriptEntity, d).should_generate_as_type_script_class:
+ full_name = f'I{full_name}'
+ return full_name
+
+
+def _type_script_type_name(property_type: PropertyType, supports_expressions: bool) -> str:
+ if isinstance(property_type, (Int, Double)):
+ return 'number'
+ elif isinstance(property_type, Bool):
+ return 'boolean'
+ elif isinstance(property_type, BoolInt):
+ return 'IntBoolean'
+ elif isinstance(property_type, (Color, String, Url)):
+ return 'string'
+ elif isinstance(property_type, StaticString):
+ raise TypeError('Is a static type')
+ elif isinstance(property_type, Dictionary):
+ return '{}'
+ elif isinstance(property_type, Array):
+ item_type = property_type.property_type
+ array_type_name = _type_script_type_name(item_type, supports_expressions)
+ if supports_expressions and not isinstance(item_type, Array):
+ array_type_name += ' | DivExpression'
+ return f'NonEmptyArray<{array_type_name}>'
+ elif isinstance(property_type, Object):
+ if property_type.object is None:
+ raise ValueError(f'Invalid {property_type}')
+ return _type_script_full_name(property_type.object)
+ else:
+ raise NotImplementedError
+
+
+def _referenced_top_level_type_name(property_type: PropertyType) -> Optional[str]:
+ if isinstance(property_type, Object):
+ obj = property_type.object
+ if obj is not None and obj.parent is None:
+ return _type_script_full_name(obj)
+ return None
+ elif isinstance(property_type, Array):
+ return _referenced_top_level_type_name(property_type.property_type)
+ return None
+
+
+def _make_type_script_imports(items: List[str]) -> Text:
+ if not items:
+ return EMPTY
+ result = Text()
+ result += 'import {'
+ for item in items:
+ result += f' {item},'
+ result += "} from './';"
+ result += EMPTY
+ return result
+
+
+class TypeScriptEntity(Entity):
+ @property
+ def static_properties(self) -> List[Property]:
+ return list(filter(lambda p: isinstance(p.property_type, StaticString), self.properties))
+
+ @property
+ def should_generate_as_type_script_class(self) -> bool:
+ return len(self.static_properties) != 0
+
+ @property
+ def props_interface_name(self) -> str:
+ return f'{_type_script_full_name(self)}Props'
+
+ @property
+ def referenced_top_level_types(self) -> List[str]:
+ return list(filter(None, map(lambda p: _referenced_top_level_type_name(p.property_type), self.properties)))
+
+ @property
+ def imports(self) -> Optional[Text]:
+ types = self.referenced_top_level_types
+ for inner_type in self.inner_types:
+ if isinstance(inner_type, Entity):
+ inner_type.__class__ = TypeScriptEntity
+ types += cast(TypeScriptEntity, inner_type).referenced_top_level_types
+ type_script_full_name = _type_script_full_name(self)
+ unique_types = list(filter(lambda t: t != type_script_full_name, set(types)))
+ return None if not unique_types else _make_type_script_imports(sorted(unique_types))
+
+ @property
+ def class_declaration(self) -> Text:
+ result = Text()
+ commentary = _as_type_script_commentary(self.description_doc())
+ if commentary != EMPTY:
+ result += commentary
+ prop_name = self.props_interface_name
+ result += f'export class {_type_script_full_name(self)} {{'
+ result += f' readonly _props?: Exact<{prop_name}, T>;'
+
+ class_properties_declaration = self.class_properties_declaration
+ if class_properties_declaration.lines:
+ result += EMPTY
+ result += class_properties_declaration
+
+ constructor_declaration = self.constructor_declaration
+ if constructor_declaration.lines:
+ result += EMPTY
+ result += constructor_declaration
+ result += '}'
+ result += EMPTY
+ result += f'interface {prop_name} {{'
+ dynamic_properties_declaration = self.dynamic_properties_declaration
+ if dynamic_properties_declaration.lines:
+ result += dynamic_properties_declaration
+ result += '}'
+ return result
+
+ @property
+ def class_properties_declaration(self) -> Text:
+ result = Text()
+ for prop in self.static_properties:
+ prop.__class__ = TypeScriptProperty
+ prop = cast(TypeScriptProperty, prop)
+ commentary = prop.commentary
+ if commentary is not None:
+ result += commentary.indented(indent_width=4)
+ result += f' readonly {prop.escaped_name} = {prop.static_value};'
+ dynamic_properties_declaration = self.dynamic_properties_declaration
+ if dynamic_properties_declaration.lines:
+ result += dynamic_properties_declaration
+ return result
+
+ @property
+ def constructor_declaration(self) -> Text:
+ optional_marker = '' if any(not p.optional for p in self.instance_properties) else '?'
+ result = Text(f' constructor(props{optional_marker}: Exact<{self.props_interface_name}, T>) {{')
+ for prop in self.instance_properties:
+ result += f' this.{prop.name} = props{optional_marker}.{prop.name};'
+ result += ' }'
+ return result
+
+ @property
+ def dynamic_properties_declaration(self) -> Text:
+ result = Text()
+ for prop in self.instance_properties:
+ prop.__class__ = TypeScriptProperty
+ prop = cast(TypeScriptProperty, prop)
+ commentary = prop.commentary
+ if commentary is not None:
+ result += commentary.indented(indent_width=4)
+ result += f' {prop.make_declaration(is_templatable=self._type_script_templatable)};'
+ return result
+
+ @property
+ def interface_declaration(self) -> Text:
+ result = Text()
+ commentary = _as_type_script_commentary(self.description_doc())
+ if commentary != EMPTY:
+ result += commentary
+ result += f'export interface {_type_script_full_name(self)} {{'
+ result += self.dynamic_properties_declaration
+ result += '}'
+ return result
+
+
+class TypeScriptProperty(Property):
+ @property
+ def commentary(self) -> Optional[Text]:
+ result = Text()
+ if self.description_doc() is not None:
+ result += _as_type_script_commentary_content(self.description_doc())
+
+ if self.is_deprecated:
+ if result.lines:
+ result += ' *'
+ result += ' * @deprecated'
+
+ if result.lines:
+ result = Text('/**') + result + Text(' */')
+ return result
+
+ return None
+
+ @property
+ def escaped_name(self) -> str:
+ return self.name if '-' not in self.name else f"'{self.name}'"
+
+ @property
+ def static_value(self) -> str:
+ if isinstance(self.property_type, StaticString):
+ return f"'{self.property_type.value}'"
+ else:
+ raise TypeError(f'{self.property_type.__class__} is not a static type')
+
+ def make_declaration(self, is_templatable: bool) -> str:
+ optional_mark = '?' if self.optional else ''
+ type_name = _type_script_type_name(self.property_type, self.supports_expressions)
+ type_decl = f'Type<{type_name}>' if is_templatable else type_name
+ if self.supports_expressions and not isinstance(self.property_type, Array):
+ type_decl += ' | DivExpression'
+ return f'{self.escaped_name}{optional_mark}: {type_decl}'
diff --git a/api_generator/api_generator/schema/__init__.py b/api_generator/api_generator/schema/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/api_generator/api_generator/schema/modeling/__init__.py b/api_generator/api_generator/schema/modeling/__init__.py
new file mode 100644
index 000000000..1dde61f3a
--- /dev/null
+++ b/api_generator/api_generator/schema/modeling/__init__.py
@@ -0,0 +1,3 @@
+from .modeler import build_objects
+
+__all__ = [build_objects]
diff --git a/api_generator/api_generator/schema/modeling/builders.py b/api_generator/api_generator/schema/modeling/builders.py
new file mode 100644
index 000000000..f5a564363
--- /dev/null
+++ b/api_generator/api_generator/schema/modeling/builders.py
@@ -0,0 +1,276 @@
+import re
+from typing import List, Tuple, Optional, Dict, Union, cast
+
+from .utils import (
+ fixing_reserved_typename,
+ generate_cases_for_templates,
+ alias,
+ platforms
+)
+from ..utils import is_list_of_type, is_dict_with_keys_of_type, code_generation_disabled
+
+from ...config import Config, GenerationMode, GeneratedLanguage
+from ..preprocessing.entities import ElementLocation
+from .entities import (
+ Entity,
+ EntityEnumeration,
+ StringEnumeration,
+ Declarable,
+ PropertyType,
+ Property,
+ default_value as property_default_value,
+ Int,
+ Bool,
+ Double,
+ StaticString,
+ Object,
+ Array,
+ ObjectFormat,
+ Url,
+ Color,
+ String,
+ Dictionary, BoolInt
+)
+from .errors import InvalidFieldRepresentationError, UnsupportedFormatTypeError
+
+
+def __generate_templates(config: Config.GenerationConfig) -> bool:
+ return config.lang in [GeneratedLanguage.SWIFT, GeneratedLanguage.KOTLIN]
+
+
+def __resolve_string_field(name: str,
+ mode: GenerationMode,
+ config: Config.GenerationConfig,
+ location: ElementLocation,
+ dictionary: Dict[str, any]) -> Tuple[PropertyType, List[Declarable]]:
+ force_instance_field: bool = dictionary.get('force_instance_field', False)
+ enum_cases: List[str] = dictionary.get('enum')
+ if not force_instance_field and enum_cases is not None:
+ if not is_list_of_type(enum_cases, str):
+ raise TypeError
+ force_enum_field: bool = dictionary.get('force_instance_field', False)
+ if len(enum_cases) > 1 or force_enum_field:
+ fixed_name = fixing_reserved_typename(name, config.lang)
+ enumeration = StringEnumeration(name=fixed_name,
+ original_name=name,
+ cases=enum_cases,
+ include_documentation_toc=dictionary.get('include_in_documentation_toc',
+ False))
+ return Object(name=name, object=None, format=ObjectFormat.DEFAULT), [enumeration]
+ else:
+ if len(enum_cases) < 1:
+ raise InvalidFieldRepresentationError(location=location + 'enum', value=enum_cases)
+ return StaticString(value=enum_cases[0]), []
+ else:
+ format_value: Optional[Union[str, Dict[str, any]]] = dictionary.get('format')
+ if format_value == 'uri':
+ return Url(schemes=dictionary.get('schemes')), []
+ elif format_value == 'color':
+ return Color(), []
+ elif is_dict_with_keys_of_type(format_value, str):
+ property_type, inner_declarations = type_property_build(dictionary=format_value,
+ outer_name=name,
+ location=location + 'format',
+ mode=mode,
+ config=config)
+ if not isinstance(property_type, Object):
+ raise UnsupportedFormatTypeError
+ assert property_type.format == ObjectFormat.DEFAULT
+ return Object(name=property_type.name, object=property_type.object,
+ format=ObjectFormat.JSON_STRING), inner_declarations
+ else:
+ pattern = dictionary.get('pattern')
+ regex = re.compile(pattern) if pattern is not None else None
+ return String(min_length=dictionary.get('minLength', 0),
+ formatted=format_value == 'formatted_string',
+ regex=regex,
+ enable_optimization=dictionary.get('client_optimized', False)), []
+
+
+def _entity_enumeration_build(entities: List[Dict[str, any]],
+ name: str,
+ original_name: str,
+ include_in_documentation_toc: bool,
+ root_entity: bool,
+ generate_case_for_templates: bool,
+ location: ElementLocation,
+ mode: GenerationMode,
+ config: Config.GenerationConfig) -> List[Declarable]:
+ resulting_declarations: List[Declarable] = []
+ property_types: List[str] = []
+ for index, entity in enumerate(entities):
+ entity_location = location + str(index)
+ preprocessor_typename: str = entity.get('$typename')
+ if preprocessor_typename is not None:
+ typename = preprocessor_typename
+ else:
+ defined_type = entity.get('type')
+ if not defined_type.startswith('$defined_'):
+ raise InvalidFieldRepresentationError(entity_location, entity)
+ typename = defined_type.replace('$defined_', '')
+
+ property_type, declarations = type_property_build(dictionary=entity,
+ outer_name=typename,
+ location=entity_location,
+ mode=mode,
+ config=config)
+
+ if isinstance(property_type, StaticString):
+ raise InvalidFieldRepresentationError(entity_location, entity)
+ elif isinstance(property_type, Object):
+ property_types.append(property_type.name)
+ else:
+ property_types.append(typename)
+ resulting_declarations.extend(declarations)
+
+ return [cast(Declarable, EntityEnumeration(name=name,
+ original_name=original_name,
+ include_in_documentation_toc=include_in_documentation_toc,
+ root_entity=root_entity,
+ generate_case_for_templates=generate_case_for_templates,
+ entities=property_types,
+ mode=mode))] + resulting_declarations
+
+
+def entity_enumeration_build(entities: List[Dict[str, any]],
+ name: str,
+ original_name: str,
+ include_in_documentation_toc: bool,
+ root_entity: bool,
+ generate_case_for_templates: bool,
+ location: ElementLocation,
+ config: Config.GenerationConfig) -> List[Declarable]:
+ def make_result(mode: GenerationMode) -> List[Declarable]:
+ return _entity_enumeration_build(entities=entities,
+ name=name,
+ original_name=original_name,
+ include_in_documentation_toc=include_in_documentation_toc,
+ root_entity=root_entity,
+ generate_case_for_templates=generate_case_for_templates,
+ location=location,
+ mode=mode,
+ config=config)
+
+ normal_result: List[Declarable] = make_result(GenerationMode(GenerationMode.NORMAL_WITH_TEMPLATES))
+ template_result: List[Declarable] = []
+ if __generate_templates(config):
+ template_result += make_result(GenerationMode(GenerationMode.TEMPLATE))
+ return normal_result + template_result
+
+
+def entity_build(name: str,
+ dictionary: Dict[str, any],
+ location: ElementLocation,
+ config: Config.GenerationConfig) -> List[Entity]:
+ def make_result(mode: GenerationMode) -> Entity:
+ return Entity(name=name,
+ dictionary=dictionary,
+ location=location,
+ mode=mode,
+ config=config)
+
+ normal: Entity = make_result(mode=GenerationMode.NORMAL_WITH_TEMPLATES)
+ if not normal.generate_as_protocol and __generate_templates(config):
+ return [normal, make_result(mode=GenerationMode.TEMPLATE)]
+ return [normal]
+
+
+def type_property_build(dictionary: Dict[str, any],
+ outer_name: str,
+ location: ElementLocation,
+ mode: GenerationMode,
+ config: Config.GenerationConfig) -> Tuple[PropertyType, List[Declarable]]:
+ name: str = alias(config.lang, dictionary) or outer_name
+ any_of_entities: List[Dict[str, any]] = dictionary.get('anyOf')
+ if is_list_of_type(any_of_entities, Dict) and all(is_dict_with_keys_of_type(d, str) for d in any_of_entities):
+ name = fixing_reserved_typename(name, config.lang)
+ entity_enum: List[Declarable] = _entity_enumeration_build(
+ entities=any_of_entities,
+ name=name,
+ original_name=outer_name,
+ include_in_documentation_toc=dictionary.get('include_in_documentation_toc', False),
+ root_entity=dictionary.get('root_entity', False),
+ generate_case_for_templates=generate_cases_for_templates(config.lang, dictionary),
+ location=location + 'anyOf',
+ mode=mode,
+ config=config)
+ return Object(name=name, object=None, format=ObjectFormat.DEFAULT), entity_enum
+
+ type_value: str = dictionary.get('type')
+ number_constraints: Optional[str] = dictionary.get('constraint')
+
+ if type_value == 'integer':
+ if dictionary.get('format') == 'boolean':
+ return BoolInt(), []
+ return Int(constraint=number_constraints), []
+ elif type_value == 'number':
+ return Double(constraint=number_constraints), []
+ elif type_value == 'boolean':
+ return Bool(), []
+ elif type_value == 'string':
+ return __resolve_string_field(name=name,
+ mode=mode,
+ config=config,
+ location=location,
+ dictionary=dictionary)
+ elif type_value == 'array':
+ items_types: Dict[str, any] = dictionary.get('items')
+ single_name: str = name[:-1] if name.endswith('s') else name
+ min_items: int = dictionary.get('minItems', 1)
+ strict_parsing: bool = dictionary.get('strictParsing', False)
+ property_type, declarations = type_property_build(dictionary=items_types,
+ outer_name=single_name,
+ location=location + 'items',
+ mode=mode,
+ config=config)
+ return Array(property_type=property_type, min_items=min_items, strict_parsing=strict_parsing), declarations
+ elif type_value == 'object':
+ if dictionary.get('additionalProperties', False) and 'properties' not in dictionary:
+ return Dictionary(), []
+ entity: Entity = Entity(name=name,
+ dictionary=dictionary,
+ location=location,
+ mode=mode,
+ config=config)
+ return Object(name=entity.original_name, object=None, format=ObjectFormat.DEFAULT), [entity]
+ else:
+ if not type_value.startswith('$defined_'):
+ raise InvalidFieldRepresentationError(location=location + 'type', value=dictionary)
+ return Object(name=type_value.replace('$defined_', ''), object=None, format=ObjectFormat.DEFAULT), []
+
+
+def property_build(properties: Dict[str, Dict[str, any]],
+ required: Optional[str],
+ location: ElementLocation,
+ mode: GenerationMode,
+ config: Config.GenerationConfig) -> Tuple[List[Property], List[Declarable]]:
+ properties_list: List[Property] = []
+ inner_types_list: List[Declarable] = []
+ for dict_field, dictionary in filter(lambda pair: not code_generation_disabled(config.lang, pair[1]),
+ properties.items()):
+ location_val = location + dict_field
+ property_type, inner_types = type_property_build(dictionary=dictionary,
+ outer_name=dict_field,
+ location=location_val,
+ mode=mode,
+ config=config)
+ default_value: str = property_default_value(lang=config.lang,
+ property_type=property_type,
+ location=location_val,
+ dictionary=dictionary)
+ is_required: bool = dict_field in required if required is not None else False
+ name = alias(lang=config.lang, dictionary=dictionary) or dict_field
+ properties_list.append(Property(name=name,
+ description=dictionary.get('description'),
+ description_translations=dictionary.get('description_translations', {}),
+ dict_field=dict_field,
+ property_type=property_type,
+ optional=not is_required,
+ is_deprecated=dictionary.get('deprecated', False),
+ mode=mode,
+ supports_expressions_flag=dictionary.get('supports_expressions', True),
+ default_value=default_value,
+ platforms=platforms(dictionary)))
+ inner_types_list.extend(inner_types)
+
+ return sorted(properties_list, key=lambda p: isinstance(p.property_type, StaticString)), inner_types_list
diff --git a/api_generator/api_generator/schema/modeling/entities.py b/api_generator/api_generator/schema/modeling/entities.py
new file mode 100644
index 000000000..25e6e901a
--- /dev/null
+++ b/api_generator/api_generator/schema/modeling/entities.py
@@ -0,0 +1,745 @@
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+from typing import List, Optional, Dict, Union, Tuple, cast
+from dataclasses import dataclass
+from enum import Enum, auto
+import validators
+import re
+
+from ..utils import is_dict_with_keys_of_type, is_list_of_type
+from .utils import (
+ alias,
+ fixing_reserved_typename,
+ description_doc
+)
+from ...utils import capitalize_camel_case
+
+from ...config import Config, GeneratedLanguage, GenerationMode, Platform, TEMPLATE_SUFFIX, DescriptionLanguage
+from ..preprocessing.entities import ElementLocation
+from ..preprocessing.errors import UnresolvedReferenceError
+from .errors import GenericError, InvalidFieldRepresentationError
+
+from . import builders
+
+
+class Declarable(ABC):
+ def __init__(self) -> None:
+ self._parent: Optional[Declarable] = None
+
+ @property
+ def parent(self) -> Optional[Entity]:
+ return self._parent
+
+ @parent.setter
+ def parent(self, parent: Entity) -> None:
+ self._parent = parent
+
+ @property
+ def resolved_declaration_prefix(self) -> str:
+ if self.parent is not None:
+ return f'{self.parent.resolved_declaration_prefix}{capitalize_camel_case(self.parent.resolved_name)}.'
+ return ''
+
+ @property
+ def resolved_prefixed_declaration(self) -> str:
+ return self.resolved_declaration_prefix + capitalize_camel_case(self.resolved_name)
+
+ @property
+ def prefixed_declaration(self) -> str:
+ return self.declaration_prefix + capitalize_camel_case(self.name)
+
+ @property
+ def declaration_prefix(self) -> str:
+ if self.parent is not None:
+ return f'{self.parent.declaration_prefix}{capitalize_camel_case(self.parent.name)}.'
+ return ''
+
+ @property
+ def template_declaration_prefix(self) -> str:
+ if self.parent is not None:
+ name = capitalize_camel_case(self.parent.resolved_name + TEMPLATE_SUFFIX)
+ return f'{self.parent.template_declaration_prefix}{name}.'
+ return ''
+
+ @property
+ @abstractmethod
+ def name(self) -> str:
+ pass
+
+ @property
+ @abstractmethod
+ def resolved_name(self) -> str:
+ pass
+
+ @property
+ @abstractmethod
+ def original_name(self) -> str:
+ pass
+
+ @property
+ @abstractmethod
+ def include_in_documentation_toc(self) -> bool:
+ pass
+
+ @property
+ @abstractmethod
+ def as_json(self) -> Dict:
+ pass
+
+ @abstractmethod
+ def resolve_dependencies(self, global_objects: List[Declarable]) -> None:
+ pass
+
+ @abstractmethod
+ def check_dependencies_resolved(self, location: ElementLocation, stack: List[Declarable]) -> None:
+ pass
+
+
+def _super_entities(config: Config.GenerationConfig, dictionary: Dict[str, any]) -> Optional[str]:
+ if config.lang in [GeneratedLanguage.SWIFT, GeneratedLanguage.DOCUMENTATION]:
+ super_entities_key = 'swift_protocols'
+ elif config.lang in [GeneratedLanguage.KOTLIN, GeneratedLanguage.KOTLIN_DSL]:
+ super_entities_key = 'kotlin_interfaces'
+ elif config.lang is GeneratedLanguage.TYPE_SCRIPT:
+ super_entities_key = 'typescript_interfaces'
+ elif config.lang is GeneratedLanguage.PYTHON:
+ super_entities_key = 'python_classes'
+ else:
+ raise NotImplementedError
+
+ super_entities: Optional[str] = dictionary.get(super_entities_key, dictionary.get(f'{super_entities_key}_local'))
+ return super_entities
+
+
+class Entity(Declarable):
+ _implemented_protocol: Optional[Entity]
+ _enclosing_enumeration: Optional[EntityEnumeration] = None
+
+ def __init__(self,
+ name: str,
+ dictionary: Dict[str, any],
+ location: ElementLocation,
+ mode: GenerationMode,
+ config: Config.GenerationConfig):
+ super().__init__()
+ self._super_entities: Optional[str] = _super_entities(config, dictionary)
+ self._resolved_name: str = alias(config.lang, dictionary) or fixing_reserved_typename(name, config.lang)
+ self._name: str = self._resolved_name + mode.name_suffix
+ self._root_entity: bool = dictionary.get('root_entity', False)
+ self._display_name: str = dictionary.get('display_name', name)
+ self._description: str = dictionary.get('description', '')
+ self._description_object: Dict[str, str] = dictionary.get('description_translations', {})
+ self._swift_super_protocol: Optional[str] = dictionary.get('swift_super_protocol')
+ self._include_in_documentation_toc: bool = dictionary.get('include_in_documentation_toc', False)
+ self._original_name: str = name
+ self._generation_mode: GenerationMode = mode
+ self._errors_collector_enabled: bool = not mode.is_template and name in config.errors_collectors
+
+ input_properties: Dict[str, any] = dictionary.get('properties', dict())
+ if not is_dict_with_keys_of_type(value=input_properties, key_type=str):
+ raise GenericError(location=location + 'properties', text='Must have format [String : Any]')
+
+ input_properties_with_dict_values: Dict[str, Dict[str, any]] = input_properties
+ if not all(is_dict_with_keys_of_type(value, str) for value in input_properties_with_dict_values.values()):
+ raise GenericError(location=location + 'properties', text='Must have format [String : [String : Any]]')
+ properties, inner_types = builders.property_build(properties=input_properties_with_dict_values,
+ required=dictionary.get('required'),
+ location=location,
+ mode=mode,
+ config=config)
+
+ self._properties: List[Property] = sorted(properties, key=lambda p: p.name)
+
+ definitions: Optional[Dict[str, Dict[str, any]]] = dictionary.get('definitions')
+ inner_types_from_definitions: List[Declarable] = []
+ if definitions is not None:
+ for name, d in definitions.items():
+ _, defs = builders.type_property_build(dictionary=d,
+ outer_name=name,
+ location=location,
+ mode=mode,
+ config=config)
+ inner_types_from_definitions.extend(defs)
+ prot_names: Optional[Union[List[str], str]] = dictionary.get('protocol_name')
+ if prot_names is not None:
+ if not isinstance(prot_names, List):
+ prot_names = [prot_names]
+ if is_list_of_type(prot_names, str):
+ self._protocol_names: List[str] = list(map(lambda n: n + mode.name_suffix, prot_names))
+ else:
+ self._protocol_names: List[str] = []
+ else:
+ self._protocol_names: List[str] = []
+
+ self._is_deprecated: bool = dictionary.get('deprecated', False)
+ self._type_script_templatable: bool = dictionary.get('typescript_templatable', True)
+ self._generate_swift_optional_args: bool = dictionary.get('generate_swift_optional_arguments', True)
+ self._lang: GeneratedLanguage = config.lang
+ self._inner_types: List[Declarable] = inner_types_from_definitions + inner_types
+ for inner_type in self._inner_types:
+ inner_type.parent = self
+ self.__resolve_property_objects()
+
+ def __str__(self):
+ joined = '\n'.join(map(lambda x: f'\t{self._name} property: {x}', self._properties))
+ return f'Entity "{self._name}":\n{joined}'
+
+ def __repr__(self):
+ return str(self)
+
+ def __resolve_property_objects(self):
+ for ind in range(len(self._properties)):
+ self._properties[ind].property_type = self.__resolve_declaration(self._properties[ind].property_type)
+
+ def __resolve_declaration(self, property_type: PropertyType) -> PropertyType:
+ if isinstance(property_type, Object) and property_type.object is None:
+ result: Optional[Declarable] = self.find_declaration(property_type.name)
+ if result is not None:
+ return Object(name=property_type.name, object=result, format=property_type.format)
+ return self.parent.__resolve_declaration(property_type) if self.parent is not None else property_type
+ elif isinstance(property_type, Array):
+ return Array(property_type=self.__resolve_declaration(property_type.property_type),
+ min_items=property_type.min_items,
+ strict_parsing=property_type.strict_parsing)
+ return property_type
+
+ @property
+ def name(self) -> str:
+ return self._name
+
+ @property
+ def resolved_name(self) -> str:
+ return self._resolved_name
+
+ @property
+ def parent(self) -> Optional[Entity]:
+ return self._parent
+
+ @property
+ def super_entities(self) -> Optional[str]:
+ return self._super_entities
+
+ @property
+ def swift_super_protocol(self) -> Optional[str]:
+ return self._swift_super_protocol
+
+ @property
+ def protocol_plus_super_entities(self) -> Optional[str]:
+ protocols = []
+ if self._implemented_protocol is not None:
+ protocols.append(capitalize_camel_case(self._implemented_protocol.name))
+ protocol_name = self.generation_mode.protocol_name(self._lang, self.resolved_prefixed_declaration)
+ if protocol_name is not None:
+ protocols.append(protocol_name)
+ if self.super_entities is not None:
+ protocols.append(self.super_entities)
+ if not protocols:
+ return None
+ return ', '.join(protocols)
+
+ @property
+ def properties(self) -> List[Property]:
+ return self._properties
+
+ @parent.setter
+ def parent(self, parent: Entity) -> None:
+ self._parent = parent
+ self.__resolve_property_objects()
+ for inner_type in self._inner_types:
+ inner_type.parent = self
+
+ @property
+ def original_name(self) -> str:
+ return self._original_name
+
+ @property
+ def include_in_documentation_toc(self) -> bool:
+ return self._include_in_documentation_toc
+
+ @property
+ def generate_as_protocol(self) -> bool:
+ return self._name in self._protocol_names
+
+ @property
+ def implemented_protocol(self) -> Optional[Entity]:
+ return self._implemented_protocol
+
+ @property
+ def enclosing_enumeration(self) -> Optional[EntityEnumeration]:
+ return self._enclosing_enumeration
+
+ @property
+ def inner_types(self) -> List[Declarable]:
+ return self._inner_types
+
+ @property
+ def generation_mode(self) -> GenerationMode:
+ return self._generation_mode
+
+ @property
+ def instance_properties(self) -> List[Property]:
+ return list(filter(lambda p: not isinstance(p.property_type, StaticString), self._properties))
+
+ @property
+ def all_properties_are_optional_except_default_values(self) -> bool:
+ all_properties_are_optional = all(p.optional for p in self.properties)
+ if not all_properties_are_optional:
+ return False
+ return any(p.default_value is not None for p in self.properties)
+
+ @property
+ def static_type(self) -> Optional[str]:
+ prop = next((p for p in self._properties if p.name == 'type' and isinstance(p.property_type, StaticString)), None)
+ if prop is not None:
+ return cast(StaticString, prop.property_type).value
+ return None
+
+ def description_doc(self, lang: DescriptionLanguage = DescriptionLanguage.EN) -> str:
+ return description_doc(self._description_object, lang, self._description)
+
+ def __resolve_declaration_with_objects(self,
+ property_type: PropertyType,
+ global_objects: List[Declarable]) -> PropertyType:
+ if isinstance(property_type, Object) and property_type.object is None:
+ result = next((d for d in global_objects if property_type.name in [d.name, d.original_name]), None)
+ if result is not None:
+ if isinstance(result, StringEnumeration):
+ return Object(name=property_type.name, object=result, format=property_type.format)
+ else:
+ actual_name = property_type.name + self._generation_mode.name_suffix
+ valid_obj = next(
+ d for d in global_objects if d.name == actual_name or d.original_name == property_type.name)
+ return Object(name=actual_name, object=valid_obj, format=property_type.format)
+ return property_type
+ elif isinstance(property_type, Array):
+ return Array(
+ property_type=self.__resolve_declaration_with_objects(property_type=property_type.property_type,
+ global_objects=global_objects),
+ min_items=property_type.min_items,
+ strict_parsing=property_type.strict_parsing)
+ return property_type
+
+ def resolve_dependencies(self, global_objects: List[Declarable]) -> None:
+ for i in range(len(self._properties)):
+ self._properties[i].property_type = self.__resolve_declaration_with_objects(
+ property_type=self._properties[i].property_type,
+ global_objects=global_objects)
+ for inner_type in self._inner_types:
+ inner_type.resolve_dependencies(global_objects)
+
+ self._implemented_protocol = next((cast(Entity, d) for d in global_objects if d.name in self._protocol_names),
+ None)
+
+ if self._lang is GeneratedLanguage.KOTLIN_DSL:
+ new_enumeration = None
+ for enumeration in global_objects:
+ if isinstance(enumeration, EntityEnumeration):
+ valid_names = [self._name, self._resolved_name, self._original_name]
+ new_enumeration = next((ent for ent in enumeration.entities if ent[0] in valid_names), None)
+ if new_enumeration is not None:
+ break
+ self._enclosing_enumeration = new_enumeration
+
+ def check_dependencies_resolved(self, location: ElementLocation, stack: List[Declarable]) -> None:
+ if self in stack:
+ return
+ for p in self._properties:
+ name: Optional[str] = None
+ declarable: Optional[Declarable] = None
+ if isinstance(p.property_type, Object):
+ name = p.property_type.name
+ declarable = p.property_type.object
+ elif isinstance(p.property_type, Array) and isinstance(p.property_type.property_type, Object):
+ name = p.property_type.property_type.name
+ declarable = p.property_type.property_type.object
+ if name is not None:
+ if declarable is None:
+ raise UnresolvedReferenceError(location=location,
+ object_name=self._name,
+ field_name=p.dict_field,
+ unresolved_typename=name)
+ if declarable is self:
+ return
+
+ declarable.check_dependencies_resolved(location=location,
+ stack=stack + [self])
+
+ @property
+ def as_json(self) -> Dict:
+ return {
+ 'type': 'entity',
+ 'name': self._name,
+ 'properties': list(map(lambda p: p.as_json, self._properties))
+ }
+
+ def find_declaration(self, name: str) -> Optional[Declarable]:
+ if name in [self._name, self._original_name]:
+ return self
+ result = next(
+ (d for d in self._inner_types if name in [d.name, d.resolved_name, d.original_name]),
+ None)
+ if result is not None:
+ return result
+ return self.parent.find_declaration(name) if self.parent is not None else None
+
+
+class EntityEnumeration(Declarable):
+ def __init__(self,
+ name: str,
+ original_name: str,
+ include_in_documentation_toc: bool,
+ root_entity: bool,
+ generate_case_for_templates: bool,
+ entities: List[str],
+ mode: GenerationMode) -> None:
+ super().__init__()
+ self._resolved_name: str = name
+ self._name: str = self._resolved_name + mode.name_suffix
+ self._original_name: str = original_name
+ self._include_in_documentation_toc: bool = include_in_documentation_toc
+ self._root_entity: bool = root_entity
+ self._generate_case_for_templates: bool = generate_case_for_templates
+ self._resolved_entity_names: List[str] = entities
+ self._entities: List[Tuple[str, Optional[Declarable]]] = list(map(
+ lambda entity: (entity + mode.name_suffix, None),
+ entities
+ ))
+ self._mode = mode
+
+ @property
+ def name(self) -> str:
+ return self._name
+
+ @property
+ def resolved_name(self) -> str:
+ return self._resolved_name
+
+ @property
+ def original_name(self) -> str:
+ return self._original_name
+
+ @property
+ def include_in_documentation_toc(self) -> bool:
+ return self._include_in_documentation_toc
+
+ @property
+ def parent(self) -> Optional[Entity]:
+ return self._parent
+
+ @property
+ def mode(self) -> GenerationMode:
+ return self._mode
+
+ @parent.setter
+ def parent(self, parent: Entity) -> None:
+ self._parent = parent
+ new_entities: List[Tuple[str, Optional[Declarable]]] = []
+ for name, obj in self._entities:
+ new_obj = obj
+ if new_obj is None and self.parent is not None:
+ new_obj = self.parent.find_declaration(name)
+ new_name = name
+ if new_obj is not None:
+ new_name = new_obj.name
+ new_entities.append((new_name, new_obj))
+ self._entities = new_entities
+
+ @property
+ def entities(self) -> List[Tuple[str, Optional[Declarable]]]:
+ return self._entities
+
+ @property
+ def entity_names(self) -> List[str]:
+ return list(map(lambda x: x[0], self._entities))
+
+ def resolve_dependencies(self, global_objects: List[Declarable]) -> None:
+ new_entities: List[Tuple[str, Optional[Declarable]]] = []
+ for name, obj in self._entities:
+ new_obj = obj or next((d for d in global_objects if name in [d.name, d.resolved_name, d.original_name]),
+ None)
+ new_name = name
+ if new_obj is not None:
+ new_name = new_obj.name
+ new_entities.append((new_name, new_obj))
+ self._entities = new_entities
+
+ def check_dependencies_resolved(self, location: ElementLocation, stack: List[Declarable]) -> None:
+ if self in stack:
+ return
+ for _, entity in self._entities:
+ if entity is not None:
+ entity.check_dependencies_resolved(location=location + self._name,
+ stack=stack + [self])
+
+ @property
+ def as_json(self) -> Dict:
+ return {
+ 'type': 'entity_enumeration',
+ 'resolved_name': self._resolved_name,
+ 'entities': list(map(lambda x: {
+ 'name': x[0],
+ 'declarable': x[1].as_json if x[1] is not None else 'None'
+ }, self._entities))
+ }
+
+ def common_interface(self, lang: GeneratedLanguage) -> Optional[str]:
+ common_interface = self._common_interface_without_serializable
+ if lang is GeneratedLanguage.SWIFT:
+ if common_interface is not None:
+ common_interface = f' & {common_interface}'
+ else:
+ common_interface = ''
+ return f'Serializable{common_interface}'
+ return common_interface
+
+ @property
+ def _common_interface_without_serializable(self) -> Optional[str]:
+ if self.entities:
+ interface: Optional[Entity] = cast(
+ Optional[Entity],
+ next(
+ (cast(Entity, e[1]).implemented_protocol for e in self.entities if isinstance(e[1], Entity)),
+ None
+ )
+ )
+ if interface is not None:
+ for entity in self.entities:
+ other_interface = entity[1]
+ if isinstance(other_interface, Entity) and other_interface.implemented_protocol is not interface:
+ return None
+ return capitalize_camel_case(interface.name)
+ return None
+
+ @property
+ def generate_case_for_templates(self) -> bool:
+ return self._generate_case_for_templates
+
+
+class StringEnumeration(Declarable):
+ def __init__(self,
+ name: str,
+ original_name: str,
+ cases: Union[List[str], List[Tuple[str, str]]],
+ include_documentation_toc: bool) -> None:
+ super().__init__()
+ self._name: str = name
+ self._original_name: str = original_name
+ if is_list_of_type(cases, str):
+ self._cases: List[Tuple[str, str]] = list(map(lambda case: (case, case), cases))
+ else:
+ self._cases: List[Tuple[str, str]] = cases
+ self._include_documentation_toc: bool = include_documentation_toc
+
+ @property
+ def name(self) -> str:
+ return self._name
+
+ @property
+ def original_name(self) -> str:
+ return self._original_name
+
+ @property
+ def resolved_name(self) -> str:
+ return self._name
+
+ @property
+ def cases(self) -> List[Tuple[str, str]]:
+ return self._cases
+
+ @property
+ def include_in_documentation_toc(self) -> bool:
+ return self._include_documentation_toc
+
+ def resolve_dependencies(self, global_objects: List[Declarable]) -> None:
+ pass
+
+ def check_dependencies_resolved(self, location: ElementLocation, stack: List[Declarable]) -> None:
+ pass
+
+ @property
+ def as_json(self) -> Dict:
+ return {
+ 'type': 'string_enumeration',
+ 'name': self._name,
+ 'cases': list(map(lambda case: case[1], self._cases))
+ }
+
+
+def default_value(lang: GeneratedLanguage,
+ property_type: PropertyType,
+ location: ElementLocation,
+ dictionary: Dict[str, any]) -> Optional[str]:
+ generic_key = 'default_value'
+ specific_prefix = ''
+ if lang is GeneratedLanguage.KOTLIN:
+ specific_prefix = 'android_'
+ elif lang is GeneratedLanguage.KOTLIN_DSL:
+ specific_prefix = 'kotlin_'
+ elif lang in [GeneratedLanguage.SWIFT, GeneratedLanguage.DOCUMENTATION]:
+ specific_prefix = 'ios_'
+ elif lang is GeneratedLanguage.TYPE_SCRIPT:
+ specific_prefix = 'typescript_'
+ elif lang is GeneratedLanguage.PYTHON:
+ specific_prefix = 'python_'
+ specific_key = specific_prefix + generic_key
+ result: Optional[str] = dictionary.get(specific_key, dictionary.get(generic_key))
+ if isinstance(result, str):
+ property_type.validate(value=result, location=location + specific_key)
+ return result
+ return None
+
+
+class PropertyType(ABC):
+ @property
+ def supports_expressions(self) -> bool:
+ if isinstance(self, (Int, Double, Bool, BoolInt, String, Color, Url)):
+ return True
+ elif isinstance(self, (Dictionary, StaticString)):
+ return False
+ elif isinstance(self, Array):
+ return self.property_type.supports_expressions
+ elif isinstance(self, Object):
+ return isinstance(self.object, StringEnumeration)
+
+ def validate(self, value: str, location: ElementLocation) -> None:
+ error = InvalidFieldRepresentationError(location, value)
+ if isinstance(self, Url):
+ if not validators.url(value):
+ raise error
+ elif isinstance(self, Color):
+ color_matcher = re.compile(r'^#([0-9a-fA-F]{2}){3,4}$')
+ if not color_matcher.fullmatch(value):
+ raise error
+
+ @property
+ def can_be_templated(self) -> bool:
+ if isinstance(self, Array):
+ return self.property_type.can_be_templated
+ elif isinstance(self, Object):
+ return not isinstance(self.object, StringEnumeration)
+ else:
+ return False
+
+ @property
+ def as_json(self) -> Dict:
+ if isinstance(self, (Int, Double, Bool, BoolInt, String, StaticString, Color, Url, Dictionary)):
+ return {
+ 'value': str(type(self).__name__)
+ }
+ elif isinstance(self, Array):
+ return {
+ 'type': 'array',
+ 'item_type': self.property_type.as_json
+ }
+ elif isinstance(self, Object):
+ return {
+ 'type': 'object',
+ 'name': self.name,
+ 'obj': self.object.as_json if self.object is not None else 'None'
+ }
+
+
+@dataclass
+class Int(PropertyType):
+ constraint: Optional[str]
+
+
+@dataclass
+class Double(PropertyType):
+ constraint: Optional[str]
+
+
+@dataclass
+class Bool(PropertyType):
+ pass
+
+
+@dataclass
+class BoolInt(PropertyType):
+ pass
+
+
+@dataclass
+class String(PropertyType):
+ min_length: int
+ formatted: bool
+ regex: Optional[re.Pattern]
+ enable_optimization: bool
+
+
+@dataclass
+class StaticString(PropertyType):
+ value: str
+
+
+@dataclass
+class Color(PropertyType):
+ pass
+
+
+@dataclass
+class Url(PropertyType):
+ schemes: Optional[List[str]]
+
+
+@dataclass
+class Dictionary(PropertyType):
+ pass
+
+
+@dataclass
+class Array(PropertyType):
+ property_type: PropertyType
+ min_items: int
+ strict_parsing: bool
+
+
+class ObjectFormat(Enum):
+ DEFAULT = auto()
+ JSON_STRING = auto()
+
+
+@dataclass
+class Object(PropertyType):
+ name: str
+ object: Optional[Declarable]
+ format: ObjectFormat
+
+
+@dataclass
+class Property:
+ name: str
+ description: Optional[str]
+ description_translations: Dict[str, str]
+ dict_field: str
+ property_type: PropertyType
+ optional: bool
+ is_deprecated: bool
+ mode: GenerationMode
+ supports_expressions_flag: bool
+ default_value: Optional[str]
+ platforms: Optional[List[Platform]]
+
+ def __str__(self) -> str:
+ return f'Property(name={self.name}, type={self.property_type})'
+
+ def __repr__(self) -> str:
+ return str(self)
+
+ @property
+ def supports_expressions(self) -> bool:
+ if self.property_type.supports_expressions:
+ return self.supports_expressions_flag
+ return False
+
+ @property
+ def as_json(self) -> Dict:
+ return {
+ 'type': 'property',
+ 'name': self.name,
+ 'property_type': self.property_type.as_json
+ }
+
+ def description_doc(self, lang: DescriptionLanguage = DescriptionLanguage.EN) -> str:
+ return description_doc(self.description_translations, lang, self.description)
diff --git a/api_generator/api_generator/schema/modeling/errors.py b/api_generator/api_generator/schema/modeling/errors.py
new file mode 100644
index 000000000..d378d64e8
--- /dev/null
+++ b/api_generator/api_generator/schema/modeling/errors.py
@@ -0,0 +1,23 @@
+from ..preprocessing.entities import ElementLocation
+
+
+class InvalidFieldRepresentationError(Exception):
+ def __init__(self, location: ElementLocation, value: any):
+ self.location = location
+ self.value = value
+ message = f'Error {location}: invalid value "{value}"'
+ super(InvalidFieldRepresentationError, self).__init__(message)
+
+
+class GenericError(Exception):
+ def __init__(self, location: ElementLocation, text: str):
+ self.location = location
+ self.text = text
+ message = f'Error {location}: "{text}"'
+ super(GenericError, self).__init__(message)
+
+
+class UnsupportedFormatTypeError(Exception):
+ def __init__(self):
+ message = 'Only object type is supported as a complex string format'
+ super(UnsupportedFormatTypeError, self).__init__(message)
diff --git a/api_generator/api_generator/schema/modeling/modeler.py b/api_generator/api_generator/schema/modeling/modeler.py
new file mode 100644
index 000000000..b8fbfd9b7
--- /dev/null
+++ b/api_generator/api_generator/schema/modeling/modeler.py
@@ -0,0 +1,74 @@
+from typing import List, Dict, Tuple, Optional
+
+from ...config import Config
+from ..utils import is_list_of_type, is_dict_with_keys_of_type
+from ..preprocessing.entities import SchemaDirectory, SchemaFile, ElementLocation
+
+from . import builders
+from .entities import Declarable, StringEnumeration
+from .utils import (
+ alias,
+ generate_cases_for_templates
+)
+
+
+def __generate_objects(file: SchemaFile, config: Config.GenerationConfig) -> List[Declarable]:
+ contents: Dict[str, any] = file.contents
+ name: str = file.name.replace('.json', '')
+ type_value: Optional[str] = contents.get('type')
+ location = ElementLocation(file)
+ if type_value == 'object':
+ return builders.entity_build(name=name,
+ dictionary=contents,
+ location=location,
+ config=config)
+ elif type_value == 'string' and contents.get('enum') is not None:
+ cases: List[str] = contents.get('enum')
+ if not is_list_of_type(cases, str):
+ raise TypeError(f'Enum at {file.name} must have format [String]')
+ return [StringEnumeration(name=alias(config.lang, contents) or name,
+ original_name=name,
+ cases=cases,
+ include_documentation_toc=contents.get('include_in_documentation_toc', False))]
+ elif contents.get('anyOf') is not None:
+ entities: List[Dict[str, any]] = contents.get('anyOf')
+ if not (is_list_of_type(entities, Dict) and all(is_dict_with_keys_of_type(entity, str) for entity in entities)):
+ raise TypeError('AnyOf must have format [[String : Any]]')
+ return builders.entity_enumeration_build(
+ entities=entities,
+ name=alias(config.lang, contents) or name,
+ original_name=name,
+ include_in_documentation_toc=contents.get('include_in_documentation_toc', False),
+ root_entity=contents.get('root_entity', False),
+ generate_case_for_templates=generate_cases_for_templates(config.lang, contents),
+ location=location,
+ config=config
+ )
+ return []
+
+
+def __build_objects(schema_dir: SchemaDirectory,
+ config: Config.GenerationConfig) -> List[Tuple[ElementLocation, Declarable]]:
+ result: List[Tuple[ElementLocation, Declarable]] = []
+ for item in schema_dir.items:
+ if isinstance(item, SchemaFile):
+ file: SchemaFile = item
+ objects: List[Declarable] = __generate_objects(file, config)
+ result.extend(map(lambda obj: (ElementLocation(file), obj), objects))
+ elif isinstance(item, SchemaDirectory):
+ result.extend(__build_objects(item, config))
+ return result
+
+
+def build_objects(schema_dir: SchemaDirectory, config: Config.GenerationConfig) -> List[Declarable]:
+ result: List[Tuple[ElementLocation, Declarable]] = __build_objects(schema_dir, config)
+
+ def get_entities() -> List[Declarable]:
+ return list(map(lambda pair: pair[1], result))
+
+ for _, entity in result:
+ entity.resolve_dependencies(global_objects=get_entities())
+ for location, entity in result:
+ entity.check_dependencies_resolved(location=location, stack=[])
+
+ return get_entities()
diff --git a/api_generator/api_generator/schema/modeling/text.py b/api_generator/api_generator/schema/modeling/text.py
new file mode 100644
index 000000000..c5ed5af46
--- /dev/null
+++ b/api_generator/api_generator/schema/modeling/text.py
@@ -0,0 +1,54 @@
+from __future__ import annotations
+from typing import Optional, Union, List
+from ..utils import is_list_of_type
+from ...utils import indented
+
+
+class Text:
+ def __init__(self, init_lines: Optional[Union[str, List[str]]] = None, indent_width: int = 0):
+ self._lines = []
+ self._indent_width = indent_width
+ if init_lines is not None:
+ if isinstance(init_lines, str):
+ self._lines.append(init_lines)
+ elif is_list_of_type(init_lines, str):
+ self._lines.extend(init_lines)
+ else:
+ raise TypeError
+
+ def __str__(self):
+ def transform(string: str) -> str:
+ if not string or string.isspace():
+ return ''
+ else:
+ return indented(string, indent_width=self.indent_width)
+ return '\n'.join(map(transform, self._lines))
+
+ def __add__(self, other) -> Text:
+ if isinstance(other, str):
+ return Text(init_lines=self._lines + [other], indent_width=self.indent_width)
+ elif is_list_of_type(other, str):
+ return Text(init_lines=self._lines + other, indent_width=self.indent_width)
+ elif is_list_of_type(other, Text):
+ sum_lines = self._lines.copy()
+ for other_text in other:
+ sum_lines.append(str(other_text))
+ return Text(init_lines=sum_lines, indent_width=self.indent_width)
+ elif isinstance(other, Text):
+ return Text(init_lines=self._lines + [str(other)], indent_width=self.indent_width)
+ else:
+ raise TypeError
+
+ def indented(self, level: int = 1, indent_width: int = 2) -> Text:
+ return Text(init_lines=self._lines, indent_width=self._indent_width + level * indent_width)
+
+ @property
+ def indent_width(self) -> int:
+ return self._indent_width
+
+ @property
+ def lines(self) -> List[str]:
+ return self._lines
+
+
+EMPTY: Text = Text('')
diff --git a/api_generator/api_generator/schema/modeling/utils.py b/api_generator/api_generator/schema/modeling/utils.py
new file mode 100644
index 000000000..9d46a4136
--- /dev/null
+++ b/api_generator/api_generator/schema/modeling/utils.py
@@ -0,0 +1,33 @@
+from typing import Dict, Optional, List
+
+from ...config import GeneratedLanguage, Platform, DescriptionLanguage
+from ..utils import get_value_with_optional_by_lang
+
+
+def generate_cases_for_templates(lang: GeneratedLanguage, dictionary: Dict[str, any]) -> bool:
+ return get_value_with_optional_by_lang('generate_case_for_templates', lang, dictionary) or False
+
+
+def alias(lang: GeneratedLanguage, dictionary: Dict[str, any]) -> Optional[str]:
+ return get_value_with_optional_by_lang('alias', lang, dictionary)
+
+
+def fixing_reserved_typename(typename: str, lang: GeneratedLanguage) -> str:
+ if lang is GeneratedLanguage.SWIFT:
+ return 'kind' if typename.lower() == 'type' else typename
+ else:
+ return typename
+
+
+def platforms(dictionary: Dict[str, any]) -> Optional[List[Platform]]:
+ platforms_list: List[str] = dictionary.get('platforms')
+ if platforms_list is None:
+ return None
+ return list(map(lambda raw: Platform[raw.upper()], platforms_list))
+
+
+def description_doc(description_translations: Dict[str, str], lang: DescriptionLanguage, description: str) -> str:
+ try:
+ return description_translations[lang.value]
+ except KeyError:
+ return description
diff --git a/api_generator/api_generator/schema/preprocessing/__init__.py b/api_generator/api_generator/schema/preprocessing/__init__.py
new file mode 100644
index 000000000..04a975630
--- /dev/null
+++ b/api_generator/api_generator/schema/preprocessing/__init__.py
@@ -0,0 +1,3 @@
+from .preprocessor import schema_preprocessing
+
+__all__ = [schema_preprocessing]
diff --git a/api_generator/api_generator/schema/preprocessing/entities.py b/api_generator/api_generator/schema/preprocessing/entities.py
new file mode 100644
index 000000000..b31c17f1e
--- /dev/null
+++ b/api_generator/api_generator/schema/preprocessing/entities.py
@@ -0,0 +1,292 @@
+from __future__ import annotations
+from abc import ABC, abstractmethod
+from typing import Dict, List, Optional
+import os
+import json
+
+from ...config import GeneratedLanguage
+from . import utils
+from . import errors
+from . import preprocessor
+
+
+class SchemaFilesystemItem(ABC):
+
+ def __str__(self) -> str:
+ return json.dumps(
+ obj=self.as_json,
+ default=lambda x: x.as_json if isinstance(x, SchemaFilesystemItem) else str(x),
+ indent=2
+ )
+
+ @property
+ @abstractmethod
+ def parent_dir(self) -> SchemaFilesystemItem:
+ pass
+
+ @property
+ @abstractmethod
+ def name(self) -> str:
+ pass
+
+ @property
+ @abstractmethod
+ def has_unresolved_references(self) -> bool:
+ pass
+
+ @abstractmethod
+ def merge_all_ofs(self, lang: GeneratedLanguage):
+ pass
+
+ @abstractmethod
+ def resolve_references(self, lang: GeneratedLanguage):
+ pass
+
+ @abstractmethod
+ def clean_unused_definitions(self):
+ pass
+
+ @property
+ @abstractmethod
+ def as_json(self) -> Dict[str, any]:
+ pass
+
+
+class SchemaDirectory(SchemaFilesystemItem):
+ def __init__(self, path: str, parent_dir: Optional[SchemaDirectory], lang: GeneratedLanguage):
+ self._name: str = os.path.basename(os.path.normpath(path))
+ self._parent_dir: SchemaDirectory = parent_dir
+ self._items: List[SchemaFilesystemItem] = list(
+ filter(
+ lambda schema_file: not (isinstance(schema_file, SchemaFile) and utils.code_generation_disabled(
+ lang=lang,
+ dictionary=schema_file.contents
+ )),
+ map(
+ lambda content: preprocessor.internal_resolve_structure(
+ path=f'{path}/{content}',
+ lang=lang,
+ parent_dir=self
+ ),
+ filter(
+ lambda file: file.endswith('.json') or '.' not in file,
+ os.listdir(path)
+ )
+ )
+ )
+ )
+
+ @property
+ def as_json(self) -> Dict[str, any]:
+ return {self._name: list(map(lambda i: i.as_json, self._items))}
+
+ @property
+ def parent_dir(self) -> SchemaDirectory:
+ return self._parent_dir
+
+ @property
+ def name(self) -> str:
+ return self._name
+
+ @property
+ def items(self) -> List[SchemaFilesystemItem]:
+ return self._items
+
+ @property
+ def has_unresolved_references(self) -> bool:
+ for item in self._items:
+ if item.has_unresolved_references:
+ return True
+ return False
+
+ def merge_all_ofs(self, lang: GeneratedLanguage):
+ for item in self._items:
+ item.merge_all_ofs(lang)
+
+ def resolve_references(self, lang: GeneratedLanguage):
+ while self.has_unresolved_references:
+ for item in self._items:
+ item.resolve_references(lang)
+
+ def clean_unused_definitions(self):
+ for item in self._items:
+ item.clean_unused_definitions()
+
+ def resolve(self, relative_path: str) -> SchemaFilesystemItem:
+ components = list(filter(lambda comp: comp, relative_path.split('/')))
+
+ if len(components) < 1:
+ raise errors.InvalidReferenceError
+ first_component = components[0]
+
+ item: SchemaFilesystemItem
+ if first_component == '..':
+ if self._parent_dir is None:
+ raise errors.InvalidReferenceError
+ item = self._parent_dir
+ else:
+ filtered_items = list(filter(lambda it: it.name == first_component, self._items))
+ if len(filtered_items) < 1:
+ raise errors.InvalidReferenceError
+ item = filtered_items[0]
+
+ if len(components) == 1:
+ return item
+
+ if not isinstance(item, SchemaDirectory):
+ raise errors.InvalidReferenceError
+
+ return item.resolve(relative_path='/'.join(components[1::]))
+
+
+class SchemaFile(SchemaFilesystemItem):
+ def __init__(self, name: str, parent_dir: Optional[SchemaDirectory], contents: Dict[str, any]):
+ self._name: str = name
+ self._parent_dir: Optional[SchemaDirectory] = parent_dir
+ self._contents: Dict[str, any] = contents
+
+ @property
+ def as_json(self) -> Dict[str, any]:
+ return {self._name: self._contents}
+
+ @property
+ def parent_dir(self) -> Optional[SchemaDirectory]:
+ return self._parent_dir
+
+ @property
+ def name(self) -> str:
+ return self._name
+
+ @property
+ def contents(self) -> Dict[str, any]:
+ return self._contents
+
+ @property
+ def has_unresolved_references(self) -> bool:
+ return preprocessor.has_unresolved_references(self._contents)
+
+ def resolve_references(self, lang: GeneratedLanguage):
+ self._contents = preprocessor.resolve_references(
+ dictionary=self._contents,
+ location=ElementLocation(file=self),
+ lang=lang
+ )
+
+ def merge_all_ofs(self, lang: GeneratedLanguage):
+ self._contents = preprocessor.merge_all_ofs(
+ dictionary=self._contents,
+ lang=lang,
+ location=ElementLocation(file=self)
+ )
+
+ def clean_unused_definitions(self):
+ self._contents = preprocessor.clean_unused_definitions(self._contents)
+
+
+class ElementLocation:
+ def __init__(self, file: SchemaFile, path: List[str] = None):
+ self._file: SchemaFile = file
+ self._path: List[str] = path or []
+
+ def __add__(self, other):
+ assert isinstance(other, str)
+ return ElementLocation(self._file, self._path + [other])
+
+ def __str__(self) -> str:
+ joined_path = '.'.join(self._path)
+ return f'in {self._file.name} at "{joined_path}"'
+
+ def __repr__(self):
+ return str(self)
+
+ @property
+ def file(self) -> SchemaFile:
+ return self._file
+
+ @property
+ def path(self) -> List[str]:
+ return self._path
+
+
+class Reference:
+ def __init__(self, location: ElementLocation, ref: str):
+ prefix: str
+ self._file: SchemaFile
+ self._path: List[str]
+ self._value: Dict[str, any]
+
+ prefix, self._file, self._path = utils.get_full_reference_location(location, ref)
+
+ definition = preprocessor.prepend_prefix_to_references(
+ prefix=prefix,
+ dictionary=utils.enclosed_dict_for(
+ keys=self._path,
+ dictionary=self._file.contents
+ )
+ )
+
+ def_type: Optional[str] = definition.get('type')
+ is_obj = def_type == 'object'
+ is_str_enum = def_type == 'string' and definition.get('enum') is not None
+ is_entity_enum = definition.get('anyOf') is not None
+ is_top_level_entity = def_type.startswith('$defined_') if isinstance(def_type, str) else False
+ is_referenced_from_top_level = len(location.path) == 0
+ should_inline = is_referenced_from_top_level or not (
+ is_obj or is_str_enum or is_entity_enum or is_top_level_entity)
+
+ if not self._path and not should_inline:
+ file_first_comp = self._file.name.split('.')[0]
+ self._value = {'type': f'$defined_{file_first_comp}'}
+ return
+
+ if def_type is None and not is_entity_enum:
+ self._value = definition
+ return
+
+ inlining_suppressed = definition.get('suppress_inline', False)
+ is_unique_reference_in_same_file: bool
+ if self._file == location.file:
+ obj_name = self._path[-1] if self._path else '#'
+ num_of_refs = utils.number_of_references(obj_name=obj_name, dictionary=location.file.contents)
+ is_unique_reference_in_same_file = num_of_refs == 1
+ else:
+ is_unique_reference_in_same_file = False
+ top_level_entity_name = self._file.name.replace('.json', '')
+
+ if is_unique_reference_in_same_file and not inlining_suppressed:
+ typename_value = self.path[-1] if self._path else top_level_entity_name
+ definition |= {'$typename': typename_value}
+ self._value = definition
+ elif should_inline:
+ self._value = definition
+ else:
+ type_value = self.path[-1] if self._path else top_level_entity_name
+ self._value = {'type': f'$defined_{type_value}'}
+
+ def __eq__(self, other) -> bool:
+ assert isinstance(other, Reference)
+ return self.file == other.file and self.path == other.path
+
+ @property
+ def file(self) -> SchemaFile:
+ return self._file
+
+ @property
+ def path(self) -> List[str]:
+ return self._path
+
+ @property
+ def value(self) -> Dict[str, any]:
+ return self._value
+
+
+class DescriptionReference:
+ def __init__(self, location: ElementLocation, ref: str):
+ file: SchemaFile
+ path: List[str]
+ _, file, path = utils.get_full_reference_location(location, ref)
+ self._value = utils.enclosed_dict_for(keys=path, dictionary=file.contents)
+
+ @property
+ def value(self) -> Dict[str, str]:
+ return self._value
diff --git a/api_generator/api_generator/schema/preprocessing/errors.py b/api_generator/api_generator/schema/preprocessing/errors.py
new file mode 100644
index 000000000..10b0c9794
--- /dev/null
+++ b/api_generator/api_generator/schema/preprocessing/errors.py
@@ -0,0 +1,68 @@
+from . import entities
+
+
+class FileAtInputPathError(Exception):
+ def __init__(self, path: str):
+ self.path = path
+ message = f'Found file at input path {path}'
+ super(FileAtInputPathError, self).__init__(message)
+
+
+class PathNotFoundError(Exception):
+ def __init__(self, path: str):
+ self.path = path
+ message = f'Could not find file or directory at path {path}'
+ super(PathNotFoundError, self).__init__(message)
+
+
+class NonDictionaryContentError(Exception):
+ def __init__(self, path: str):
+ self.path = path
+ message = f'Root JSON object in file at {path} is not an object'
+ super(NonDictionaryContentError, self).__init__(message)
+
+
+class InvalidReferenceError(Exception):
+ def __init__(self):
+ message = 'Could not resolve some reference'
+ super(InvalidReferenceError, self).__init__(message)
+
+
+class InvalidFileReferenceError(Exception):
+ def __init__(self, location: entities.ElementLocation, reference: str):
+ self.location: entities.ElementLocation = location
+ self.reference: str = reference
+ message = f'Could not resolve reference {reference} {location}'
+ super(InvalidFileReferenceError, self).__init__(message)
+
+
+class UnsupportedCircularReferenceError(Exception):
+ def __init__(self, description: str):
+ self.description: str = description
+ message = f'Unsupported circular reference: {description}\nOnly circular references to classes are allowed'
+ super(UnsupportedCircularReferenceError, self).__init__(message)
+
+
+class AmbiguousMergeError(Exception):
+ def __init__(self, location: entities.ElementLocation):
+ self.location: entities.ElementLocation = location
+ message = f'Could not merge objects {location}'
+ super(AmbiguousMergeError, self).__init__(message)
+
+
+class InvalidAllOfError(Exception):
+ def __init__(self, location: entities.ElementLocation):
+ self.location: entities.ElementLocation = location
+ message = f'Value of allOf key is not an array of objects {location}'
+ super(InvalidAllOfError, self).__init__(message)
+
+
+class UnresolvedReferenceError(Exception):
+ def __init__(self, location: entities.ElementLocation, object_name: str, field_name: str, unresolved_typename: str):
+ self.location: entities.ElementLocation = location
+ self.object_name: str = object_name
+ self.field_name: str = field_name
+ self.unresolved_typename: str = unresolved_typename
+ message = f'Property of "{object_name}" with name "{field_name}" with type "{unresolved_typename}" ' \
+ f'is not defined {location}. Did you make a reference to non-root complex type?'
+ super(UnresolvedReferenceError, self).__init__(message)
diff --git a/api_generator/api_generator/schema/preprocessing/preprocessor.py b/api_generator/api_generator/schema/preprocessing/preprocessor.py
new file mode 100644
index 000000000..159eafd55
--- /dev/null
+++ b/api_generator/api_generator/schema/preprocessing/preprocessor.py
@@ -0,0 +1,217 @@
+from __future__ import annotations
+from typing import Dict, List, Optional
+from copy import deepcopy
+import os
+import json
+
+from ...config import Config, GeneratedLanguage
+from ..utils import code_generation_disabled
+from .utils import is_list_of_type, Key, Value, number_of_references
+from . import errors
+from . import entities
+
+
+def has_unresolved_references(dictionary: Dict) -> bool:
+ if isinstance(dictionary.get('$ref'), str):
+ return True
+ if isinstance(dictionary.get('$description'), str):
+ return True
+ for value in dictionary.values():
+ if isinstance(value, Dict) and has_unresolved_references(value):
+ return True
+ elif is_list_of_type(value, Dict):
+ for inner_dict in value:
+ if has_unresolved_references(inner_dict):
+ return True
+ return False
+
+
+def prepend_prefix_to_references(prefix: str, dictionary: Dict[Key, any]) -> Dict[Key, Value]:
+ if not prefix:
+ return dictionary
+ result = deepcopy(dictionary)
+ reference = result.get('$ref')
+ if isinstance(reference, str):
+ new_value = prefix + reference
+ if type(new_value) is not Value:
+ raise errors.InvalidReferenceError
+ if not reference.startswith('#/'):
+ result['$ref'] = new_value
+ else:
+ for key, value in dictionary.items():
+ if isinstance(value, Dict):
+ result[key] = prepend_prefix_to_references(prefix, value)
+ elif is_list_of_type(value, list_type=Dict):
+ result[key] = list(map(lambda element: prepend_prefix_to_references(prefix, element), value))
+ return result
+
+
+def __merge(d1: Dict[str, any], d2: Dict[str, any], location: entities.ElementLocation):
+ result = deepcopy(d1)
+ other = d2
+ result.pop('allOf', None)
+ for key in other:
+ if key in result:
+ error = errors.AmbiguousMergeError(location=location + key)
+ result_val, other_val = result[key], other[key]
+ if isinstance(result_val, Dict) and isinstance(other_val, Dict):
+ result[key] = __merge(result_val, other_val, location=location + key)
+
+ elif is_list_of_type(result_val, str) and is_list_of_type(other_val, str):
+ if set(result_val) & set(other_val):
+ raise error
+
+ result[key] = result_val + other_val
+ else:
+ raise error
+ else:
+ result[key] = other[key]
+
+ return result
+
+
+def merge_all_ofs(dictionary: Dict[str, any], lang: GeneratedLanguage, location: entities.ElementLocation
+ ) -> Dict[str, any]:
+ result = deepcopy(dictionary)
+ for key in result:
+ value = dictionary.get(key)
+ if isinstance(value, Dict):
+ result[key] = merge_all_ofs(dictionary=value, lang=lang, location=location + key)
+ elif is_list_of_type(value, Dict):
+ result[key] = list(map(
+ lambda element: merge_all_ofs(dictionary=element, lang=lang, location=location + key),
+ value
+ ))
+ if 'allOf' in result:
+ if not isinstance(result['allOf'], List):
+ raise errors.InvalidAllOfError(location)
+ dicts_to_merge: List[Dict[str, any]] = list(filter(
+ lambda element: not code_generation_disabled(lang, element),
+ result['allOf']
+ ))
+ for d in dicts_to_merge:
+ ref_str = d.get('$ref')
+ if isinstance(ref_str, str):
+ ref = entities.Reference(location, ref_str)
+ value = merge_all_ofs(dictionary=ref.value, lang=lang, location=location)
+ result = __merge(d1=result, d2=value, location=location)
+ else:
+ result = __merge(d1=result, d2=d, location=location)
+ result.pop('allOf', None)
+ return result
+
+
+def clean_unused_definitions(dictionary: Dict[str, any]) -> Dict[str, any]:
+ definitions: Dict[str, any] = dictionary.get('definitions')
+ if definitions is None:
+ return dictionary
+ used_definitions: Dict[str, any] = dict()
+ for name, definition in definitions.items():
+ if number_of_references(obj_name=name, dictionary=dictionary) > 0:
+ used_definitions[name] = definition
+ else:
+ print(f'Purging unused definition "{name}"')
+ dictionary['definitions'] = used_definitions
+ return dictionary
+
+
+def resolve_references(
+ dictionary: Dict[str, any],
+ location: entities.ElementLocation,
+ lang: GeneratedLanguage,
+ reference_stack: List[entities.Reference] = None
+) -> Dict[str, any]:
+ description_ref = dictionary.get('$description')
+ if isinstance(description_ref, str):
+ description = entities.DescriptionReference(location=location, ref=description_ref)
+ result = deepcopy(dictionary)
+ result['description_translations'] = description.value
+ result.pop('$description', None)
+ return result
+
+ if reference_stack is None:
+ reference_stack = []
+ reference = dictionary.get('$ref')
+
+ def loop_str_transform(r: entities.Reference) -> str:
+ joined_path = '/'.join(r.path)
+ return f'{r.file.name}#{joined_path}'
+
+ if isinstance(reference, str):
+ ref = entities.Reference(location=location, ref=reference)
+ if ref in reference_stack:
+ loop_str = ' -> '.join(map(loop_str_transform, reference_stack + [ref]))
+ print(f'Handling circular reference: {loop_str}')
+
+ ref_type = ref.value.get('type')
+ if not (isinstance(ref_type, str) and ref_type.startswith('$defined_')):
+ raise errors.UnsupportedCircularReferenceError(description=loop_str)
+ return ref.value
+ result = deepcopy(dictionary)
+ result['$resolved_refs'] = result.get('$resolved_refs', []) + [reference]
+ result.pop('$ref', None)
+ return __merge(d1=result, d2=ref.value, location=location)
+
+ resolved_refs = dictionary.get('$resolved_refs')
+ if not is_list_of_type(resolved_refs, list_type=str):
+ resolved_refs = []
+ new_stack = reference_stack + list(map(lambda it: entities.Reference(location=location, ref=it), resolved_refs))
+
+ resolved_dict = deepcopy(dictionary)
+ for key in list(resolved_dict):
+ value = resolved_dict.get(key)
+ if isinstance(value, Dict):
+ if not code_generation_disabled(lang=lang, dictionary=value):
+ resolved_dict[key] = resolve_references(
+ dictionary=value,
+ location=location + key,
+ lang=lang,
+ reference_stack=new_stack
+ )
+ else:
+ resolved_dict.pop(key, None)
+ elif is_list_of_type(value, list_type=Dict):
+ resolved_dict[key] = list(map(
+ lambda element: resolve_references(dictionary=element, lang=lang, location=location + key),
+ filter(
+ lambda element: not code_generation_disabled(lang=lang, dictionary=element),
+ value
+ )
+ ))
+ return resolved_dict
+
+
+def internal_resolve_structure(
+ path: str,
+ lang: GeneratedLanguage,
+ parent_dir: Optional[entities.SchemaDirectory] = None
+) -> entities.SchemaFilesystemItem:
+ print(f'Processing path: {path}')
+
+ if not os.path.exists(path):
+ raise errors.PathNotFoundError(path)
+
+ if os.path.isdir(path):
+ return entities.SchemaDirectory(path, parent_dir, lang)
+
+ json_data = json.loads(open(path).read())
+ if not isinstance(json_data, Dict):
+ raise errors.NonDictionaryContentError(path)
+
+ name = os.path.basename(os.path.normpath(path))
+ return entities.SchemaFile(name=name, parent_dir=parent_dir, contents=json_data)
+
+
+def resolve_structure(config: Config) -> entities.SchemaDirectory:
+ result = internal_resolve_structure(path=config.schema_path, lang=config.generation.lang)
+ if not isinstance(result, entities.SchemaDirectory):
+ raise errors.FileAtInputPathError(config.schema_path)
+ return result
+
+
+def schema_preprocessing(config: Config) -> entities.SchemaDirectory:
+ root_directory = resolve_structure(config)
+ root_directory.merge_all_ofs(config.generation.lang)
+ root_directory.resolve_references(config.generation.lang)
+ root_directory.clean_unused_definitions()
+ return root_directory
diff --git a/api_generator/api_generator/schema/preprocessing/utils.py b/api_generator/api_generator/schema/preprocessing/utils.py
new file mode 100644
index 000000000..592044121
--- /dev/null
+++ b/api_generator/api_generator/schema/preprocessing/utils.py
@@ -0,0 +1,83 @@
+from typing import Dict, List, TypeVar, Optional
+from copy import deepcopy
+
+from . import errors
+from .entities import ElementLocation, SchemaFile
+from ...config import GeneratedLanguage
+
+Key = TypeVar('T')
+Value = TypeVar('V')
+
+
+def enclosed_dict_for(keys: List[Key], dictionary: Dict[Key, any]) -> Dict[Key, any]:
+ if not keys:
+ return dictionary
+ d = deepcopy(dictionary)
+ for key in keys:
+ d = d.get(key)
+ return d
+
+
+def get_value_with_optional_by_lang(key: str, lang: GeneratedLanguage, dictionary: Dict[str, any]) -> Optional[Value]:
+ if key in dictionary:
+ return dictionary[key]
+ return dictionary.get(f'{key}_{lang.value}')
+
+
+def is_list_of_type(value, list_type) -> bool:
+ return isinstance(value, List) and all(isinstance(element, list_type) for element in value)
+
+
+def number_of_references(obj_name: str, dictionary: Dict[str, any]) -> int:
+ result = 0
+ for item in dictionary.values():
+ if isinstance(item, Dict):
+ result += number_of_references(obj_name, item)
+ elif is_list_of_type(item, list_type=Dict):
+ result += sum(map(lambda element: number_of_references(obj_name=obj_name, dictionary=element), item))
+
+ ref = dictionary.get('$ref')
+ if isinstance(ref, str) and ref.endswith('/' + obj_name):
+ result += 1
+
+ def_type = dictionary.get('type')
+ if isinstance(def_type, str) and def_type == f'$defined_{obj_name}':
+ result += 1
+
+ return result
+
+
+def code_generation_disabled(lang: GeneratedLanguage, dictionary: Dict[str, any]) -> bool:
+ value = get_value_with_optional_by_lang('code_generation_disabled', lang, dictionary)
+ return value or False
+
+
+def get_full_reference_location(location: ElementLocation, ref: str) -> (str, SchemaFile, List[str]):
+ invalid_reference_error = errors.InvalidFileReferenceError(location, ref)
+ components: List[str] = ref.split('#')
+
+ if len(components) > 2:
+ raise invalid_reference_error
+
+ if len(components) == 2 and not components[0]:
+ path: List[str] = list(filter(None, components[1].split('/')))
+ return '', location.file, path
+
+ try:
+ parent_directory = location.file.parent_dir
+ if parent_directory is None:
+ raise invalid_reference_error
+ ref = parent_directory.resolve(components[0])
+ if not isinstance(ref, SchemaFile):
+ raise invalid_reference_error
+
+ if len(components) == 2:
+ path: List[str] = list(filter(None, components[1].split('/')))
+ else:
+ path: List[str] = []
+
+ ref_dir_path = '/'.join(components[0].split('/')[:-1])
+ prefix = '' if not ref_dir_path else ref_dir_path + '/'
+ return prefix, ref, path
+ except errors.InvalidReferenceError:
+ raise invalid_reference_error
diff --git a/api_generator/api_generator/schema/utils.py b/api_generator/api_generator/schema/utils.py
new file mode 100644
index 000000000..85e29652b
--- /dev/null
+++ b/api_generator/api_generator/schema/utils.py
@@ -0,0 +1,33 @@
+from typing import Dict, List, TypeVar, Optional
+
+from ..config import GeneratedLanguage
+
+Key = TypeVar('T')
+Value = TypeVar('V')
+
+
+def enclosed_dict_for(keys: List[Key], dictionary: Dict[Key, any]) -> Dict[Key, any]:
+ if len(keys) == 0:
+ return dictionary
+ d = dictionary.copy()
+ for key in keys:
+ d = d.get(key)
+ return d
+
+
+def get_value_with_optional_by_lang(key: str, lang: GeneratedLanguage, dictionary: Dict[str, any]) -> Optional[Value]:
+ if key in dictionary:
+ return dictionary[key]
+ return dictionary.get(f'{key}_{lang.value}')
+
+
+def code_generation_disabled(lang: GeneratedLanguage, dictionary: Dict[str, any]) -> bool:
+ return get_value_with_optional_by_lang('code_generation_disabled', lang, dictionary) or False
+
+
+def is_list_of_type(value, list_type) -> bool:
+ return isinstance(value, List) and all(isinstance(element, list_type) for element in value)
+
+
+def is_dict_with_keys_of_type(value, key_type) -> bool:
+ return isinstance(value, Dict) and all(isinstance(key, key_type) for key in value)
diff --git a/api_generator/api_generator/utils.py b/api_generator/api_generator/utils.py
new file mode 100644
index 000000000..ca3bf6233
--- /dev/null
+++ b/api_generator/api_generator/utils.py
@@ -0,0 +1,69 @@
+from typing import List, Dict, Any
+import re
+import os
+import shutil
+import json
+
+
+def __camel_case_components(string: str) -> List[str]:
+ result: List[str] = []
+ start_component_pos = 0
+ for current_pos, char in enumerate(string):
+ if char.isupper():
+ if start_component_pos != current_pos:
+ result.append(string[start_component_pos:current_pos])
+ start_component_pos = current_pos
+ result.append(string[start_component_pos::])
+ return result
+
+
+def name_components(string: str) -> List[str]:
+ components = []
+ for component in re.split(r'[_\-:\s]+', string):
+ components.extend(__camel_case_components(component))
+ return list(filter(lambda x: x, components))
+
+
+def capitalize_camel_case(string: str) -> str:
+ return ''.join(map(lambda component: component.capitalize(), name_components(string)))
+
+
+def lower_camel_case(string: str) -> str:
+ components = name_components(string)
+ if not components:
+ return ''
+ return ''.join([components[0].lower()] + list(map(lambda component: component.capitalize(), components[1::])))
+
+
+def constant_upper_case(string: str):
+ return '_'.join(map(lambda s: s.upper(), name_components(string)))
+
+
+def fixing_first_digit(string: str) -> str:
+ if not string:
+ return string
+ first_char = string[0]
+ if first_char.isdigit():
+ return f'_{string}'
+ return string
+
+
+def clear_content_of_directory(directory: str) -> None:
+ for filename in os.listdir(directory):
+ file_path = os.path.join(directory, filename)
+ try:
+ if os.path.isfile(file_path) or os.path.islink(file_path):
+ os.unlink(file_path)
+ elif os.path.isdir(file_path):
+ shutil.rmtree(file_path)
+ except Exception as e:
+ print(f'Failed to delete {file_path}. Reason: {e}')
+
+
+def json_dict(string: str) -> Dict[str, Any]:
+ return json.loads(string)
+
+
+def indented(string: str, level: int = 1, indent_width: int = 2) -> str:
+ indent = ' ' * level * indent_width
+ return indent + string.replace('\n', '\n' + indent)
diff --git a/api_generator/dev_requirements.txt b/api_generator/dev_requirements.txt
new file mode 100644
index 000000000..30d794e39
--- /dev/null
+++ b/api_generator/dev_requirements.txt
@@ -0,0 +1,3 @@
+flake8==2.5.4
+pytest==7.1.2
+deepdiff==5.8.1
diff --git a/api_generator/requirements.txt b/api_generator/requirements.txt
new file mode 100644
index 000000000..b3ee7d5ca
--- /dev/null
+++ b/api_generator/requirements.txt
@@ -0,0 +1,2 @@
+pydantic==1.9.1
+validators==0.20.0
diff --git a/api_generator/tests/configs/documentation_config.json b/api_generator/tests/configs/documentation_config.json
new file mode 100644
index 000000000..d25cef5f3
--- /dev/null
+++ b/api_generator/tests/configs/documentation_config.json
@@ -0,0 +1,3 @@
+{
+ "lang": "documentation"
+}
diff --git a/api_generator/tests/configs/kotlin_config.json b/api_generator/tests/configs/kotlin_config.json
new file mode 100644
index 000000000..348c388a2
--- /dev/null
+++ b/api_generator/tests/configs/kotlin_config.json
@@ -0,0 +1,10 @@
+{
+ "lang": "kotlin",
+ "header": "// Generated code. Do not modify.\n\npackage com.yandex.div2\n\nimport android.graphics.Color\nimport android.net.Uri\nimport androidx.annotation.ColorInt\nimport com.yandex.div.json.*\nimport com.yandex.div.json.expressions.Expression\nimport com.yandex.div.json.expressions.ExpressionsList\nimport com.yandex.div.json.schema.*\nimport com.yandex.div.core.annotations.Mockable\nimport java.io.IOException\nimport java.util.BitSet\nimport org.json.JSONObject\nimport com.yandex.div.data.*\n",
+ "errorsCollectors": [
+ "div-data"
+ ],
+ "kotlinAnnotations": [
+ "@Mockable"
+ ]
+}
diff --git a/api_generator/tests/configs/swift_config.json b/api_generator/tests/configs/swift_config.json
new file mode 100644
index 000000000..770a495f0
--- /dev/null
+++ b/api_generator/tests/configs/swift_config.json
@@ -0,0 +1,4 @@
+{
+ "lang": "swift",
+ "header": "// Generated code. Do not modify.\n\nimport CommonCore\nimport Foundation\nimport Serialization\n"
+}
diff --git a/api_generator/tests/configs/typescript_config.json b/api_generator/tests/configs/typescript_config.json
new file mode 100644
index 000000000..ab91c4a15
--- /dev/null
+++ b/api_generator/tests/configs/typescript_config.json
@@ -0,0 +1,4 @@
+{
+ "lang": "typescript",
+ "header": "// Generated code. Do not modify.\n\nimport { TemplateBlock } from '../blocks';\nimport { Exact, IntBoolean, NonEmptyArray } from '../helper';\nimport { Type } from '../template';\n"
+}
diff --git a/api_generator/tests/references/documentation/en/entity.md b/api_generator/tests/references/documentation/en/entity.md
new file mode 100644
index 000000000..b8bfb97e7
--- /dev/null
+++ b/api_generator/tests/references/documentation/en/entity.md
@@ -0,0 +1,19 @@
+# entity
+It can be one of the following types:
+* [entity_with_array](entity_with_array.md#entity_with_array)
+* [entity_with_array_of_nested_items](entity_with_array_of_nested_items.md#entity_with_array_of_nested_items)
+* [entity_with_array_with_transform](entity_with_array_with_transform.md#entity_with_array_with_transform)
+* [entity_with_complex_property](entity_with_complex_property.md#entity_with_complex_property)
+* [entity_with_complex_property_with_default_value](entity_with_complex_property_with_default_value.md#entity_with_complex_property_with_default_value)
+* [entity_with_entity_property](entity_with_entity_property.md#entity_with_entity_property)
+* [entity_with_optional_complex_property](entity_with_optional_complex_property.md#entity_with_optional_complex_property)
+* [entity_with_optional_property](entity_with_optional_property.md#entity_with_optional_property)
+* [entity_with_optional_string_enum_property](entity_with_optional_string_enum_property.md#entity_with_optional_string_enum_property)
+* [entity_with_property_with_default_value](entity_with_property_with_default_value.md#entity_with_property_with_default_value)
+* [entity_with_required_property](entity_with_required_property.md#entity_with_required_property)
+* [entity_with_simple_properties](entity_with_simple_properties.md#entity_with_simple_properties)
+* [entity_with_strict_array](entity_with_strict_array.md#entity_with_strict_array)
+* [entity_with_string_array_property](entity_with_string_array_property.md#entity_with_string_array_property)
+* [entity_with_string_enum_property](entity_with_string_enum_property.md#entity_with_string_enum_property)
+* [entity_with_string_enum_property_with_default_value](entity_with_string_enum_property_with_default_value.md#entity_with_string_enum_property_with_default_value)
+* [entity_without_properties](entity_without_properties.md#entity_without_properties)
diff --git a/api_generator/tests/references/documentation/en/entity_with_array.md b/api_generator/tests/references/documentation/en/entity_with_array.md
new file mode 100644
index 000000000..e01d1b0bb
--- /dev/null
+++ b/api_generator/tests/references/documentation/en/entity_with_array.md
@@ -0,0 +1,16 @@
+# entity_with_array
+There is no description yet.
+
+## JSON
+```json
+{
+ type*: "entity_with_array",
+ array*: [ entity, ... ]
+}
+```
+
+## Parameters
+| Parameters | Description |
+| --- | --- |
+| `array` | **array**
Required parameter.
An array must not be empty.
List of possible values:
[entity_with_array](entity_with_array.md#entity_with_array)[entity_with_array_of_nested_items](entity_with_array_of_nested_items.md#entity_with_array_of_nested_items)[entity_with_array_with_transform](entity_with_array_with_transform.md#entity_with_array_with_transform)[entity_with_complex_property](entity_with_complex_property.md#entity_with_complex_property)[entity_with_complex_property_with_default_value](entity_with_complex_property_with_default_value.md#entity_with_complex_property_with_default_value)[entity_with_entity_property](entity_with_entity_property.md#entity_with_entity_property)[entity_with_optional_complex_property](entity_with_optional_complex_property.md#entity_with_optional_complex_property)[entity_with_optional_property](entity_with_optional_property.md#entity_with_optional_property)[entity_with_optional_string_enum_property](entity_with_optional_string_enum_property.md#entity_with_optional_string_enum_property)[entity_with_property_with_default_value](entity_with_property_with_default_value.md#entity_with_property_with_default_value)[entity_with_required_property](entity_with_required_property.md#entity_with_required_property)[entity_with_simple_properties](entity_with_simple_properties.md#entity_with_simple_properties)[entity_with_strict_array](entity_with_strict_array.md#entity_with_strict_array)[entity_with_string_array_property](entity_with_string_array_property.md#entity_with_string_array_property)[entity_with_string_enum_property](entity_with_string_enum_property.md#entity_with_string_enum_property)[entity_with_string_enum_property_with_default_value](entity_with_string_enum_property_with_default_value.md#entity_with_string_enum_property_with_default_value)[entity_without_properties](entity_without_properties.md#entity_without_properties) |
+| `type` | **string**
Required parameter.
The value must always be `entity_with_array`.
|
diff --git a/api_generator/tests/references/documentation/en/entity_with_array_of_nested_items.md b/api_generator/tests/references/documentation/en/entity_with_array_of_nested_items.md
new file mode 100644
index 000000000..a942799c6
--- /dev/null
+++ b/api_generator/tests/references/documentation/en/entity_with_array_of_nested_items.md
@@ -0,0 +1,28 @@
+# entity_with_array_of_nested_items
+There is no description yet.
+
+## JSON
+```json
+{
+ type*: "entity_with_array_of_nested_items",
+ items*: [
+ {
+ entity*: entity,
+ property*: "string"
+ },
+ ...
+ ]
+}
+```
+
+## Parameters
+| Parameters | Description |
+| --- | --- |
+| `items` | **array**
Required parameter.
An array must not be empty.
|
+| `type` | **string**
Required parameter.
The value must always be `entity_with_array_of_nested_items`.
|
+
+### item
+| Parameters | Description |
+| --- | --- |
+| `entity` | **object**
Required parameter.
The value has the type `entity`.
List of possible values:
[entity_with_array](entity_with_array.md#entity_with_array)[entity_with_array_of_nested_items](entity_with_array_of_nested_items.md#entity_with_array_of_nested_items)[entity_with_array_with_transform](entity_with_array_with_transform.md#entity_with_array_with_transform)[entity_with_complex_property](entity_with_complex_property.md#entity_with_complex_property)[entity_with_complex_property_with_default_value](entity_with_complex_property_with_default_value.md#entity_with_complex_property_with_default_value)[entity_with_entity_property](entity_with_entity_property.md#entity_with_entity_property)[entity_with_optional_complex_property](entity_with_optional_complex_property.md#entity_with_optional_complex_property)[entity_with_optional_property](entity_with_optional_property.md#entity_with_optional_property)[entity_with_optional_string_enum_property](entity_with_optional_string_enum_property.md#entity_with_optional_string_enum_property)[entity_with_property_with_default_value](entity_with_property_with_default_value.md#entity_with_property_with_default_value)[entity_with_required_property](entity_with_required_property.md#entity_with_required_property)[entity_with_simple_properties](entity_with_simple_properties.md#entity_with_simple_properties)[entity_with_strict_array](entity_with_strict_array.md#entity_with_strict_array)[entity_with_string_array_property](entity_with_string_array_property.md#entity_with_string_array_property)[entity_with_string_enum_property](entity_with_string_enum_property.md#entity_with_string_enum_property)[entity_with_string_enum_property_with_default_value](entity_with_string_enum_property_with_default_value.md#entity_with_string_enum_property_with_default_value)[entity_without_properties](entity_without_properties.md#entity_without_properties) |
+| `property` | **string**
Required parameter.
A string must not be empty.
|
diff --git a/api_generator/tests/references/documentation/en/entity_with_array_with_transform.md b/api_generator/tests/references/documentation/en/entity_with_array_with_transform.md
new file mode 100644
index 000000000..9684a2d91
--- /dev/null
+++ b/api_generator/tests/references/documentation/en/entity_with_array_with_transform.md
@@ -0,0 +1,16 @@
+# entity_with_array_with_transform
+There is no description yet.
+
+## JSON
+```json
+{
+ type*: "entity_with_array_with_transform",
+ array*: [ "string", ... ]
+}
+```
+
+## Parameters
+| Parameters | Description |
+| --- | --- |
+| `array` | **array**
Required parameter.
An array must not be empty.
|
+| `type` | **string**
Required parameter.
The value must always be `entity_with_array_with_transform`.
|
diff --git a/api_generator/tests/references/documentation/en/entity_with_complex_property.md b/api_generator/tests/references/documentation/en/entity_with_complex_property.md
new file mode 100644
index 000000000..b49217131
--- /dev/null
+++ b/api_generator/tests/references/documentation/en/entity_with_complex_property.md
@@ -0,0 +1,23 @@
+# entity_with_complex_property
+There is no description yet.
+
+## JSON
+```json
+{
+ type*: "entity_with_complex_property",
+ property*: {
+ value*: "string"
+ }
+}
+```
+
+## Parameters
+| Parameters | Description |
+| --- | --- |
+| `property` | **object**
Required parameter.
The value has the type `property`.
|
+| `type` | **string**
Required parameter.
The value must always be `entity_with_complex_property`.
|
+
+### property
+| Parameters | Description |
+| --- | --- |
+| `value` | **string**
Required parameter.
The value must be a valid URL.
|
diff --git a/api_generator/tests/references/documentation/en/entity_with_complex_property_with_default_value.md b/api_generator/tests/references/documentation/en/entity_with_complex_property_with_default_value.md
new file mode 100644
index 000000000..095684451
--- /dev/null
+++ b/api_generator/tests/references/documentation/en/entity_with_complex_property_with_default_value.md
@@ -0,0 +1,23 @@
+# entity_with_complex_property_with_default_value
+There is no description yet.
+
+## JSON
+```json
+{
+ type*: "entity_with_complex_property_with_default_value",
+ property: {
+ value*: "string"
+ }
+}
+```
+
+## Parameters
+| Parameters | Description |
+| --- | --- |
+| `property` | **object**
The value has the type `property`.
Default value: `{ "value": "Default text" }`.
|
+| `type` | **string**
Required parameter.
The value must always be `entity_with_complex_property_with_default_value`.
|
+
+### property
+| Parameters | Description |
+| --- | --- |
+| `value` | **string**
Required parameter.
|
diff --git a/api_generator/tests/references/documentation/en/entity_with_entity_property.md b/api_generator/tests/references/documentation/en/entity_with_entity_property.md
new file mode 100644
index 000000000..be6432b96
--- /dev/null
+++ b/api_generator/tests/references/documentation/en/entity_with_entity_property.md
@@ -0,0 +1,16 @@
+# entity_with_entity_property
+There is no description yet.
+
+## JSON
+```json
+{
+ type*: "entity_with_entity_property",
+ entity: entity
+}
+```
+
+## Parameters
+| Parameters | Description |
+| --- | --- |
+| `entity` | **object**
The value has the type `entity`.
Default value: `{ "type": "entity_with_string_enum_property", "property": "second" }`.
List of possible values:
[entity_with_array](entity_with_array.md#entity_with_array)[entity_with_array_of_nested_items](entity_with_array_of_nested_items.md#entity_with_array_of_nested_items)[entity_with_array_with_transform](entity_with_array_with_transform.md#entity_with_array_with_transform)[entity_with_complex_property](entity_with_complex_property.md#entity_with_complex_property)[entity_with_complex_property_with_default_value](entity_with_complex_property_with_default_value.md#entity_with_complex_property_with_default_value)[entity_with_entity_property](entity_with_entity_property.md#entity_with_entity_property)[entity_with_optional_complex_property](entity_with_optional_complex_property.md#entity_with_optional_complex_property)[entity_with_optional_property](entity_with_optional_property.md#entity_with_optional_property)[entity_with_optional_string_enum_property](entity_with_optional_string_enum_property.md#entity_with_optional_string_enum_property)[entity_with_property_with_default_value](entity_with_property_with_default_value.md#entity_with_property_with_default_value)[entity_with_required_property](entity_with_required_property.md#entity_with_required_property)[entity_with_simple_properties](entity_with_simple_properties.md#entity_with_simple_properties)[entity_with_strict_array](entity_with_strict_array.md#entity_with_strict_array)[entity_with_string_array_property](entity_with_string_array_property.md#entity_with_string_array_property)[entity_with_string_enum_property](entity_with_string_enum_property.md#entity_with_string_enum_property)[entity_with_string_enum_property_with_default_value](entity_with_string_enum_property_with_default_value.md#entity_with_string_enum_property_with_default_value)[entity_without_properties](entity_without_properties.md#entity_without_properties) |
+| `type` | **string**
Required parameter.
The value must always be `entity_with_entity_property`.
|
diff --git a/api_generator/tests/references/documentation/en/entity_with_optional_complex_property.md b/api_generator/tests/references/documentation/en/entity_with_optional_complex_property.md
new file mode 100644
index 000000000..94fe7b695
--- /dev/null
+++ b/api_generator/tests/references/documentation/en/entity_with_optional_complex_property.md
@@ -0,0 +1,23 @@
+# entity_with_optional_complex_property
+There is no description yet.
+
+## JSON
+```json
+{
+ type*: "entity_with_optional_complex_property",
+ property: {
+ value*: "string"
+ }
+}
+```
+
+## Parameters
+| Parameters | Description |
+| --- | --- |
+| `property` | **object**
The value has the type `property`.
|
+| `type` | **string**
Required parameter.
The value must always be `entity_with_optional_complex_property`.
|
+
+### property
+| Parameters | Description |
+| --- | --- |
+| `value` | **string**
Required parameter.
The value must be a valid URL.
|
diff --git a/api_generator/tests/references/documentation/en/entity_with_optional_property.md b/api_generator/tests/references/documentation/en/entity_with_optional_property.md
new file mode 100644
index 000000000..63d6c718d
--- /dev/null
+++ b/api_generator/tests/references/documentation/en/entity_with_optional_property.md
@@ -0,0 +1,16 @@
+# entity_with_optional_property
+There is no description yet.
+
+## JSON
+```json
+{
+ type*: "entity_with_optional_property",
+ property: "string"
+}
+```
+
+## Parameters
+| Parameters | Description |
+| --- | --- |
+| `property` | **string**
A string must not be empty.
|
+| `type` | **string**
Required parameter.
The value must always be `entity_with_optional_property`.
|
diff --git a/api_generator/tests/references/documentation/en/entity_with_optional_string_enum_property.md b/api_generator/tests/references/documentation/en/entity_with_optional_string_enum_property.md
new file mode 100644
index 000000000..53511b857
--- /dev/null
+++ b/api_generator/tests/references/documentation/en/entity_with_optional_string_enum_property.md
@@ -0,0 +1,16 @@
+# entity_with_optional_string_enum_property
+There is no description yet.
+
+## JSON
+```json
+{
+ type*: "entity_with_optional_string_enum_property",
+ property: "string"
+}
+```
+
+## Parameters
+| Parameters | Description |
+| --- | --- |
+| `property` | **string**
Possible values: `first`, `second`.
|
+| `type` | **string**
Required parameter.
The value must always be `entity_with_optional_string_enum_property`.
|
diff --git a/api_generator/tests/references/documentation/en/entity_with_property_with_default_value.md b/api_generator/tests/references/documentation/en/entity_with_property_with_default_value.md
new file mode 100644
index 000000000..c9f20f2c9
--- /dev/null
+++ b/api_generator/tests/references/documentation/en/entity_with_property_with_default_value.md
@@ -0,0 +1,31 @@
+# entity_with_property_with_default_value
+There is no description yet.
+
+## JSON
+```json
+{
+ type*: "entity_with_property_with_default_value",
+ int: "int",
+ nested: {
+ int: "int",
+ non_optional*: "string",
+ url: "string"
+ },
+ url: "string"
+}
+```
+
+## Parameters
+| Parameters | Description |
+| --- | --- |
+| `int` | **int**
Restriction for the value `x`: `x >= 0`.
Default value: `0`.
|
+| `nested` | **object**
non_optional is used to suppress auto-generation of default value for object with all-optional fields.
The value has the type `nested`.
|
+| `type` | **string**
Required parameter.
The value must always be `entity_with_property_with_default_value`.
|
+| `url` | **string**
The value must be a valid URL.
Allowed schemes: `https`.
Default value: `https://yandex.ru`.
|
+
+### nested
+| Parameters | Description |
+| --- | --- |
+| `int` | **int**
Restriction for the value `x`: `x >= 0`.
Default value: `0`.
|
+| `non_optional` | **string**
Required parameter.
|
+| `url` | **string**
The value must be a valid URL.
Allowed schemes: `https`.
Default value: `https://yandex.ru`.
|
diff --git a/api_generator/tests/references/documentation/en/entity_with_required_property.md b/api_generator/tests/references/documentation/en/entity_with_required_property.md
new file mode 100644
index 000000000..b9d26e67b
--- /dev/null
+++ b/api_generator/tests/references/documentation/en/entity_with_required_property.md
@@ -0,0 +1,16 @@
+# entity_with_required_property
+There is no description yet.
+
+## JSON
+```json
+{
+ type*: "entity_with_required_property",
+ property*: "string"
+}
+```
+
+## Parameters
+| Parameters | Description |
+| --- | --- |
+| `property` | **string**
Required parameter.
A string must not be empty.
|
+| `type` | **string**
Required parameter.
The value must always be `entity_with_required_property`.
|
diff --git a/api_generator/tests/references/documentation/en/entity_with_simple_properties.md b/api_generator/tests/references/documentation/en/entity_with_simple_properties.md
new file mode 100644
index 000000000..f48eaf2f9
--- /dev/null
+++ b/api_generator/tests/references/documentation/en/entity_with_simple_properties.md
@@ -0,0 +1,32 @@
+# entity_with_simple_properties
+Entity with simple properties.
+
+## JSON
+```json
+{
+ type*: "entity_with_simple_properties",
+ boolean: "bool",
+ boolean_int: "bool_int",
+ color: "string",
+ double: "number",
+ id: "int",
+ integer: "int",
+ positive_integer: "int",
+ string: "string",
+ url: "string"
+}
+```
+
+## Parameters
+| Parameters | Description |
+| --- | --- |
+| `boolean` | **bool**
Boolean property.
|
+| `boolean_int` | **bool_int**
Parameter is deprecated
Boolean value in numeric format.
Available platforms: Android.
|
+| `color` | **string**
Color.
Valid formats: `#RGB`, `#ARGB`, `#RRGGBB`, `#AARRGGBB`.
|
+| `double` | **number**
Floating point number.
|
+| `id` | **int**
ID. Can't contain expressions.
|
+| `integer` | **int**
Integer.
|
+| `positive_integer` | **int**
Positive integer.
Restriction for the value `x`: `x > 0`.
The functionality is under development.
|
+| `string` | **string**
String.
A string must not be empty.
Available platforms: Android, iOS, browser.
|
+| `type` | **string**
Required parameter.
The value must always be `entity_with_simple_properties`.
|
+| `url` | **string**
The value must be a valid URL.
|
diff --git a/api_generator/tests/references/documentation/en/entity_with_strict_array.md b/api_generator/tests/references/documentation/en/entity_with_strict_array.md
new file mode 100644
index 000000000..8e93723b7
--- /dev/null
+++ b/api_generator/tests/references/documentation/en/entity_with_strict_array.md
@@ -0,0 +1,16 @@
+# entity_with_strict_array
+There is no description yet.
+
+## JSON
+```json
+{
+ type*: "entity_with_strict_array",
+ array*: [ entity, ... ]
+}
+```
+
+## Parameters
+| Parameters | Description |
+| --- | --- |
+| `array` | **array**
Required parameter.
An array must not be empty.
Partial parsing is not allowed.
List of possible values:
[entity_with_array](entity_with_array.md#entity_with_array)[entity_with_array_of_nested_items](entity_with_array_of_nested_items.md#entity_with_array_of_nested_items)[entity_with_array_with_transform](entity_with_array_with_transform.md#entity_with_array_with_transform)[entity_with_complex_property](entity_with_complex_property.md#entity_with_complex_property)[entity_with_complex_property_with_default_value](entity_with_complex_property_with_default_value.md#entity_with_complex_property_with_default_value)[entity_with_entity_property](entity_with_entity_property.md#entity_with_entity_property)[entity_with_optional_complex_property](entity_with_optional_complex_property.md#entity_with_optional_complex_property)[entity_with_optional_property](entity_with_optional_property.md#entity_with_optional_property)[entity_with_optional_string_enum_property](entity_with_optional_string_enum_property.md#entity_with_optional_string_enum_property)[entity_with_property_with_default_value](entity_with_property_with_default_value.md#entity_with_property_with_default_value)[entity_with_required_property](entity_with_required_property.md#entity_with_required_property)[entity_with_simple_properties](entity_with_simple_properties.md#entity_with_simple_properties)[entity_with_strict_array](entity_with_strict_array.md#entity_with_strict_array)[entity_with_string_array_property](entity_with_string_array_property.md#entity_with_string_array_property)[entity_with_string_enum_property](entity_with_string_enum_property.md#entity_with_string_enum_property)[entity_with_string_enum_property_with_default_value](entity_with_string_enum_property_with_default_value.md#entity_with_string_enum_property_with_default_value)[entity_without_properties](entity_without_properties.md#entity_without_properties) |
+| `type` | **string**
Required parameter.
The value must always be `entity_with_strict_array`.
|
diff --git a/api_generator/tests/references/documentation/en/entity_with_string_array_property.md b/api_generator/tests/references/documentation/en/entity_with_string_array_property.md
new file mode 100644
index 000000000..cbe32d3b2
--- /dev/null
+++ b/api_generator/tests/references/documentation/en/entity_with_string_array_property.md
@@ -0,0 +1,16 @@
+# entity_with_string_array_property
+There is no description yet.
+
+## JSON
+```json
+{
+ type*: "entity_with_string_array_property",
+ array*: [ "string", ... ]
+}
+```
+
+## Parameters
+| Parameters | Description |
+| --- | --- |
+| `array` | **array**
Required parameter.
An array must not be empty.
|
+| `type` | **string**
Required parameter.
The value must always be `entity_with_string_array_property`.
|
diff --git a/api_generator/tests/references/documentation/en/entity_with_string_enum_property.md b/api_generator/tests/references/documentation/en/entity_with_string_enum_property.md
new file mode 100644
index 000000000..756e3024f
--- /dev/null
+++ b/api_generator/tests/references/documentation/en/entity_with_string_enum_property.md
@@ -0,0 +1,16 @@
+# entity_with_string_enum_property
+There is no description yet.
+
+## JSON
+```json
+{
+ type*: "entity_with_string_enum_property",
+ property*: "string"
+}
+```
+
+## Parameters
+| Parameters | Description |
+| --- | --- |
+| `property` | **string**
Required parameter.
Possible values: `first`, `second`.
|
+| `type` | **string**
Required parameter.
The value must always be `entity_with_string_enum_property`.
|
diff --git a/api_generator/tests/references/documentation/en/entity_with_string_enum_property_with_default_value.md b/api_generator/tests/references/documentation/en/entity_with_string_enum_property_with_default_value.md
new file mode 100644
index 000000000..675c8ff22
--- /dev/null
+++ b/api_generator/tests/references/documentation/en/entity_with_string_enum_property_with_default_value.md
@@ -0,0 +1,16 @@
+# entity_with_string_enum_property_with_default_value
+There is no description yet.
+
+## JSON
+```json
+{
+ type*: "entity_with_string_enum_property_with_default_value",
+ value: "string"
+}
+```
+
+## Parameters
+| Parameters | Description |
+| --- | --- |
+| `type` | **string**
Required parameter.
The value must always be `entity_with_string_enum_property_with_default_value`.
|
+| `value` | **string**
Possible values: `first`, `second`, `third`.
Default value: `second`.
|
diff --git a/api_generator/tests/references/documentation/en/entity_without_properties.md b/api_generator/tests/references/documentation/en/entity_without_properties.md
new file mode 100644
index 000000000..7e04b342c
--- /dev/null
+++ b/api_generator/tests/references/documentation/en/entity_without_properties.md
@@ -0,0 +1,14 @@
+# entity_without_properties
+There is no description yet.
+
+## JSON
+```json
+{
+ type*: "entity_without_properties"
+}
+```
+
+## Parameters
+| Parameters | Description |
+| --- | --- |
+| `type` | **string**
Required parameter.
The value must always be `entity_without_properties`.
|
diff --git a/api_generator/tests/references/documentation/ru/entity.md b/api_generator/tests/references/documentation/ru/entity.md
new file mode 100644
index 000000000..62a11e60d
--- /dev/null
+++ b/api_generator/tests/references/documentation/ru/entity.md
@@ -0,0 +1,19 @@
+# entity
+Может быть одним из следующих типов:
+* [entity_with_array](entity_with_array.md#entity_with_array)
+* [entity_with_array_of_nested_items](entity_with_array_of_nested_items.md#entity_with_array_of_nested_items)
+* [entity_with_array_with_transform](entity_with_array_with_transform.md#entity_with_array_with_transform)
+* [entity_with_complex_property](entity_with_complex_property.md#entity_with_complex_property)
+* [entity_with_complex_property_with_default_value](entity_with_complex_property_with_default_value.md#entity_with_complex_property_with_default_value)
+* [entity_with_entity_property](entity_with_entity_property.md#entity_with_entity_property)
+* [entity_with_optional_complex_property](entity_with_optional_complex_property.md#entity_with_optional_complex_property)
+* [entity_with_optional_property](entity_with_optional_property.md#entity_with_optional_property)
+* [entity_with_optional_string_enum_property](entity_with_optional_string_enum_property.md#entity_with_optional_string_enum_property)
+* [entity_with_property_with_default_value](entity_with_property_with_default_value.md#entity_with_property_with_default_value)
+* [entity_with_required_property](entity_with_required_property.md#entity_with_required_property)
+* [entity_with_simple_properties](entity_with_simple_properties.md#entity_with_simple_properties)
+* [entity_with_strict_array](entity_with_strict_array.md#entity_with_strict_array)
+* [entity_with_string_array_property](entity_with_string_array_property.md#entity_with_string_array_property)
+* [entity_with_string_enum_property](entity_with_string_enum_property.md#entity_with_string_enum_property)
+* [entity_with_string_enum_property_with_default_value](entity_with_string_enum_property_with_default_value.md#entity_with_string_enum_property_with_default_value)
+* [entity_without_properties](entity_without_properties.md#entity_without_properties)
diff --git a/api_generator/tests/references/documentation/ru/entity_with_array.md b/api_generator/tests/references/documentation/ru/entity_with_array.md
new file mode 100644
index 000000000..27ba3b625
--- /dev/null
+++ b/api_generator/tests/references/documentation/ru/entity_with_array.md
@@ -0,0 +1,16 @@
+# entity_with_array
+Описания пока нет
+
+## JSON
+```json
+{
+ type*: "entity_with_array",
+ array*: [ entity, ... ]
+}
+```
+
+## Параметры
+| Параметры | Описание |
+| --- | --- |
+| `array` | **array**
Обязательный параметр.
Массив должен быть непустым.
Список возможных значений:
[entity_with_array](entity_with_array.md#entity_with_array)[entity_with_array_of_nested_items](entity_with_array_of_nested_items.md#entity_with_array_of_nested_items)[entity_with_array_with_transform](entity_with_array_with_transform.md#entity_with_array_with_transform)[entity_with_complex_property](entity_with_complex_property.md#entity_with_complex_property)[entity_with_complex_property_with_default_value](entity_with_complex_property_with_default_value.md#entity_with_complex_property_with_default_value)[entity_with_entity_property](entity_with_entity_property.md#entity_with_entity_property)[entity_with_optional_complex_property](entity_with_optional_complex_property.md#entity_with_optional_complex_property)[entity_with_optional_property](entity_with_optional_property.md#entity_with_optional_property)[entity_with_optional_string_enum_property](entity_with_optional_string_enum_property.md#entity_with_optional_string_enum_property)[entity_with_property_with_default_value](entity_with_property_with_default_value.md#entity_with_property_with_default_value)[entity_with_required_property](entity_with_required_property.md#entity_with_required_property)[entity_with_simple_properties](entity_with_simple_properties.md#entity_with_simple_properties)[entity_with_strict_array](entity_with_strict_array.md#entity_with_strict_array)[entity_with_string_array_property](entity_with_string_array_property.md#entity_with_string_array_property)[entity_with_string_enum_property](entity_with_string_enum_property.md#entity_with_string_enum_property)[entity_with_string_enum_property_with_default_value](entity_with_string_enum_property_with_default_value.md#entity_with_string_enum_property_with_default_value)[entity_without_properties](entity_without_properties.md#entity_without_properties) |
+| `type` | **string**
Обязательный параметр.
Значение всегда должно равняться `entity_with_array`.
|
diff --git a/api_generator/tests/references/documentation/ru/entity_with_array_of_nested_items.md b/api_generator/tests/references/documentation/ru/entity_with_array_of_nested_items.md
new file mode 100644
index 000000000..4ac5fe58d
--- /dev/null
+++ b/api_generator/tests/references/documentation/ru/entity_with_array_of_nested_items.md
@@ -0,0 +1,28 @@
+# entity_with_array_of_nested_items
+Описания пока нет
+
+## JSON
+```json
+{
+ type*: "entity_with_array_of_nested_items",
+ items*: [
+ {
+ entity*: entity,
+ property*: "string"
+ },
+ ...
+ ]
+}
+```
+
+## Параметры
+| Параметры | Описание |
+| --- | --- |
+| `items` | **array**
Обязательный параметр.
Массив должен быть непустым.
|
+| `type` | **string**
Обязательный параметр.
Значение всегда должно равняться `entity_with_array_of_nested_items`.
|
+
+### item
+| Параметры | Описание |
+| --- | --- |
+| `entity` | **object**
Обязательный параметр.
Значение имеет тип `entity`.
Список возможных значений:
[entity_with_array](entity_with_array.md#entity_with_array)[entity_with_array_of_nested_items](entity_with_array_of_nested_items.md#entity_with_array_of_nested_items)[entity_with_array_with_transform](entity_with_array_with_transform.md#entity_with_array_with_transform)[entity_with_complex_property](entity_with_complex_property.md#entity_with_complex_property)[entity_with_complex_property_with_default_value](entity_with_complex_property_with_default_value.md#entity_with_complex_property_with_default_value)[entity_with_entity_property](entity_with_entity_property.md#entity_with_entity_property)[entity_with_optional_complex_property](entity_with_optional_complex_property.md#entity_with_optional_complex_property)[entity_with_optional_property](entity_with_optional_property.md#entity_with_optional_property)[entity_with_optional_string_enum_property](entity_with_optional_string_enum_property.md#entity_with_optional_string_enum_property)[entity_with_property_with_default_value](entity_with_property_with_default_value.md#entity_with_property_with_default_value)[entity_with_required_property](entity_with_required_property.md#entity_with_required_property)[entity_with_simple_properties](entity_with_simple_properties.md#entity_with_simple_properties)[entity_with_strict_array](entity_with_strict_array.md#entity_with_strict_array)[entity_with_string_array_property](entity_with_string_array_property.md#entity_with_string_array_property)[entity_with_string_enum_property](entity_with_string_enum_property.md#entity_with_string_enum_property)[entity_with_string_enum_property_with_default_value](entity_with_string_enum_property_with_default_value.md#entity_with_string_enum_property_with_default_value)[entity_without_properties](entity_without_properties.md#entity_without_properties) |
+| `property` | **string**
Обязательный параметр.
Строка должна быть непустой.
|
diff --git a/api_generator/tests/references/documentation/ru/entity_with_array_with_transform.md b/api_generator/tests/references/documentation/ru/entity_with_array_with_transform.md
new file mode 100644
index 000000000..beefb24fd
--- /dev/null
+++ b/api_generator/tests/references/documentation/ru/entity_with_array_with_transform.md
@@ -0,0 +1,16 @@
+# entity_with_array_with_transform
+Описания пока нет
+
+## JSON
+```json
+{
+ type*: "entity_with_array_with_transform",
+ array*: [ "string", ... ]
+}
+```
+
+## Параметры
+| Параметры | Описание |
+| --- | --- |
+| `array` | **array**
Обязательный параметр.
Массив должен быть непустым.
|
+| `type` | **string**
Обязательный параметр.
Значение всегда должно равняться `entity_with_array_with_transform`.
|
diff --git a/api_generator/tests/references/documentation/ru/entity_with_complex_property.md b/api_generator/tests/references/documentation/ru/entity_with_complex_property.md
new file mode 100644
index 000000000..75e1f2af3
--- /dev/null
+++ b/api_generator/tests/references/documentation/ru/entity_with_complex_property.md
@@ -0,0 +1,23 @@
+# entity_with_complex_property
+Описания пока нет
+
+## JSON
+```json
+{
+ type*: "entity_with_complex_property",
+ property*: {
+ value*: "string"
+ }
+}
+```
+
+## Параметры
+| Параметры | Описание |
+| --- | --- |
+| `property` | **object**
Обязательный параметр.
Значение имеет тип `property`.
|
+| `type` | **string**
Обязательный параметр.
Значение всегда должно равняться `entity_with_complex_property`.
|
+
+### property
+| Параметры | Описание |
+| --- | --- |
+| `value` | **string**
Обязательный параметр.
Значение должно быть валидным URL.
|
diff --git a/api_generator/tests/references/documentation/ru/entity_with_complex_property_with_default_value.md b/api_generator/tests/references/documentation/ru/entity_with_complex_property_with_default_value.md
new file mode 100644
index 000000000..32ee1c07f
--- /dev/null
+++ b/api_generator/tests/references/documentation/ru/entity_with_complex_property_with_default_value.md
@@ -0,0 +1,23 @@
+# entity_with_complex_property_with_default_value
+Описания пока нет
+
+## JSON
+```json
+{
+ type*: "entity_with_complex_property_with_default_value",
+ property: {
+ value*: "string"
+ }
+}
+```
+
+## Параметры
+| Параметры | Описание |
+| --- | --- |
+| `property` | **object**
Значение имеет тип `property`.
Значение по умолчанию: `{ "value": "Default text" }`.
|
+| `type` | **string**
Обязательный параметр.
Значение всегда должно равняться `entity_with_complex_property_with_default_value`.
|
+
+### property
+| Параметры | Описание |
+| --- | --- |
+| `value` | **string**
Обязательный параметр.
|
diff --git a/api_generator/tests/references/documentation/ru/entity_with_entity_property.md b/api_generator/tests/references/documentation/ru/entity_with_entity_property.md
new file mode 100644
index 000000000..cc3155bd5
--- /dev/null
+++ b/api_generator/tests/references/documentation/ru/entity_with_entity_property.md
@@ -0,0 +1,16 @@
+# entity_with_entity_property
+Описания пока нет
+
+## JSON
+```json
+{
+ type*: "entity_with_entity_property",
+ entity: entity
+}
+```
+
+## Параметры
+| Параметры | Описание |
+| --- | --- |
+| `entity` | **object**
Значение имеет тип `entity`.
Значение по умолчанию: `{ "type": "entity_with_string_enum_property", "property": "second" }`.
Список возможных значений:
[entity_with_array](entity_with_array.md#entity_with_array)[entity_with_array_of_nested_items](entity_with_array_of_nested_items.md#entity_with_array_of_nested_items)[entity_with_array_with_transform](entity_with_array_with_transform.md#entity_with_array_with_transform)[entity_with_complex_property](entity_with_complex_property.md#entity_with_complex_property)[entity_with_complex_property_with_default_value](entity_with_complex_property_with_default_value.md#entity_with_complex_property_with_default_value)[entity_with_entity_property](entity_with_entity_property.md#entity_with_entity_property)[entity_with_optional_complex_property](entity_with_optional_complex_property.md#entity_with_optional_complex_property)[entity_with_optional_property](entity_with_optional_property.md#entity_with_optional_property)[entity_with_optional_string_enum_property](entity_with_optional_string_enum_property.md#entity_with_optional_string_enum_property)[entity_with_property_with_default_value](entity_with_property_with_default_value.md#entity_with_property_with_default_value)[entity_with_required_property](entity_with_required_property.md#entity_with_required_property)[entity_with_simple_properties](entity_with_simple_properties.md#entity_with_simple_properties)[entity_with_strict_array](entity_with_strict_array.md#entity_with_strict_array)[entity_with_string_array_property](entity_with_string_array_property.md#entity_with_string_array_property)[entity_with_string_enum_property](entity_with_string_enum_property.md#entity_with_string_enum_property)[entity_with_string_enum_property_with_default_value](entity_with_string_enum_property_with_default_value.md#entity_with_string_enum_property_with_default_value)[entity_without_properties](entity_without_properties.md#entity_without_properties) |
+| `type` | **string**
Обязательный параметр.
Значение всегда должно равняться `entity_with_entity_property`.
|
diff --git a/api_generator/tests/references/documentation/ru/entity_with_optional_complex_property.md b/api_generator/tests/references/documentation/ru/entity_with_optional_complex_property.md
new file mode 100644
index 000000000..519c060e9
--- /dev/null
+++ b/api_generator/tests/references/documentation/ru/entity_with_optional_complex_property.md
@@ -0,0 +1,23 @@
+# entity_with_optional_complex_property
+Описания пока нет
+
+## JSON
+```json
+{
+ type*: "entity_with_optional_complex_property",
+ property: {
+ value*: "string"
+ }
+}
+```
+
+## Параметры
+| Параметры | Описание |
+| --- | --- |
+| `property` | **object**
Значение имеет тип `property`.
|
+| `type` | **string**
Обязательный параметр.
Значение всегда должно равняться `entity_with_optional_complex_property`.
|
+
+### property
+| Параметры | Описание |
+| --- | --- |
+| `value` | **string**
Обязательный параметр.
Значение должно быть валидным URL.
|
diff --git a/api_generator/tests/references/documentation/ru/entity_with_optional_property.md b/api_generator/tests/references/documentation/ru/entity_with_optional_property.md
new file mode 100644
index 000000000..4e53e4bd0
--- /dev/null
+++ b/api_generator/tests/references/documentation/ru/entity_with_optional_property.md
@@ -0,0 +1,16 @@
+# entity_with_optional_property
+Описания пока нет
+
+## JSON
+```json
+{
+ type*: "entity_with_optional_property",
+ property: "string"
+}
+```
+
+## Параметры
+| Параметры | Описание |
+| --- | --- |
+| `property` | **string**
Строка должна быть непустой.
|
+| `type` | **string**
Обязательный параметр.
Значение всегда должно равняться `entity_with_optional_property`.
|
diff --git a/api_generator/tests/references/documentation/ru/entity_with_optional_string_enum_property.md b/api_generator/tests/references/documentation/ru/entity_with_optional_string_enum_property.md
new file mode 100644
index 000000000..197f8ced2
--- /dev/null
+++ b/api_generator/tests/references/documentation/ru/entity_with_optional_string_enum_property.md
@@ -0,0 +1,16 @@
+# entity_with_optional_string_enum_property
+Описания пока нет
+
+## JSON
+```json
+{
+ type*: "entity_with_optional_string_enum_property",
+ property: "string"
+}
+```
+
+## Параметры
+| Параметры | Описание |
+| --- | --- |
+| `property` | **string**
Возможные значения: `first`, `second`.
|
+| `type` | **string**
Обязательный параметр.
Значение всегда должно равняться `entity_with_optional_string_enum_property`.
|
diff --git a/api_generator/tests/references/documentation/ru/entity_with_property_with_default_value.md b/api_generator/tests/references/documentation/ru/entity_with_property_with_default_value.md
new file mode 100644
index 000000000..588dcbdfb
--- /dev/null
+++ b/api_generator/tests/references/documentation/ru/entity_with_property_with_default_value.md
@@ -0,0 +1,31 @@
+# entity_with_property_with_default_value
+Описания пока нет
+
+## JSON
+```json
+{
+ type*: "entity_with_property_with_default_value",
+ int: "int",
+ nested: {
+ int: "int",
+ non_optional*: "string",
+ url: "string"
+ },
+ url: "string"
+}
+```
+
+## Параметры
+| Параметры | Описание |
+| --- | --- |
+| `int` | **int**
Ограничение для значения `x`: `x >= 0`.
Значение по умолчанию: `0`.
|
+| `nested` | **object**
non_optional используется, чтобы запретить автогенерацию дефолтного значения для объекта, все свойства которого опциональны.
Значение имеет тип `nested`.
|
+| `type` | **string**
Обязательный параметр.
Значение всегда должно равняться `entity_with_property_with_default_value`.
|
+| `url` | **string**
Значение должно быть валидным URL.
Разрешенные схемы: `https`.
Значение по умолчанию: `https://yandex.ru`.
|
+
+### nested
+| Параметры | Описание |
+| --- | --- |
+| `int` | **int**
Ограничение для значения `x`: `x >= 0`.
Значение по умолчанию: `0`.
|
+| `non_optional` | **string**
Обязательный параметр.
|
+| `url` | **string**
Значение должно быть валидным URL.
Разрешенные схемы: `https`.
Значение по умолчанию: `https://yandex.ru`.
|
diff --git a/api_generator/tests/references/documentation/ru/entity_with_required_property.md b/api_generator/tests/references/documentation/ru/entity_with_required_property.md
new file mode 100644
index 000000000..904aebe88
--- /dev/null
+++ b/api_generator/tests/references/documentation/ru/entity_with_required_property.md
@@ -0,0 +1,16 @@
+# entity_with_required_property
+Описания пока нет
+
+## JSON
+```json
+{
+ type*: "entity_with_required_property",
+ property*: "string"
+}
+```
+
+## Параметры
+| Параметры | Описание |
+| --- | --- |
+| `property` | **string**
Обязательный параметр.
Строка должна быть непустой.
|
+| `type` | **string**
Обязательный параметр.
Значение всегда должно равняться `entity_with_required_property`.
|
diff --git a/api_generator/tests/references/documentation/ru/entity_with_simple_properties.md b/api_generator/tests/references/documentation/ru/entity_with_simple_properties.md
new file mode 100644
index 000000000..40a14f456
--- /dev/null
+++ b/api_generator/tests/references/documentation/ru/entity_with_simple_properties.md
@@ -0,0 +1,32 @@
+# entity_with_simple_properties
+Объект с простыми свойствами.
+
+## JSON
+```json
+{
+ type*: "entity_with_simple_properties",
+ boolean: "bool",
+ boolean_int: "bool_int",
+ color: "string",
+ double: "number",
+ id: "int",
+ integer: "int",
+ positive_integer: "int",
+ string: "string",
+ url: "string"
+}
+```
+
+## Параметры
+| Параметры | Описание |
+| --- | --- |
+| `boolean` | **bool**
Логическое значение.
|
+| `boolean_int` | **bool_int**
Параметр устарел.
Логическое значение в числовом формате.
Доступные платформы: Android.
|
+| `color` | **string**
Цвет.
Допустимые форматы: `#RGB`, `#ARGB`, `#RRGGBB`, `#AARRGGBB`.
|
+| `double` | **number**
Число с плавающей точкой.
|
+| `id` | **int**
Идентификатор. Не может содержать выражение.
|
+| `integer` | **int**
Целое число.
|
+| `positive_integer` | **int**
Положительное целое число.
Ограничение для значения `x`: `x > 0`.
Функциональность находится в разработке.
|
+| `string` | **string**
Строка.
Строка должна быть непустой.
Доступные платформы: Android, iOS, браузер.
|
+| `type` | **string**
Обязательный параметр.
Значение всегда должно равняться `entity_with_simple_properties`.
|
+| `url` | **string**
Значение должно быть валидным URL.
|
diff --git a/api_generator/tests/references/documentation/ru/entity_with_strict_array.md b/api_generator/tests/references/documentation/ru/entity_with_strict_array.md
new file mode 100644
index 000000000..669af44ab
--- /dev/null
+++ b/api_generator/tests/references/documentation/ru/entity_with_strict_array.md
@@ -0,0 +1,16 @@
+# entity_with_strict_array
+Описания пока нет
+
+## JSON
+```json
+{
+ type*: "entity_with_strict_array",
+ array*: [ entity, ... ]
+}
+```
+
+## Параметры
+| Параметры | Описание |
+| --- | --- |
+| `array` | **array**
Обязательный параметр.
Массив должен быть непустым.
Не допускается частичный парсинг.
Список возможных значений:
[entity_with_array](entity_with_array.md#entity_with_array)[entity_with_array_of_nested_items](entity_with_array_of_nested_items.md#entity_with_array_of_nested_items)[entity_with_array_with_transform](entity_with_array_with_transform.md#entity_with_array_with_transform)[entity_with_complex_property](entity_with_complex_property.md#entity_with_complex_property)[entity_with_complex_property_with_default_value](entity_with_complex_property_with_default_value.md#entity_with_complex_property_with_default_value)[entity_with_entity_property](entity_with_entity_property.md#entity_with_entity_property)[entity_with_optional_complex_property](entity_with_optional_complex_property.md#entity_with_optional_complex_property)[entity_with_optional_property](entity_with_optional_property.md#entity_with_optional_property)[entity_with_optional_string_enum_property](entity_with_optional_string_enum_property.md#entity_with_optional_string_enum_property)[entity_with_property_with_default_value](entity_with_property_with_default_value.md#entity_with_property_with_default_value)[entity_with_required_property](entity_with_required_property.md#entity_with_required_property)[entity_with_simple_properties](entity_with_simple_properties.md#entity_with_simple_properties)[entity_with_strict_array](entity_with_strict_array.md#entity_with_strict_array)[entity_with_string_array_property](entity_with_string_array_property.md#entity_with_string_array_property)[entity_with_string_enum_property](entity_with_string_enum_property.md#entity_with_string_enum_property)[entity_with_string_enum_property_with_default_value](entity_with_string_enum_property_with_default_value.md#entity_with_string_enum_property_with_default_value)[entity_without_properties](entity_without_properties.md#entity_without_properties) |
+| `type` | **string**
Обязательный параметр.
Значение всегда должно равняться `entity_with_strict_array`.
|
diff --git a/api_generator/tests/references/documentation/ru/entity_with_string_array_property.md b/api_generator/tests/references/documentation/ru/entity_with_string_array_property.md
new file mode 100644
index 000000000..0a1a6134d
--- /dev/null
+++ b/api_generator/tests/references/documentation/ru/entity_with_string_array_property.md
@@ -0,0 +1,16 @@
+# entity_with_string_array_property
+Описания пока нет
+
+## JSON
+```json
+{
+ type*: "entity_with_string_array_property",
+ array*: [ "string", ... ]
+}
+```
+
+## Параметры
+| Параметры | Описание |
+| --- | --- |
+| `array` | **array**
Обязательный параметр.
Массив должен быть непустым.
|
+| `type` | **string**
Обязательный параметр.
Значение всегда должно равняться `entity_with_string_array_property`.
|
diff --git a/api_generator/tests/references/documentation/ru/entity_with_string_enum_property.md b/api_generator/tests/references/documentation/ru/entity_with_string_enum_property.md
new file mode 100644
index 000000000..ebcbbcf45
--- /dev/null
+++ b/api_generator/tests/references/documentation/ru/entity_with_string_enum_property.md
@@ -0,0 +1,16 @@
+# entity_with_string_enum_property
+Описания пока нет
+
+## JSON
+```json
+{
+ type*: "entity_with_string_enum_property",
+ property*: "string"
+}
+```
+
+## Параметры
+| Параметры | Описание |
+| --- | --- |
+| `property` | **string**
Обязательный параметр.
Возможные значения: `first`, `second`.
|
+| `type` | **string**
Обязательный параметр.
Значение всегда должно равняться `entity_with_string_enum_property`.
|
diff --git a/api_generator/tests/references/documentation/ru/entity_with_string_enum_property_with_default_value.md b/api_generator/tests/references/documentation/ru/entity_with_string_enum_property_with_default_value.md
new file mode 100644
index 000000000..18ef1a60b
--- /dev/null
+++ b/api_generator/tests/references/documentation/ru/entity_with_string_enum_property_with_default_value.md
@@ -0,0 +1,16 @@
+# entity_with_string_enum_property_with_default_value
+Описания пока нет
+
+## JSON
+```json
+{
+ type*: "entity_with_string_enum_property_with_default_value",
+ value: "string"
+}
+```
+
+## Параметры
+| Параметры | Описание |
+| --- | --- |
+| `type` | **string**
Обязательный параметр.
Значение всегда должно равняться `entity_with_string_enum_property_with_default_value`.
|
+| `value` | **string**
Возможные значения: `first`, `second`, `third`.
Значение по умолчанию: `second`.
|
diff --git a/api_generator/tests/references/documentation/ru/entity_without_properties.md b/api_generator/tests/references/documentation/ru/entity_without_properties.md
new file mode 100644
index 000000000..0abd92f2d
--- /dev/null
+++ b/api_generator/tests/references/documentation/ru/entity_without_properties.md
@@ -0,0 +1,14 @@
+# entity_without_properties
+Описания пока нет
+
+## JSON
+```json
+{
+ type*: "entity_without_properties"
+}
+```
+
+## Параметры
+| Параметры | Описание |
+| --- | --- |
+| `type` | **string**
Обязательный параметр.
Значение всегда должно равняться `entity_without_properties`.
|
diff --git a/api_generator/tests/references/documentation/toc.yaml b/api_generator/tests/references/documentation/toc.yaml
new file mode 100644
index 000000000..f16fdd1d6
--- /dev/null
+++ b/api_generator/tests/references/documentation/toc.yaml
@@ -0,0 +1,57 @@
+title:
+href:
+items:
+ -name: entity
+ href: concepts/divs/entity.md
+ hidden: true
+ -name: entity_with_array
+ href: concepts/divs/entity_with_array.md
+ hidden: true
+ -name: entity_with_array_of_nested_items
+ href: concepts/divs/entity_with_array_of_nested_items.md
+ hidden: true
+ -name: entity_with_array_with_transform
+ href: concepts/divs/entity_with_array_with_transform.md
+ hidden: true
+ -name: entity_with_complex_property
+ href: concepts/divs/entity_with_complex_property.md
+ hidden: true
+ -name: entity_with_complex_property_with_default_value
+ href: concepts/divs/entity_with_complex_property_with_default_value.md
+ hidden: true
+ -name: entity_with_entity_property
+ href: concepts/divs/entity_with_entity_property.md
+ hidden: true
+ -name: entity_with_optional_complex_property
+ href: concepts/divs/entity_with_optional_complex_property.md
+ hidden: true
+ -name: entity_with_optional_property
+ href: concepts/divs/entity_with_optional_property.md
+ hidden: true
+ -name: entity_with_optional_string_enum_property
+ href: concepts/divs/entity_with_optional_string_enum_property.md
+ hidden: true
+ -name: entity_with_required_property
+ href: concepts/divs/entity_with_required_property.md
+ hidden: true
+ -name: entity_with_strict_array
+ href: concepts/divs/entity_with_strict_array.md
+ hidden: true
+ -name: entity_with_string_array_property
+ href: concepts/divs/entity_with_string_array_property.md
+ hidden: true
+ -name: entity_with_string_enum_property
+ href: concepts/divs/entity_with_string_enum_property.md
+ hidden: true
+ -name: entity_with_string_enum_property_with_default_value
+ href: concepts/divs/entity_with_string_enum_property_with_default_value.md
+ hidden: true
+ -name: entity_without_properties
+ href: concepts/divs/entity_without_properties.md
+ hidden: true
+ -name: entity_with_property_with_default_value
+ href: concepts/divs/entity_with_property_with_default_value.md
+ hidden: true
+ -name: entity_with_simple_properties
+ href: concepts/divs/entity_with_simple_properties.md
+ hidden: true
\ No newline at end of file
diff --git a/api_generator/tests/references/kotlin/Entity.kt b/api_generator/tests/references/kotlin/Entity.kt
new file mode 100644
index 000000000..73e97c51c
--- /dev/null
+++ b/api_generator/tests/references/kotlin/Entity.kt
@@ -0,0 +1,117 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+sealed class Entity : JSONSerializable {
+ class WithArray(val value: EntityWithArray) : Entity()
+ class WithArrayOfNestedItems(val value: EntityWithArrayOfNestedItems) : Entity()
+ class WithArrayWithTransform(val value: EntityWithArrayWithTransform) : Entity()
+ class WithComplexProperty(val value: EntityWithComplexProperty) : Entity()
+ class WithComplexPropertyWithDefaultValue(val value: EntityWithComplexPropertyWithDefaultValue) : Entity()
+ class WithEntityProperty(val value: EntityWithEntityProperty) : Entity()
+ class WithOptionalComplexProperty(val value: EntityWithOptionalComplexProperty) : Entity()
+ class WithOptionalProperty(val value: EntityWithOptionalProperty) : Entity()
+ class WithOptionalStringEnumProperty(val value: EntityWithOptionalStringEnumProperty) : Entity()
+ class WithPropertyWithDefaultValue(val value: EntityWithPropertyWithDefaultValue) : Entity()
+ class WithRequiredProperty(val value: EntityWithRequiredProperty) : Entity()
+ class WithSimpleProperties(val value: EntityWithSimpleProperties) : Entity()
+ class WithStrictArray(val value: EntityWithStrictArray) : Entity()
+ class WithStringArrayProperty(val value: EntityWithStringArrayProperty) : Entity()
+ class WithStringEnumProperty(val value: EntityWithStringEnumProperty) : Entity()
+ class WithStringEnumPropertyWithDefaultValue(val value: EntityWithStringEnumPropertyWithDefaultValue) : Entity()
+ class WithoutProperties(val value: EntityWithoutProperties) : Entity()
+
+ fun value(): Any {
+ return when (this) {
+ is WithArray -> value
+ is WithArrayOfNestedItems -> value
+ is WithArrayWithTransform -> value
+ is WithComplexProperty -> value
+ is WithComplexPropertyWithDefaultValue -> value
+ is WithEntityProperty -> value
+ is WithOptionalComplexProperty -> value
+ is WithOptionalProperty -> value
+ is WithOptionalStringEnumProperty -> value
+ is WithPropertyWithDefaultValue -> value
+ is WithRequiredProperty -> value
+ is WithSimpleProperties -> value
+ is WithStrictArray -> value
+ is WithStringArrayProperty -> value
+ is WithStringEnumProperty -> value
+ is WithStringEnumPropertyWithDefaultValue -> value
+ is WithoutProperties -> value
+ }
+ }
+
+ override fun writeToJSON(): JSONObject {
+ return when (this) {
+ is WithArray -> value.writeToJSON()
+ is WithArrayOfNestedItems -> value.writeToJSON()
+ is WithArrayWithTransform -> value.writeToJSON()
+ is WithComplexProperty -> value.writeToJSON()
+ is WithComplexPropertyWithDefaultValue -> value.writeToJSON()
+ is WithEntityProperty -> value.writeToJSON()
+ is WithOptionalComplexProperty -> value.writeToJSON()
+ is WithOptionalProperty -> value.writeToJSON()
+ is WithOptionalStringEnumProperty -> value.writeToJSON()
+ is WithPropertyWithDefaultValue -> value.writeToJSON()
+ is WithRequiredProperty -> value.writeToJSON()
+ is WithSimpleProperties -> value.writeToJSON()
+ is WithStrictArray -> value.writeToJSON()
+ is WithStringArrayProperty -> value.writeToJSON()
+ is WithStringEnumProperty -> value.writeToJSON()
+ is WithStringEnumPropertyWithDefaultValue -> value.writeToJSON()
+ is WithoutProperties -> value.writeToJSON()
+ }
+ }
+
+ companion object {
+ @Throws(ParsingException::class)
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): Entity {
+ val logger = env.logger
+ val type: String = json.read("type", logger = logger, env = env)
+ when (type) {
+ EntityWithArray.TYPE -> return WithArray(EntityWithArray(env, json))
+ EntityWithArrayOfNestedItems.TYPE -> return WithArrayOfNestedItems(EntityWithArrayOfNestedItems(env, json))
+ EntityWithArrayWithTransform.TYPE -> return WithArrayWithTransform(EntityWithArrayWithTransform(env, json))
+ EntityWithComplexProperty.TYPE -> return WithComplexProperty(EntityWithComplexProperty(env, json))
+ EntityWithComplexPropertyWithDefaultValue.TYPE -> return WithComplexPropertyWithDefaultValue(EntityWithComplexPropertyWithDefaultValue(env, json))
+ EntityWithEntityProperty.TYPE -> return WithEntityProperty(EntityWithEntityProperty(env, json))
+ EntityWithOptionalComplexProperty.TYPE -> return WithOptionalComplexProperty(EntityWithOptionalComplexProperty(env, json))
+ EntityWithOptionalProperty.TYPE -> return WithOptionalProperty(EntityWithOptionalProperty(env, json))
+ EntityWithOptionalStringEnumProperty.TYPE -> return WithOptionalStringEnumProperty(EntityWithOptionalStringEnumProperty(env, json))
+ EntityWithPropertyWithDefaultValue.TYPE -> return WithPropertyWithDefaultValue(EntityWithPropertyWithDefaultValue(env, json))
+ EntityWithRequiredProperty.TYPE -> return WithRequiredProperty(EntityWithRequiredProperty(env, json))
+ EntityWithSimpleProperties.TYPE -> return WithSimpleProperties(EntityWithSimpleProperties(env, json))
+ EntityWithStrictArray.TYPE -> return WithStrictArray(EntityWithStrictArray(env, json))
+ EntityWithStringArrayProperty.TYPE -> return WithStringArrayProperty(EntityWithStringArrayProperty(env, json))
+ EntityWithStringEnumProperty.TYPE -> return WithStringEnumProperty(EntityWithStringEnumProperty(env, json))
+ EntityWithStringEnumPropertyWithDefaultValue.TYPE -> return WithStringEnumPropertyWithDefaultValue(EntityWithStringEnumPropertyWithDefaultValue(env, json))
+ EntityWithoutProperties.TYPE -> return WithoutProperties(EntityWithoutProperties(env, json))
+ }
+ val template = env.templates.getOrThrow(type, json) as? EntityTemplate
+ if (template != null) {
+ return template.resolve(env, json)
+ } else {
+ throw typeMismatch(json = json, key = "type", value = type)
+ }
+ }
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> Entity(env, json = it) }
+ }
+}
diff --git a/api_generator/tests/references/kotlin/EntityTemplate.kt b/api_generator/tests/references/kotlin/EntityTemplate.kt
new file mode 100644
index 000000000..f97cb07b7
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityTemplate.kt
@@ -0,0 +1,161 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+sealed class EntityTemplate : JSONSerializable, JsonTemplate {
+ class WithArray(val value: EntityWithArrayTemplate) : EntityTemplate()
+ class WithArrayOfNestedItems(val value: EntityWithArrayOfNestedItemsTemplate) : EntityTemplate()
+ class WithArrayWithTransform(val value: EntityWithArrayWithTransformTemplate) : EntityTemplate()
+ class WithComplexProperty(val value: EntityWithComplexPropertyTemplate) : EntityTemplate()
+ class WithComplexPropertyWithDefaultValue(val value: EntityWithComplexPropertyWithDefaultValueTemplate) : EntityTemplate()
+ class WithEntityProperty(val value: EntityWithEntityPropertyTemplate) : EntityTemplate()
+ class WithOptionalComplexProperty(val value: EntityWithOptionalComplexPropertyTemplate) : EntityTemplate()
+ class WithOptionalProperty(val value: EntityWithOptionalPropertyTemplate) : EntityTemplate()
+ class WithOptionalStringEnumProperty(val value: EntityWithOptionalStringEnumPropertyTemplate) : EntityTemplate()
+ class WithPropertyWithDefaultValue(val value: EntityWithPropertyWithDefaultValueTemplate) : EntityTemplate()
+ class WithRequiredProperty(val value: EntityWithRequiredPropertyTemplate) : EntityTemplate()
+ class WithSimpleProperties(val value: EntityWithSimplePropertiesTemplate) : EntityTemplate()
+ class WithStrictArray(val value: EntityWithStrictArrayTemplate) : EntityTemplate()
+ class WithStringArrayProperty(val value: EntityWithStringArrayPropertyTemplate) : EntityTemplate()
+ class WithStringEnumProperty(val value: EntityWithStringEnumPropertyTemplate) : EntityTemplate()
+ class WithStringEnumPropertyWithDefaultValue(val value: EntityWithStringEnumPropertyWithDefaultValueTemplate) : EntityTemplate()
+ class WithoutProperties(val value: EntityWithoutPropertiesTemplate) : EntityTemplate()
+
+ fun value(): Any {
+ return when (this) {
+ is WithArray -> value
+ is WithArrayOfNestedItems -> value
+ is WithArrayWithTransform -> value
+ is WithComplexProperty -> value
+ is WithComplexPropertyWithDefaultValue -> value
+ is WithEntityProperty -> value
+ is WithOptionalComplexProperty -> value
+ is WithOptionalProperty -> value
+ is WithOptionalStringEnumProperty -> value
+ is WithPropertyWithDefaultValue -> value
+ is WithRequiredProperty -> value
+ is WithSimpleProperties -> value
+ is WithStrictArray -> value
+ is WithStringArrayProperty -> value
+ is WithStringEnumProperty -> value
+ is WithStringEnumPropertyWithDefaultValue -> value
+ is WithoutProperties -> value
+ }
+ }
+
+ override fun writeToJSON(): JSONObject {
+ return when (this) {
+ is WithArray -> value.writeToJSON()
+ is WithArrayOfNestedItems -> value.writeToJSON()
+ is WithArrayWithTransform -> value.writeToJSON()
+ is WithComplexProperty -> value.writeToJSON()
+ is WithComplexPropertyWithDefaultValue -> value.writeToJSON()
+ is WithEntityProperty -> value.writeToJSON()
+ is WithOptionalComplexProperty -> value.writeToJSON()
+ is WithOptionalProperty -> value.writeToJSON()
+ is WithOptionalStringEnumProperty -> value.writeToJSON()
+ is WithPropertyWithDefaultValue -> value.writeToJSON()
+ is WithRequiredProperty -> value.writeToJSON()
+ is WithSimpleProperties -> value.writeToJSON()
+ is WithStrictArray -> value.writeToJSON()
+ is WithStringArrayProperty -> value.writeToJSON()
+ is WithStringEnumProperty -> value.writeToJSON()
+ is WithStringEnumPropertyWithDefaultValue -> value.writeToJSON()
+ is WithoutProperties -> value.writeToJSON()
+ }
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): Entity {
+ return when (this) {
+ is WithArray -> Entity.WithArray(value.resolve(env, data))
+ is WithArrayOfNestedItems -> Entity.WithArrayOfNestedItems(value.resolve(env, data))
+ is WithArrayWithTransform -> Entity.WithArrayWithTransform(value.resolve(env, data))
+ is WithComplexProperty -> Entity.WithComplexProperty(value.resolve(env, data))
+ is WithComplexPropertyWithDefaultValue -> Entity.WithComplexPropertyWithDefaultValue(value.resolve(env, data))
+ is WithEntityProperty -> Entity.WithEntityProperty(value.resolve(env, data))
+ is WithOptionalComplexProperty -> Entity.WithOptionalComplexProperty(value.resolve(env, data))
+ is WithOptionalProperty -> Entity.WithOptionalProperty(value.resolve(env, data))
+ is WithOptionalStringEnumProperty -> Entity.WithOptionalStringEnumProperty(value.resolve(env, data))
+ is WithPropertyWithDefaultValue -> Entity.WithPropertyWithDefaultValue(value.resolve(env, data))
+ is WithRequiredProperty -> Entity.WithRequiredProperty(value.resolve(env, data))
+ is WithSimpleProperties -> Entity.WithSimpleProperties(value.resolve(env, data))
+ is WithStrictArray -> Entity.WithStrictArray(value.resolve(env, data))
+ is WithStringArrayProperty -> Entity.WithStringArrayProperty(value.resolve(env, data))
+ is WithStringEnumProperty -> Entity.WithStringEnumProperty(value.resolve(env, data))
+ is WithStringEnumPropertyWithDefaultValue -> Entity.WithStringEnumPropertyWithDefaultValue(value.resolve(env, data))
+ is WithoutProperties -> Entity.WithoutProperties(value.resolve(env, data))
+ }
+ }
+
+ val type: String
+ get() {
+ return when (this) {
+ is WithArray -> EntityWithArrayTemplate.TYPE
+ is WithArrayOfNestedItems -> EntityWithArrayOfNestedItemsTemplate.TYPE
+ is WithArrayWithTransform -> EntityWithArrayWithTransformTemplate.TYPE
+ is WithComplexProperty -> EntityWithComplexPropertyTemplate.TYPE
+ is WithComplexPropertyWithDefaultValue -> EntityWithComplexPropertyWithDefaultValueTemplate.TYPE
+ is WithEntityProperty -> EntityWithEntityPropertyTemplate.TYPE
+ is WithOptionalComplexProperty -> EntityWithOptionalComplexPropertyTemplate.TYPE
+ is WithOptionalProperty -> EntityWithOptionalPropertyTemplate.TYPE
+ is WithOptionalStringEnumProperty -> EntityWithOptionalStringEnumPropertyTemplate.TYPE
+ is WithPropertyWithDefaultValue -> EntityWithPropertyWithDefaultValueTemplate.TYPE
+ is WithRequiredProperty -> EntityWithRequiredPropertyTemplate.TYPE
+ is WithSimpleProperties -> EntityWithSimplePropertiesTemplate.TYPE
+ is WithStrictArray -> EntityWithStrictArrayTemplate.TYPE
+ is WithStringArrayProperty -> EntityWithStringArrayPropertyTemplate.TYPE
+ is WithStringEnumProperty -> EntityWithStringEnumPropertyTemplate.TYPE
+ is WithStringEnumPropertyWithDefaultValue -> EntityWithStringEnumPropertyWithDefaultValueTemplate.TYPE
+ is WithoutProperties -> EntityWithoutPropertiesTemplate.TYPE
+ }
+ }
+
+ companion object {
+ @Throws(ParsingException::class)
+ operator fun invoke(
+ env: ParsingEnvironment,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ): EntityTemplate {
+ val logger = env.logger
+ val receivedType: String = json.read("type", logger = logger, env = env)
+ val parent = env.templates[receivedType] as? EntityTemplate
+ val type = parent?.type ?: receivedType
+ when (type) {
+ EntityWithArrayTemplate.TYPE -> return WithArray(EntityWithArrayTemplate(env, parent?.value() as EntityWithArrayTemplate?, topLevel, json))
+ EntityWithArrayOfNestedItemsTemplate.TYPE -> return WithArrayOfNestedItems(EntityWithArrayOfNestedItemsTemplate(env, parent?.value() as EntityWithArrayOfNestedItemsTemplate?, topLevel, json))
+ EntityWithArrayWithTransformTemplate.TYPE -> return WithArrayWithTransform(EntityWithArrayWithTransformTemplate(env, parent?.value() as EntityWithArrayWithTransformTemplate?, topLevel, json))
+ EntityWithComplexPropertyTemplate.TYPE -> return WithComplexProperty(EntityWithComplexPropertyTemplate(env, parent?.value() as EntityWithComplexPropertyTemplate?, topLevel, json))
+ EntityWithComplexPropertyWithDefaultValueTemplate.TYPE -> return WithComplexPropertyWithDefaultValue(EntityWithComplexPropertyWithDefaultValueTemplate(env, parent?.value() as EntityWithComplexPropertyWithDefaultValueTemplate?, topLevel, json))
+ EntityWithEntityPropertyTemplate.TYPE -> return WithEntityProperty(EntityWithEntityPropertyTemplate(env, parent?.value() as EntityWithEntityPropertyTemplate?, topLevel, json))
+ EntityWithOptionalComplexPropertyTemplate.TYPE -> return WithOptionalComplexProperty(EntityWithOptionalComplexPropertyTemplate(env, parent?.value() as EntityWithOptionalComplexPropertyTemplate?, topLevel, json))
+ EntityWithOptionalPropertyTemplate.TYPE -> return WithOptionalProperty(EntityWithOptionalPropertyTemplate(env, parent?.value() as EntityWithOptionalPropertyTemplate?, topLevel, json))
+ EntityWithOptionalStringEnumPropertyTemplate.TYPE -> return WithOptionalStringEnumProperty(EntityWithOptionalStringEnumPropertyTemplate(env, parent?.value() as EntityWithOptionalStringEnumPropertyTemplate?, topLevel, json))
+ EntityWithPropertyWithDefaultValueTemplate.TYPE -> return WithPropertyWithDefaultValue(EntityWithPropertyWithDefaultValueTemplate(env, parent?.value() as EntityWithPropertyWithDefaultValueTemplate?, topLevel, json))
+ EntityWithRequiredPropertyTemplate.TYPE -> return WithRequiredProperty(EntityWithRequiredPropertyTemplate(env, parent?.value() as EntityWithRequiredPropertyTemplate?, topLevel, json))
+ EntityWithSimplePropertiesTemplate.TYPE -> return WithSimpleProperties(EntityWithSimplePropertiesTemplate(env, parent?.value() as EntityWithSimplePropertiesTemplate?, topLevel, json))
+ EntityWithStrictArrayTemplate.TYPE -> return WithStrictArray(EntityWithStrictArrayTemplate(env, parent?.value() as EntityWithStrictArrayTemplate?, topLevel, json))
+ EntityWithStringArrayPropertyTemplate.TYPE -> return WithStringArrayProperty(EntityWithStringArrayPropertyTemplate(env, parent?.value() as EntityWithStringArrayPropertyTemplate?, topLevel, json))
+ EntityWithStringEnumPropertyTemplate.TYPE -> return WithStringEnumProperty(EntityWithStringEnumPropertyTemplate(env, parent?.value() as EntityWithStringEnumPropertyTemplate?, topLevel, json))
+ EntityWithStringEnumPropertyWithDefaultValueTemplate.TYPE -> return WithStringEnumPropertyWithDefaultValue(EntityWithStringEnumPropertyWithDefaultValueTemplate(env, parent?.value() as EntityWithStringEnumPropertyWithDefaultValueTemplate?, topLevel, json))
+ EntityWithoutPropertiesTemplate.TYPE -> return WithoutProperties(EntityWithoutPropertiesTemplate(env, parent?.value() as EntityWithoutPropertiesTemplate?, topLevel, json))
+ else -> throw typeMismatch(json = json, key = "type", value = type)
+ }
+ }
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityTemplate(env, json = it) }
+ }
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithArray.kt b/api_generator/tests/references/kotlin/EntityWithArray.kt
new file mode 100644
index 000000000..296348138
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithArray.kt
@@ -0,0 +1,47 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithArray(
+ @JvmField final val array: List, // at least 1 elements
+) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.write(key = "array", value = array)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_array"
+
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): EntityWithArray {
+ val logger = env.logger
+ return EntityWithArray(
+ array = JsonParser.readList(json, "array", Entity.CREATOR, ARRAY_VALIDATOR, logger, env)
+ )
+ }
+
+ private val ARRAY_VALIDATOR = ListValidator { it: List<*> -> it.size >= 1 }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithArray(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithArrayOfNestedItems.kt b/api_generator/tests/references/kotlin/EntityWithArrayOfNestedItems.kt
new file mode 100644
index 000000000..eba5672bd
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithArrayOfNestedItems.kt
@@ -0,0 +1,79 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithArrayOfNestedItems(
+ @JvmField final val items: List- , // at least 1 elements
+) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.write(key = "items", value = items)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_array_of_nested_items"
+
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): EntityWithArrayOfNestedItems {
+ val logger = env.logger
+ return EntityWithArrayOfNestedItems(
+ items = JsonParser.readList(json, "items", Item.CREATOR, ITEMS_VALIDATOR, logger, env)
+ )
+ }
+
+ private val ITEMS_VALIDATOR = ListValidator { it: List<*> -> it.size >= 1 }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithArrayOfNestedItems(env, json = it) }
+ }
+
+
+ @Mockable
+ class Item(
+ @JvmField final val entity: Entity,
+ @JvmField final val property: Expression, // at least 1 char
+ ) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.write(key = "entity", value = entity)
+ json.writeExpression(key = "property", value = property)
+ return json
+ }
+
+ companion object {
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): Item {
+ val logger = env.logger
+ return Item(
+ entity = JsonParser.read(json, "entity", Entity.CREATOR, logger, env),
+ property = JsonParser.readExpression(json, "property", PROPERTY_VALIDATOR, logger, env, TYPE_HELPER_STRING)
+ )
+ }
+
+ private val PROPERTY_TEMPLATE_VALIDATOR = ValueValidator { it: String -> it.length >= 1 }
+ private val PROPERTY_VALIDATOR = ValueValidator { it: String -> it.length >= 1 }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> Item(env, json = it) }
+ }
+
+ }
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithArrayOfNestedItemsTemplate.kt b/api_generator/tests/references/kotlin/EntityWithArrayOfNestedItemsTemplate.kt
new file mode 100644
index 000000000..db47ee7e9
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithArrayOfNestedItemsTemplate.kt
@@ -0,0 +1,99 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithArrayOfNestedItemsTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val items: Field
> // at least 1 elements
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: EntityWithArrayOfNestedItemsTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ items = JsonTemplateParser.readListField(json, "items", topLevel, parent?.items, ItemTemplate.CREATOR, ITEMS_TEMPLATE_VALIDATOR, logger, env)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithArrayOfNestedItems {
+ return EntityWithArrayOfNestedItems(
+ items = items.resolveTemplateList(env = env, key = "items", data = data, ITEMS_VALIDATOR, reader = ITEMS_READER)
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeField(key = "items", field = items)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_array_of_nested_items"
+
+ private val ITEMS_VALIDATOR = ListValidator { it: List<*> -> it.size >= 1 }
+ private val ITEMS_TEMPLATE_VALIDATOR = ListValidator { it: List<*> -> it.size >= 1 }
+
+ val ITEMS_READER: Reader> = { key, json, env -> JsonParser.readList(json, key, EntityWithArrayOfNestedItems.Item.CREATOR, ITEMS_VALIDATOR, env.logger, env) }
+ val TYPE_READER: Reader = { key, json, env -> JsonParser.read(json, key, env.logger, env) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithArrayOfNestedItemsTemplate(env, json = it) }
+ }
+
+
+ @Mockable
+ class ItemTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val entity: Field
+ @JvmField final val property: Field> // at least 1 char
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: ItemTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ entity = JsonTemplateParser.readField(json, "entity", topLevel, parent?.entity, EntityTemplate.CREATOR, logger, env)
+ property = JsonTemplateParser.readFieldWithExpression(json, "property", topLevel, parent?.property, PROPERTY_TEMPLATE_VALIDATOR, logger, env, TYPE_HELPER_STRING)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithArrayOfNestedItems.Item {
+ return EntityWithArrayOfNestedItems.Item(
+ entity = entity.resolveTemplate(env = env, key = "entity", data = data, reader = ENTITY_READER),
+ property = property.resolve(env = env, key = "property", data = data, reader = PROPERTY_READER)
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeField(key = "entity", field = entity)
+ json.writeFieldWithExpression(key = "property", field = property)
+ return json
+ }
+
+ companion object {
+ private val PROPERTY_TEMPLATE_VALIDATOR = ValueValidator { it: String -> it.length >= 1 }
+ private val PROPERTY_VALIDATOR = ValueValidator { it: String -> it.length >= 1 }
+
+ val ENTITY_READER: Reader = { key, json, env -> JsonParser.read(json, key, Entity.CREATOR, env.logger, env) }
+ val PROPERTY_READER: Reader> = { key, json, env -> JsonParser.readExpression(json, key, PROPERTY_VALIDATOR, env.logger, env, TYPE_HELPER_STRING) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> ItemTemplate(env, json = it) }
+ }
+
+ }
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithArrayTemplate.kt b/api_generator/tests/references/kotlin/EntityWithArrayTemplate.kt
new file mode 100644
index 000000000..35c71516c
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithArrayTemplate.kt
@@ -0,0 +1,57 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithArrayTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val array: Field> // at least 1 elements
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: EntityWithArrayTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ array = JsonTemplateParser.readListField(json, "array", topLevel, parent?.array, EntityTemplate.CREATOR, ARRAY_TEMPLATE_VALIDATOR, logger, env)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithArray {
+ return EntityWithArray(
+ array = array.resolveTemplateList(env = env, key = "array", data = data, ARRAY_VALIDATOR, reader = ARRAY_READER)
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeField(key = "array", field = array)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_array"
+
+ private val ARRAY_VALIDATOR = ListValidator { it: List<*> -> it.size >= 1 }
+ private val ARRAY_TEMPLATE_VALIDATOR = ListValidator { it: List<*> -> it.size >= 1 }
+
+ val ARRAY_READER: Reader> = { key, json, env -> JsonParser.readList(json, key, Entity.CREATOR, ARRAY_VALIDATOR, env.logger, env) }
+ val TYPE_READER: Reader = { key, json, env -> JsonParser.read(json, key, env.logger, env) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithArrayTemplate(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithArrayWithTransform.kt b/api_generator/tests/references/kotlin/EntityWithArrayWithTransform.kt
new file mode 100644
index 000000000..b2c55690e
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithArrayWithTransform.kt
@@ -0,0 +1,47 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithArrayWithTransform(
+ @JvmField final val array: ExpressionsList, // at least 1 elements
+) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeExpressionsList(key = "array", value = array, converter = COLOR_INT_TO_STRING)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_array_with_transform"
+
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): EntityWithArrayWithTransform {
+ val logger = env.logger
+ return EntityWithArrayWithTransform(
+ array = JsonParser.readExpressionsList(json, "array", STRING_TO_COLOR_INT, ARRAY_VALIDATOR, logger, env, TYPE_HELPER_COLOR)
+ )
+ }
+
+ private val ARRAY_VALIDATOR = ListValidator { it: List<*> -> it.size >= 1 }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithArrayWithTransform(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithArrayWithTransformTemplate.kt b/api_generator/tests/references/kotlin/EntityWithArrayWithTransformTemplate.kt
new file mode 100644
index 000000000..730632c81
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithArrayWithTransformTemplate.kt
@@ -0,0 +1,57 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithArrayWithTransformTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val array: Field> // at least 1 elements
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: EntityWithArrayWithTransformTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ array = JsonTemplateParser.readExpressionsListField(json, "array", topLevel, parent?.array, STRING_TO_COLOR_INT, ARRAY_TEMPLATE_VALIDATOR, logger, env, TYPE_HELPER_COLOR)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithArrayWithTransform {
+ return EntityWithArrayWithTransform(
+ array = array.resolveExpressionList(env = env, key = "array", data = data, reader = ARRAY_READER)
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeExpressionsListField(key = "array", field = array, converter = COLOR_INT_TO_STRING)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_array_with_transform"
+
+ private val ARRAY_VALIDATOR = ListValidator { it: List<*> -> it.size >= 1 }
+ private val ARRAY_TEMPLATE_VALIDATOR = ListValidator { it: List<*> -> it.size >= 1 }
+
+ val ARRAY_READER: Reader> = { key, json, env -> JsonParser.readExpressionsList(json, key, STRING_TO_COLOR_INT, ARRAY_VALIDATOR, env.logger, env, TYPE_HELPER_COLOR) }
+ val TYPE_READER: Reader = { key, json, env -> JsonParser.read(json, key, env.logger, env) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithArrayWithTransformTemplate(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithComplexProperty.kt b/api_generator/tests/references/kotlin/EntityWithComplexProperty.kt
new file mode 100644
index 000000000..eaf6a7944
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithComplexProperty.kt
@@ -0,0 +1,71 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithComplexProperty(
+ @JvmField final val property: Property,
+) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.write(key = "property", value = property)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_complex_property"
+
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): EntityWithComplexProperty {
+ val logger = env.logger
+ return EntityWithComplexProperty(
+ property = JsonParser.read(json, "property", Property.CREATOR, logger, env)
+ )
+ }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithComplexProperty(env, json = it) }
+ }
+
+
+ @Mockable
+ class Property(
+ @JvmField final val value: Expression,
+ ) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeExpression(key = "value", value = value, converter = URI_TO_STRING)
+ return json
+ }
+
+ companion object {
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): Property {
+ val logger = env.logger
+ return Property(
+ value = JsonParser.readExpression(json, "value", STRING_TO_URI, logger, env, TYPE_HELPER_URI)
+ )
+ }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> Property(env, json = it) }
+ }
+
+ }
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithComplexPropertyTemplate.kt b/api_generator/tests/references/kotlin/EntityWithComplexPropertyTemplate.kt
new file mode 100644
index 000000000..ba71dc796
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithComplexPropertyTemplate.kt
@@ -0,0 +1,88 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithComplexPropertyTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val property: Field
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: EntityWithComplexPropertyTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ property = JsonTemplateParser.readField(json, "property", topLevel, parent?.property, PropertyTemplate.CREATOR, logger, env)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithComplexProperty {
+ return EntityWithComplexProperty(
+ property = property.resolveTemplate(env = env, key = "property", data = data, reader = PROPERTY_READER)
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeField(key = "property", field = property)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_complex_property"
+
+ val PROPERTY_READER: Reader = { key, json, env -> JsonParser.read(json, key, EntityWithComplexProperty.Property.CREATOR, env.logger, env) }
+ val TYPE_READER: Reader = { key, json, env -> JsonParser.read(json, key, env.logger, env) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithComplexPropertyTemplate(env, json = it) }
+ }
+
+
+ @Mockable
+ class PropertyTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val value: Field>
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: PropertyTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ value = JsonTemplateParser.readFieldWithExpression(json, "value", topLevel, parent?.value, STRING_TO_URI, logger, env, TYPE_HELPER_URI)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithComplexProperty.Property {
+ return EntityWithComplexProperty.Property(
+ value = value.resolve(env = env, key = "value", data = data, reader = VALUE_READER)
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeFieldWithExpression(key = "value", field = value, converter = URI_TO_STRING)
+ return json
+ }
+
+ companion object {
+ val VALUE_READER: Reader> = { key, json, env -> JsonParser.readExpression(json, key, STRING_TO_URI, env.logger, env, TYPE_HELPER_URI) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> PropertyTemplate(env, json = it) }
+ }
+
+ }
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithComplexPropertyWithDefaultValue.kt b/api_generator/tests/references/kotlin/EntityWithComplexPropertyWithDefaultValue.kt
new file mode 100644
index 000000000..e80113a32
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithComplexPropertyWithDefaultValue.kt
@@ -0,0 +1,73 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithComplexPropertyWithDefaultValue(
+ @JvmField final val property: Property = PROPERTY_DEFAULT_VALUE, // default value: EntityWithComplexPropertyWithDefaultValue.Property(value = Expression.constant("Default text"))
+) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.write(key = "property", value = property)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_complex_property_with_default_value"
+
+ private val PROPERTY_DEFAULT_VALUE = EntityWithComplexPropertyWithDefaultValue.Property(value = Expression.constant("Default text"))
+
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): EntityWithComplexPropertyWithDefaultValue {
+ val logger = env.logger
+ return EntityWithComplexPropertyWithDefaultValue(
+ property = JsonParser.readOptional(json, "property", Property.CREATOR, logger, env) ?: PROPERTY_DEFAULT_VALUE
+ )
+ }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithComplexPropertyWithDefaultValue(env, json = it) }
+ }
+
+
+ @Mockable
+ class Property(
+ @JvmField final val value: Expression,
+ ) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeExpression(key = "value", value = value)
+ return json
+ }
+
+ companion object {
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): Property {
+ val logger = env.logger
+ return Property(
+ value = JsonParser.readExpression(json, "value", logger, env, TYPE_HELPER_STRING)
+ )
+ }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> Property(env, json = it) }
+ }
+
+ }
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithComplexPropertyWithDefaultValueTemplate.kt b/api_generator/tests/references/kotlin/EntityWithComplexPropertyWithDefaultValueTemplate.kt
new file mode 100644
index 000000000..b5a669d86
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithComplexPropertyWithDefaultValueTemplate.kt
@@ -0,0 +1,90 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithComplexPropertyWithDefaultValueTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val property: Field // default value: EntityWithComplexPropertyWithDefaultValue.Property(value = Expression.constant("Default text"))
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: EntityWithComplexPropertyWithDefaultValueTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ property = JsonTemplateParser.readOptionalField(json, "property", topLevel, parent?.property, PropertyTemplate.CREATOR, logger, env)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithComplexPropertyWithDefaultValue {
+ return EntityWithComplexPropertyWithDefaultValue(
+ property = property.resolveOptionalTemplate(env = env, key = "property", data = data, reader = PROPERTY_READER) ?: PROPERTY_DEFAULT_VALUE
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeField(key = "property", field = property)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_complex_property_with_default_value"
+
+ private val PROPERTY_DEFAULT_VALUE = EntityWithComplexPropertyWithDefaultValue.Property(value = Expression.constant("Default text"))
+
+ val PROPERTY_READER: Reader = { key, json, env -> JsonParser.readOptional(json, key, EntityWithComplexPropertyWithDefaultValue.Property.CREATOR, env.logger, env) }
+ val TYPE_READER: Reader = { key, json, env -> JsonParser.read(json, key, env.logger, env) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithComplexPropertyWithDefaultValueTemplate(env, json = it) }
+ }
+
+
+ @Mockable
+ class PropertyTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val value: Field>
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: PropertyTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ value = JsonTemplateParser.readFieldWithExpression(json, "value", topLevel, parent?.value, logger, env, TYPE_HELPER_STRING)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithComplexPropertyWithDefaultValue.Property {
+ return EntityWithComplexPropertyWithDefaultValue.Property(
+ value = value.resolve(env = env, key = "value", data = data, reader = VALUE_READER)
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeFieldWithExpression(key = "value", field = value)
+ return json
+ }
+
+ companion object {
+ val VALUE_READER: Reader> = { key, json, env -> JsonParser.readExpression(json, key, env.logger, env, TYPE_HELPER_STRING) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> PropertyTemplate(env, json = it) }
+ }
+
+ }
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithEntityProperty.kt b/api_generator/tests/references/kotlin/EntityWithEntityProperty.kt
new file mode 100644
index 000000000..f0922be88
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithEntityProperty.kt
@@ -0,0 +1,47 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithEntityProperty(
+ @JvmField final val entity: Entity = ENTITY_DEFAULT_VALUE, // default value: Entity.WithStringEnumProperty(EntityWithStringEnumProperty(property = Expression.constant(EntityWithStringEnumProperty.Property.SECOND)))
+) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.write(key = "entity", value = entity)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_entity_property"
+
+ private val ENTITY_DEFAULT_VALUE = Entity.WithStringEnumProperty(EntityWithStringEnumProperty(property = Expression.constant(EntityWithStringEnumProperty.Property.SECOND)))
+
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): EntityWithEntityProperty {
+ val logger = env.logger
+ return EntityWithEntityProperty(
+ entity = JsonParser.readOptional(json, "entity", Entity.CREATOR, logger, env) ?: ENTITY_DEFAULT_VALUE
+ )
+ }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithEntityProperty(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithEntityPropertyTemplate.kt b/api_generator/tests/references/kotlin/EntityWithEntityPropertyTemplate.kt
new file mode 100644
index 000000000..fe414c7d0
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithEntityPropertyTemplate.kt
@@ -0,0 +1,56 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithEntityPropertyTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val entity: Field // default value: Entity.WithStringEnumProperty(EntityWithStringEnumProperty(property = Expression.constant(EntityWithStringEnumProperty.Property.SECOND)))
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: EntityWithEntityPropertyTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ entity = JsonTemplateParser.readOptionalField(json, "entity", topLevel, parent?.entity, EntityTemplate.CREATOR, logger, env)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithEntityProperty {
+ return EntityWithEntityProperty(
+ entity = entity.resolveOptionalTemplate(env = env, key = "entity", data = data, reader = ENTITY_READER) ?: ENTITY_DEFAULT_VALUE
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeField(key = "entity", field = entity)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_entity_property"
+
+ private val ENTITY_DEFAULT_VALUE = Entity.WithStringEnumProperty(EntityWithStringEnumProperty(property = Expression.constant(EntityWithStringEnumProperty.Property.SECOND)))
+
+ val ENTITY_READER: Reader = { key, json, env -> JsonParser.readOptional(json, key, Entity.CREATOR, env.logger, env) }
+ val TYPE_READER: Reader = { key, json, env -> JsonParser.read(json, key, env.logger, env) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithEntityPropertyTemplate(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithOptionalComplexProperty.kt b/api_generator/tests/references/kotlin/EntityWithOptionalComplexProperty.kt
new file mode 100644
index 000000000..715a6da3a
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithOptionalComplexProperty.kt
@@ -0,0 +1,71 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithOptionalComplexProperty(
+ @JvmField final val property: Property? = null,
+) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.write(key = "property", value = property)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_optional_complex_property"
+
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): EntityWithOptionalComplexProperty {
+ val logger = env.logger
+ return EntityWithOptionalComplexProperty(
+ property = JsonParser.readOptional(json, "property", Property.CREATOR, logger, env)
+ )
+ }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithOptionalComplexProperty(env, json = it) }
+ }
+
+
+ @Mockable
+ class Property(
+ @JvmField final val value: Expression,
+ ) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeExpression(key = "value", value = value, converter = URI_TO_STRING)
+ return json
+ }
+
+ companion object {
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): Property {
+ val logger = env.logger
+ return Property(
+ value = JsonParser.readExpression(json, "value", STRING_TO_URI, logger, env, TYPE_HELPER_URI)
+ )
+ }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> Property(env, json = it) }
+ }
+
+ }
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithOptionalComplexPropertyTemplate.kt b/api_generator/tests/references/kotlin/EntityWithOptionalComplexPropertyTemplate.kt
new file mode 100644
index 000000000..53283fc02
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithOptionalComplexPropertyTemplate.kt
@@ -0,0 +1,88 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithOptionalComplexPropertyTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val property: Field
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: EntityWithOptionalComplexPropertyTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ property = JsonTemplateParser.readOptionalField(json, "property", topLevel, parent?.property, PropertyTemplate.CREATOR, logger, env)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithOptionalComplexProperty {
+ return EntityWithOptionalComplexProperty(
+ property = property.resolveOptionalTemplate(env = env, key = "property", data = data, reader = PROPERTY_READER)
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeField(key = "property", field = property)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_optional_complex_property"
+
+ val PROPERTY_READER: Reader = { key, json, env -> JsonParser.readOptional(json, key, EntityWithOptionalComplexProperty.Property.CREATOR, env.logger, env) }
+ val TYPE_READER: Reader = { key, json, env -> JsonParser.read(json, key, env.logger, env) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithOptionalComplexPropertyTemplate(env, json = it) }
+ }
+
+
+ @Mockable
+ class PropertyTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val value: Field>
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: PropertyTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ value = JsonTemplateParser.readFieldWithExpression(json, "value", topLevel, parent?.value, STRING_TO_URI, logger, env, TYPE_HELPER_URI)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithOptionalComplexProperty.Property {
+ return EntityWithOptionalComplexProperty.Property(
+ value = value.resolve(env = env, key = "value", data = data, reader = VALUE_READER)
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeFieldWithExpression(key = "value", field = value, converter = URI_TO_STRING)
+ return json
+ }
+
+ companion object {
+ val VALUE_READER: Reader> = { key, json, env -> JsonParser.readExpression(json, key, STRING_TO_URI, env.logger, env, TYPE_HELPER_URI) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> PropertyTemplate(env, json = it) }
+ }
+
+ }
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithOptionalProperty.kt b/api_generator/tests/references/kotlin/EntityWithOptionalProperty.kt
new file mode 100644
index 000000000..53f20db73
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithOptionalProperty.kt
@@ -0,0 +1,48 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithOptionalProperty(
+ @JvmField final val property: Expression? = null, // at least 1 char
+) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeExpression(key = "property", value = property)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_optional_property"
+
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): EntityWithOptionalProperty {
+ val logger = env.logger
+ return EntityWithOptionalProperty(
+ property = JsonParser.readOptionalExpression(json, "property", PROPERTY_VALIDATOR, logger, env, TYPE_HELPER_STRING)
+ )
+ }
+
+ private val PROPERTY_TEMPLATE_VALIDATOR = ValueValidator { it: String -> it.length >= 1 }
+ private val PROPERTY_VALIDATOR = ValueValidator { it: String -> it.length >= 1 }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithOptionalProperty(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithOptionalPropertyTemplate.kt b/api_generator/tests/references/kotlin/EntityWithOptionalPropertyTemplate.kt
new file mode 100644
index 000000000..9be8824e0
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithOptionalPropertyTemplate.kt
@@ -0,0 +1,57 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithOptionalPropertyTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val property: Field> // at least 1 char
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: EntityWithOptionalPropertyTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ property = JsonTemplateParser.readOptionalFieldWithExpression(json, "property", topLevel, parent?.property, PROPERTY_TEMPLATE_VALIDATOR, logger, env, TYPE_HELPER_STRING)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithOptionalProperty {
+ return EntityWithOptionalProperty(
+ property = property.resolveOptional(env = env, key = "property", data = data, reader = PROPERTY_READER)
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeFieldWithExpression(key = "property", field = property)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_optional_property"
+
+ private val PROPERTY_TEMPLATE_VALIDATOR = ValueValidator { it: String -> it.length >= 1 }
+ private val PROPERTY_VALIDATOR = ValueValidator { it: String -> it.length >= 1 }
+
+ val PROPERTY_READER: Reader?> = { key, json, env -> JsonParser.readOptionalExpression(json, key, PROPERTY_VALIDATOR, env.logger, env, TYPE_HELPER_STRING) }
+ val TYPE_READER: Reader = { key, json, env -> JsonParser.read(json, key, env.logger, env) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithOptionalPropertyTemplate(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithOptionalStringEnumProperty.kt b/api_generator/tests/references/kotlin/EntityWithOptionalStringEnumProperty.kt
new file mode 100644
index 000000000..4f259d2a5
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithOptionalStringEnumProperty.kt
@@ -0,0 +1,74 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithOptionalStringEnumProperty(
+ @JvmField final val property: Expression? = null,
+) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeExpression(key = "property", value = property, converter = { v: Property -> Property.toString(v) })
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_optional_string_enum_property"
+
+ private val TYPE_HELPER_PROPERTY = TypeHelper.from(default = EntityWithOptionalStringEnumProperty.Property.values().first()) { it is EntityWithOptionalStringEnumProperty.Property }
+
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): EntityWithOptionalStringEnumProperty {
+ val logger = env.logger
+ return EntityWithOptionalStringEnumProperty(
+ property = JsonParser.readOptionalExpression(json, "property", Property.Converter.FROM_STRING, logger, env, TYPE_HELPER_PROPERTY)
+ )
+ }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithOptionalStringEnumProperty(env, json = it) }
+ }
+
+
+ enum class Property(private val value: String) {
+ FIRST("first"),
+ SECOND("second");
+
+ companion object Converter {
+ fun toString(obj: Property): String {
+ return obj.value
+ }
+
+ fun fromString(string: String): Property? {
+ return when (string) {
+ FIRST.value -> FIRST
+ SECOND.value -> SECOND
+ else -> null
+ }
+ }
+
+ val FROM_STRING = { string: String ->
+ when (string) {
+ FIRST.value -> FIRST
+ SECOND.value -> SECOND
+ else -> null
+ }
+ }
+ }
+ }
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithOptionalStringEnumPropertyTemplate.kt b/api_generator/tests/references/kotlin/EntityWithOptionalStringEnumPropertyTemplate.kt
new file mode 100644
index 000000000..aacc235bb
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithOptionalStringEnumPropertyTemplate.kt
@@ -0,0 +1,56 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithOptionalStringEnumPropertyTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val property: Field>
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: EntityWithOptionalStringEnumPropertyTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ property = JsonTemplateParser.readOptionalFieldWithExpression(json, "property", topLevel, parent?.property, EntityWithOptionalStringEnumProperty.Property.Converter.FROM_STRING, logger, env, TYPE_HELPER_PROPERTY)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithOptionalStringEnumProperty {
+ return EntityWithOptionalStringEnumProperty(
+ property = property.resolveOptional(env = env, key = "property", data = data, reader = PROPERTY_READER)
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeFieldWithExpression(key = "property", field = property, converter = { v: EntityWithOptionalStringEnumProperty.Property -> EntityWithOptionalStringEnumProperty.Property.toString(v) })
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_optional_string_enum_property"
+
+ private val TYPE_HELPER_PROPERTY = TypeHelper.from(default = EntityWithOptionalStringEnumProperty.Property.values().first()) { it is EntityWithOptionalStringEnumProperty.Property }
+
+ val PROPERTY_READER: Reader?> = { key, json, env -> JsonParser.readOptionalExpression(json, key, EntityWithOptionalStringEnumProperty.Property.Converter.FROM_STRING, env.logger, env, TYPE_HELPER_PROPERTY) }
+ val TYPE_READER: Reader = { key, json, env -> JsonParser.read(json, key, env.logger, env) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithOptionalStringEnumPropertyTemplate(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithPropertyWithDefaultValue.kt b/api_generator/tests/references/kotlin/EntityWithPropertyWithDefaultValue.kt
new file mode 100644
index 000000000..50f17b384
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithPropertyWithDefaultValue.kt
@@ -0,0 +1,99 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithPropertyWithDefaultValue(
+ @JvmField final val int: Expression = INT_DEFAULT_VALUE, // constraint: number >= 0; default value: 0
+ @JvmField final val nested: Nested? = null,
+ @JvmField final val url: Expression = URL_DEFAULT_VALUE, // valid schemes: [https]; default value: https://yandex.ru
+) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeExpression(key = "int", value = int)
+ json.write(key = "nested", value = nested)
+ json.write(key = "type", value = TYPE)
+ json.writeExpression(key = "url", value = url, converter = URI_TO_STRING)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_property_with_default_value"
+
+ private val INT_DEFAULT_VALUE = Expression.constant(0)
+ private val URL_DEFAULT_VALUE = Expression.constant(Uri.parse("https://yandex.ru"))
+
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): EntityWithPropertyWithDefaultValue {
+ val logger = env.logger
+ return EntityWithPropertyWithDefaultValue(
+ int = JsonParser.readOptionalExpression(json, "int", NUMBER_TO_INT, INT_VALIDATOR, logger, env, INT_DEFAULT_VALUE, TYPE_HELPER_INT) ?: INT_DEFAULT_VALUE,
+ nested = JsonParser.readOptional(json, "nested", Nested.CREATOR, logger, env),
+ url = JsonParser.readOptionalExpression(json, "url", STRING_TO_URI, URL_VALIDATOR, logger, env, URL_DEFAULT_VALUE, TYPE_HELPER_URI) ?: URL_DEFAULT_VALUE
+ )
+ }
+
+ private val INT_TEMPLATE_VALIDATOR = ValueValidator { it: Int -> it >= 0 }
+ private val INT_VALIDATOR = ValueValidator { it: Int -> it >= 0 }
+ private val URL_TEMPLATE_VALIDATOR = ValueValidator { it.hasScheme(listOf("https")) }
+ private val URL_VALIDATOR = ValueValidator { it.hasScheme(listOf("https")) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithPropertyWithDefaultValue(env, json = it) }
+ }
+
+
+ @Mockable
+ class Nested(
+ @JvmField final val int: Expression = INT_DEFAULT_VALUE, // constraint: number >= 0; default value: 0
+ @JvmField final val nonOptional: Expression,
+ @JvmField final val url: Expression = URL_DEFAULT_VALUE, // valid schemes: [https]; default value: https://yandex.ru
+ ) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeExpression(key = "int", value = int)
+ json.writeExpression(key = "non_optional", value = nonOptional)
+ json.writeExpression(key = "url", value = url, converter = URI_TO_STRING)
+ return json
+ }
+
+ companion object {
+ private val INT_DEFAULT_VALUE = Expression.constant(0)
+ private val URL_DEFAULT_VALUE = Expression.constant(Uri.parse("https://yandex.ru"))
+
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): Nested {
+ val logger = env.logger
+ return Nested(
+ int = JsonParser.readOptionalExpression(json, "int", NUMBER_TO_INT, INT_VALIDATOR, logger, env, INT_DEFAULT_VALUE, TYPE_HELPER_INT) ?: INT_DEFAULT_VALUE,
+ nonOptional = JsonParser.readExpression(json, "non_optional", logger, env, TYPE_HELPER_STRING),
+ url = JsonParser.readOptionalExpression(json, "url", STRING_TO_URI, URL_VALIDATOR, logger, env, URL_DEFAULT_VALUE, TYPE_HELPER_URI) ?: URL_DEFAULT_VALUE
+ )
+ }
+
+ private val INT_TEMPLATE_VALIDATOR = ValueValidator { it: Int -> it >= 0 }
+ private val INT_VALIDATOR = ValueValidator { it: Int -> it >= 0 }
+ private val URL_TEMPLATE_VALIDATOR = ValueValidator { it.hasScheme(listOf("https")) }
+ private val URL_VALIDATOR = ValueValidator { it.hasScheme(listOf("https")) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> Nested(env, json = it) }
+ }
+
+ }
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithPropertyWithDefaultValueTemplate.kt b/api_generator/tests/references/kotlin/EntityWithPropertyWithDefaultValueTemplate.kt
new file mode 100644
index 000000000..40ea3ba9c
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithPropertyWithDefaultValueTemplate.kt
@@ -0,0 +1,124 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithPropertyWithDefaultValueTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val int: Field> // constraint: number >= 0; default value: 0
+ @JvmField final val nested: Field
+ @JvmField final val url: Field> // valid schemes: [https]; default value: https://yandex.ru
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: EntityWithPropertyWithDefaultValueTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ int = JsonTemplateParser.readOptionalFieldWithExpression(json, "int", topLevel, parent?.int, NUMBER_TO_INT, INT_TEMPLATE_VALIDATOR, logger, env, TYPE_HELPER_INT)
+ nested = JsonTemplateParser.readOptionalField(json, "nested", topLevel, parent?.nested, NestedTemplate.CREATOR, logger, env)
+ url = JsonTemplateParser.readOptionalFieldWithExpression(json, "url", topLevel, parent?.url, STRING_TO_URI, URL_TEMPLATE_VALIDATOR, logger, env, TYPE_HELPER_URI)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithPropertyWithDefaultValue {
+ return EntityWithPropertyWithDefaultValue(
+ int = int.resolveOptional(env = env, key = "int", data = data, reader = INT_READER) ?: INT_DEFAULT_VALUE,
+ nested = nested.resolveOptionalTemplate(env = env, key = "nested", data = data, reader = NESTED_READER),
+ url = url.resolveOptional(env = env, key = "url", data = data, reader = URL_READER) ?: URL_DEFAULT_VALUE
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeFieldWithExpression(key = "int", field = int)
+ json.writeField(key = "nested", field = nested)
+ json.write(key = "type", value = TYPE)
+ json.writeFieldWithExpression(key = "url", field = url, converter = URI_TO_STRING)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_property_with_default_value"
+
+ private val INT_DEFAULT_VALUE = Expression.constant(0)
+ private val URL_DEFAULT_VALUE = Expression.constant(Uri.parse("https://yandex.ru"))
+
+ private val INT_TEMPLATE_VALIDATOR = ValueValidator { it: Int -> it >= 0 }
+ private val INT_VALIDATOR = ValueValidator { it: Int -> it >= 0 }
+ private val URL_TEMPLATE_VALIDATOR = ValueValidator { it.hasScheme(listOf("https")) }
+ private val URL_VALIDATOR = ValueValidator { it.hasScheme(listOf("https")) }
+
+ val INT_READER: Reader?> = { key, json, env -> JsonParser.readOptionalExpression(json, key, NUMBER_TO_INT, INT_VALIDATOR, env.logger, env, INT_DEFAULT_VALUE, TYPE_HELPER_INT) }
+ val NESTED_READER: Reader = { key, json, env -> JsonParser.readOptional(json, key, EntityWithPropertyWithDefaultValue.Nested.CREATOR, env.logger, env) }
+ val TYPE_READER: Reader = { key, json, env -> JsonParser.read(json, key, env.logger, env) }
+ val URL_READER: Reader?> = { key, json, env -> JsonParser.readOptionalExpression(json, key, STRING_TO_URI, URL_VALIDATOR, env.logger, env, URL_DEFAULT_VALUE, TYPE_HELPER_URI) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithPropertyWithDefaultValueTemplate(env, json = it) }
+ }
+
+
+ @Mockable
+ class NestedTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val int: Field> // constraint: number >= 0; default value: 0
+ @JvmField final val nonOptional: Field>
+ @JvmField final val url: Field> // valid schemes: [https]; default value: https://yandex.ru
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: NestedTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ int = JsonTemplateParser.readOptionalFieldWithExpression(json, "int", topLevel, parent?.int, NUMBER_TO_INT, INT_TEMPLATE_VALIDATOR, logger, env, TYPE_HELPER_INT)
+ nonOptional = JsonTemplateParser.readFieldWithExpression(json, "non_optional", topLevel, parent?.nonOptional, logger, env, TYPE_HELPER_STRING)
+ url = JsonTemplateParser.readOptionalFieldWithExpression(json, "url", topLevel, parent?.url, STRING_TO_URI, URL_TEMPLATE_VALIDATOR, logger, env, TYPE_HELPER_URI)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithPropertyWithDefaultValue.Nested {
+ return EntityWithPropertyWithDefaultValue.Nested(
+ int = int.resolveOptional(env = env, key = "int", data = data, reader = INT_READER) ?: INT_DEFAULT_VALUE,
+ nonOptional = nonOptional.resolve(env = env, key = "non_optional", data = data, reader = NON_OPTIONAL_READER),
+ url = url.resolveOptional(env = env, key = "url", data = data, reader = URL_READER) ?: URL_DEFAULT_VALUE
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeFieldWithExpression(key = "int", field = int)
+ json.writeFieldWithExpression(key = "non_optional", field = nonOptional)
+ json.writeFieldWithExpression(key = "url", field = url, converter = URI_TO_STRING)
+ return json
+ }
+
+ companion object {
+ private val INT_DEFAULT_VALUE = Expression.constant(0)
+ private val URL_DEFAULT_VALUE = Expression.constant(Uri.parse("https://yandex.ru"))
+
+ private val INT_TEMPLATE_VALIDATOR = ValueValidator { it: Int -> it >= 0 }
+ private val INT_VALIDATOR = ValueValidator { it: Int -> it >= 0 }
+ private val URL_TEMPLATE_VALIDATOR = ValueValidator { it.hasScheme(listOf("https")) }
+ private val URL_VALIDATOR = ValueValidator { it.hasScheme(listOf("https")) }
+
+ val INT_READER: Reader?> = { key, json, env -> JsonParser.readOptionalExpression(json, key, NUMBER_TO_INT, INT_VALIDATOR, env.logger, env, INT_DEFAULT_VALUE, TYPE_HELPER_INT) }
+ val NON_OPTIONAL_READER: Reader> = { key, json, env -> JsonParser.readExpression(json, key, env.logger, env, TYPE_HELPER_STRING) }
+ val URL_READER: Reader?> = { key, json, env -> JsonParser.readOptionalExpression(json, key, STRING_TO_URI, URL_VALIDATOR, env.logger, env, URL_DEFAULT_VALUE, TYPE_HELPER_URI) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> NestedTemplate(env, json = it) }
+ }
+
+ }
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithRequiredProperty.kt b/api_generator/tests/references/kotlin/EntityWithRequiredProperty.kt
new file mode 100644
index 000000000..4b3eeeecc
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithRequiredProperty.kt
@@ -0,0 +1,48 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithRequiredProperty(
+ @JvmField final val property: Expression, // at least 1 char
+) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeExpression(key = "property", value = property)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_required_property"
+
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): EntityWithRequiredProperty {
+ val logger = env.logger
+ return EntityWithRequiredProperty(
+ property = JsonParser.readExpression(json, "property", PROPERTY_VALIDATOR, logger, env, TYPE_HELPER_STRING)
+ )
+ }
+
+ private val PROPERTY_TEMPLATE_VALIDATOR = ValueValidator { it: String -> it.length >= 1 }
+ private val PROPERTY_VALIDATOR = ValueValidator { it: String -> it.length >= 1 }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithRequiredProperty(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithRequiredPropertyTemplate.kt b/api_generator/tests/references/kotlin/EntityWithRequiredPropertyTemplate.kt
new file mode 100644
index 000000000..c4a1f9894
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithRequiredPropertyTemplate.kt
@@ -0,0 +1,57 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithRequiredPropertyTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val property: Field> // at least 1 char
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: EntityWithRequiredPropertyTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ property = JsonTemplateParser.readFieldWithExpression(json, "property", topLevel, parent?.property, PROPERTY_TEMPLATE_VALIDATOR, logger, env, TYPE_HELPER_STRING)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithRequiredProperty {
+ return EntityWithRequiredProperty(
+ property = property.resolve(env = env, key = "property", data = data, reader = PROPERTY_READER)
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeFieldWithExpression(key = "property", field = property)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_required_property"
+
+ private val PROPERTY_TEMPLATE_VALIDATOR = ValueValidator { it: String -> it.length >= 1 }
+ private val PROPERTY_VALIDATOR = ValueValidator { it: String -> it.length >= 1 }
+
+ val PROPERTY_READER: Reader> = { key, json, env -> JsonParser.readExpression(json, key, PROPERTY_VALIDATOR, env.logger, env, TYPE_HELPER_STRING) }
+ val TYPE_READER: Reader = { key, json, env -> JsonParser.read(json, key, env.logger, env) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithRequiredPropertyTemplate(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithSimpleProperties.kt b/api_generator/tests/references/kotlin/EntityWithSimpleProperties.kt
new file mode 100644
index 000000000..c30e7fb79
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithSimpleProperties.kt
@@ -0,0 +1,74 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithSimpleProperties(
+ @JvmField final val boolean: Expression? = null,
+ @JvmField final val booleanInt: Expression? = null,
+ @JvmField final val color: Expression? = null,
+ @JvmField final val double: Expression? = null,
+ @JvmField final val id: Int? = null,
+ @JvmField final val integer: Expression? = null,
+ @JvmField final val positiveInteger: Expression? = null, // constraint: number > 0
+ @JvmField final val string: Expression? = null, // at least 1 char
+ @JvmField final val url: Expression? = null,
+) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeExpression(key = "boolean", value = boolean, converter = BOOLEAN_TO_INT)
+ json.writeExpression(key = "boolean_int", value = booleanInt, converter = BOOLEAN_TO_INT)
+ json.writeExpression(key = "color", value = color, converter = COLOR_INT_TO_STRING)
+ json.writeExpression(key = "double", value = double)
+ json.write(key = "id", value = id)
+ json.writeExpression(key = "integer", value = integer)
+ json.writeExpression(key = "positive_integer", value = positiveInteger)
+ json.writeExpression(key = "string", value = string)
+ json.write(key = "type", value = TYPE)
+ json.writeExpression(key = "url", value = url, converter = URI_TO_STRING)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_simple_properties"
+
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): EntityWithSimpleProperties {
+ val logger = env.logger
+ return EntityWithSimpleProperties(
+ boolean = JsonParser.readOptionalExpression(json, "boolean", NUMBER_TO_BOOLEAN, logger, env, TYPE_HELPER_BOOLEAN),
+ booleanInt = JsonParser.readOptionalExpression(json, "boolean_int", NUMBER_TO_BOOLEAN, logger, env, TYPE_HELPER_BOOLEAN),
+ color = JsonParser.readOptionalExpression(json, "color", STRING_TO_COLOR_INT, logger, env, TYPE_HELPER_COLOR),
+ double = JsonParser.readOptionalExpression(json, "double", NUMBER_TO_DOUBLE, logger, env, TYPE_HELPER_DOUBLE),
+ id = JsonParser.readOptional(json, "id", NUMBER_TO_INT, logger, env),
+ integer = JsonParser.readOptionalExpression(json, "integer", NUMBER_TO_INT, logger, env, TYPE_HELPER_INT),
+ positiveInteger = JsonParser.readOptionalExpression(json, "positive_integer", NUMBER_TO_INT, POSITIVE_INTEGER_VALIDATOR, logger, env, TYPE_HELPER_INT),
+ string = JsonParser.readOptionalExpression(json, "string", STRING_VALIDATOR, logger, env, TYPE_HELPER_STRING),
+ url = JsonParser.readOptionalExpression(json, "url", STRING_TO_URI, logger, env, TYPE_HELPER_URI)
+ )
+ }
+
+ private val POSITIVE_INTEGER_TEMPLATE_VALIDATOR = ValueValidator { it: Int -> it > 0 }
+ private val POSITIVE_INTEGER_VALIDATOR = ValueValidator { it: Int -> it > 0 }
+ private val STRING_TEMPLATE_VALIDATOR = ValueValidator { it: String -> it.length >= 1 }
+ private val STRING_VALIDATOR = ValueValidator { it: String -> it.length >= 1 }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithSimpleProperties(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithSimplePropertiesTemplate.kt b/api_generator/tests/references/kotlin/EntityWithSimplePropertiesTemplate.kt
new file mode 100644
index 000000000..a4ad8495d
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithSimplePropertiesTemplate.kt
@@ -0,0 +1,99 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithSimplePropertiesTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val boolean: Field>
+ @JvmField final val booleanInt: Field>
+ @JvmField final val color: Field>
+ @JvmField final val double: Field>
+ @JvmField final val id: Field
+ @JvmField final val integer: Field>
+ @JvmField final val positiveInteger: Field> // constraint: number > 0
+ @JvmField final val string: Field> // at least 1 char
+ @JvmField final val url: Field>
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: EntityWithSimplePropertiesTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ boolean = JsonTemplateParser.readOptionalFieldWithExpression(json, "boolean", topLevel, parent?.boolean, NUMBER_TO_BOOLEAN, logger, env, TYPE_HELPER_BOOLEAN)
+ booleanInt = JsonTemplateParser.readOptionalFieldWithExpression(json, "boolean_int", topLevel, parent?.booleanInt, NUMBER_TO_BOOLEAN, logger, env, TYPE_HELPER_BOOLEAN)
+ color = JsonTemplateParser.readOptionalFieldWithExpression(json, "color", topLevel, parent?.color, STRING_TO_COLOR_INT, logger, env, TYPE_HELPER_COLOR)
+ double = JsonTemplateParser.readOptionalFieldWithExpression(json, "double", topLevel, parent?.double, NUMBER_TO_DOUBLE, logger, env, TYPE_HELPER_DOUBLE)
+ id = JsonTemplateParser.readOptionalField(json, "id", topLevel, parent?.id, NUMBER_TO_INT, logger, env)
+ integer = JsonTemplateParser.readOptionalFieldWithExpression(json, "integer", topLevel, parent?.integer, NUMBER_TO_INT, logger, env, TYPE_HELPER_INT)
+ positiveInteger = JsonTemplateParser.readOptionalFieldWithExpression(json, "positive_integer", topLevel, parent?.positiveInteger, NUMBER_TO_INT, POSITIVE_INTEGER_TEMPLATE_VALIDATOR, logger, env, TYPE_HELPER_INT)
+ string = JsonTemplateParser.readOptionalFieldWithExpression(json, "string", topLevel, parent?.string, STRING_TEMPLATE_VALIDATOR, logger, env, TYPE_HELPER_STRING)
+ url = JsonTemplateParser.readOptionalFieldWithExpression(json, "url", topLevel, parent?.url, STRING_TO_URI, logger, env, TYPE_HELPER_URI)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithSimpleProperties {
+ return EntityWithSimpleProperties(
+ boolean = boolean.resolveOptional(env = env, key = "boolean", data = data, reader = BOOLEAN_READER),
+ booleanInt = booleanInt.resolveOptional(env = env, key = "boolean_int", data = data, reader = BOOLEAN_INT_READER),
+ color = color.resolveOptional(env = env, key = "color", data = data, reader = COLOR_READER),
+ double = double.resolveOptional(env = env, key = "double", data = data, reader = DOUBLE_READER),
+ id = id.resolveOptional(env = env, key = "id", data = data, reader = ID_READER),
+ integer = integer.resolveOptional(env = env, key = "integer", data = data, reader = INTEGER_READER),
+ positiveInteger = positiveInteger.resolveOptional(env = env, key = "positive_integer", data = data, reader = POSITIVE_INTEGER_READER),
+ string = string.resolveOptional(env = env, key = "string", data = data, reader = STRING_READER),
+ url = url.resolveOptional(env = env, key = "url", data = data, reader = URL_READER)
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeFieldWithExpression(key = "boolean", field = boolean, converter = BOOLEAN_TO_INT)
+ json.writeFieldWithExpression(key = "boolean_int", field = booleanInt, converter = BOOLEAN_TO_INT)
+ json.writeFieldWithExpression(key = "color", field = color, converter = COLOR_INT_TO_STRING)
+ json.writeFieldWithExpression(key = "double", field = double)
+ json.writeField(key = "id", field = id)
+ json.writeFieldWithExpression(key = "integer", field = integer)
+ json.writeFieldWithExpression(key = "positive_integer", field = positiveInteger)
+ json.writeFieldWithExpression(key = "string", field = string)
+ json.write(key = "type", value = TYPE)
+ json.writeFieldWithExpression(key = "url", field = url, converter = URI_TO_STRING)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_simple_properties"
+
+ private val POSITIVE_INTEGER_TEMPLATE_VALIDATOR = ValueValidator { it: Int -> it > 0 }
+ private val POSITIVE_INTEGER_VALIDATOR = ValueValidator { it: Int -> it > 0 }
+ private val STRING_TEMPLATE_VALIDATOR = ValueValidator { it: String -> it.length >= 1 }
+ private val STRING_VALIDATOR = ValueValidator { it: String -> it.length >= 1 }
+
+ val BOOLEAN_READER: Reader?> = { key, json, env -> JsonParser.readOptionalExpression(json, key, NUMBER_TO_BOOLEAN, env.logger, env, TYPE_HELPER_BOOLEAN) }
+ val BOOLEAN_INT_READER: Reader?> = { key, json, env -> JsonParser.readOptionalExpression(json, key, NUMBER_TO_BOOLEAN, env.logger, env, TYPE_HELPER_BOOLEAN) }
+ val COLOR_READER: Reader?> = { key, json, env -> JsonParser.readOptionalExpression(json, key, STRING_TO_COLOR_INT, env.logger, env, TYPE_HELPER_COLOR) }
+ val DOUBLE_READER: Reader?> = { key, json, env -> JsonParser.readOptionalExpression(json, key, NUMBER_TO_DOUBLE, env.logger, env, TYPE_HELPER_DOUBLE) }
+ val ID_READER: Reader = { key, json, env -> JsonParser.readOptional(json, key, NUMBER_TO_INT, env.logger, env) }
+ val INTEGER_READER: Reader?> = { key, json, env -> JsonParser.readOptionalExpression(json, key, NUMBER_TO_INT, env.logger, env, TYPE_HELPER_INT) }
+ val POSITIVE_INTEGER_READER: Reader?> = { key, json, env -> JsonParser.readOptionalExpression(json, key, NUMBER_TO_INT, POSITIVE_INTEGER_VALIDATOR, env.logger, env, TYPE_HELPER_INT) }
+ val STRING_READER: Reader?> = { key, json, env -> JsonParser.readOptionalExpression(json, key, STRING_VALIDATOR, env.logger, env, TYPE_HELPER_STRING) }
+ val TYPE_READER: Reader = { key, json, env -> JsonParser.read(json, key, env.logger, env) }
+ val URL_READER: Reader?> = { key, json, env -> JsonParser.readOptionalExpression(json, key, STRING_TO_URI, env.logger, env, TYPE_HELPER_URI) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithSimplePropertiesTemplate(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithStrictArray.kt b/api_generator/tests/references/kotlin/EntityWithStrictArray.kt
new file mode 100644
index 000000000..8e42a8ac5
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithStrictArray.kt
@@ -0,0 +1,47 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithStrictArray(
+ @JvmField final val array: List, // at least 1 elements; all received elements must be valid
+) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.write(key = "array", value = array)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_strict_array"
+
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): EntityWithStrictArray {
+ val logger = env.logger
+ return EntityWithStrictArray(
+ array = JsonParser.readStrictList(json, "array", Entity.CREATOR, ARRAY_VALIDATOR, logger, env)
+ )
+ }
+
+ private val ARRAY_VALIDATOR = ListValidator { it: List<*> -> it.size >= 1 }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithStrictArray(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithStrictArrayTemplate.kt b/api_generator/tests/references/kotlin/EntityWithStrictArrayTemplate.kt
new file mode 100644
index 000000000..981e98321
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithStrictArrayTemplate.kt
@@ -0,0 +1,57 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithStrictArrayTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val array: Field> // at least 1 elements; all received elements must be valid
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: EntityWithStrictArrayTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ array = JsonTemplateParser.readStrictListField(json, "array", topLevel, parent?.array, EntityTemplate.CREATOR, ARRAY_TEMPLATE_VALIDATOR, logger, env)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithStrictArray {
+ return EntityWithStrictArray(
+ array = array.resolveTemplateList(env = env, key = "array", data = data, ARRAY_VALIDATOR, reader = ARRAY_READER)
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeField(key = "array", field = array)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_strict_array"
+
+ private val ARRAY_VALIDATOR = ListValidator { it: List<*> -> it.size >= 1 }
+ private val ARRAY_TEMPLATE_VALIDATOR = ListValidator { it: List<*> -> it.size >= 1 }
+
+ val ARRAY_READER: Reader> = { key, json, env -> JsonParser.readStrictList(json, key, Entity.CREATOR, ARRAY_VALIDATOR, env.logger, env) }
+ val TYPE_READER: Reader = { key, json, env -> JsonParser.read(json, key, env.logger, env) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithStrictArrayTemplate(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithStringArrayProperty.kt b/api_generator/tests/references/kotlin/EntityWithStringArrayProperty.kt
new file mode 100644
index 000000000..5d2ac7e5f
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithStringArrayProperty.kt
@@ -0,0 +1,47 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithStringArrayProperty(
+ @JvmField final val array: ExpressionsList, // at least 1 elements
+) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeExpressionsList(key = "array", value = array)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_string_array_property"
+
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): EntityWithStringArrayProperty {
+ val logger = env.logger
+ return EntityWithStringArrayProperty(
+ array = JsonParser.readExpressionsList(json, "array", ARRAY_VALIDATOR, logger, env, TYPE_HELPER_STRING)
+ )
+ }
+
+ private val ARRAY_VALIDATOR = ListValidator { it: List<*> -> it.size >= 1 }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithStringArrayProperty(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithStringArrayPropertyTemplate.kt b/api_generator/tests/references/kotlin/EntityWithStringArrayPropertyTemplate.kt
new file mode 100644
index 000000000..de96a8401
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithStringArrayPropertyTemplate.kt
@@ -0,0 +1,57 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithStringArrayPropertyTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val array: Field> // at least 1 elements
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: EntityWithStringArrayPropertyTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ array = JsonTemplateParser.readExpressionsListField(json, "array", topLevel, parent?.array, ARRAY_TEMPLATE_VALIDATOR, logger, env, TYPE_HELPER_STRING)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithStringArrayProperty {
+ return EntityWithStringArrayProperty(
+ array = array.resolveExpressionList(env = env, key = "array", data = data, reader = ARRAY_READER)
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeExpressionsListField(key = "array", field = array)
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_string_array_property"
+
+ private val ARRAY_VALIDATOR = ListValidator { it: List<*> -> it.size >= 1 }
+ private val ARRAY_TEMPLATE_VALIDATOR = ListValidator { it: List<*> -> it.size >= 1 }
+
+ val ARRAY_READER: Reader> = { key, json, env -> JsonParser.readExpressionsList(json, key, ARRAY_VALIDATOR, env.logger, env, TYPE_HELPER_STRING) }
+ val TYPE_READER: Reader = { key, json, env -> JsonParser.read(json, key, env.logger, env) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithStringArrayPropertyTemplate(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithStringEnumProperty.kt b/api_generator/tests/references/kotlin/EntityWithStringEnumProperty.kt
new file mode 100644
index 000000000..0b011546d
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithStringEnumProperty.kt
@@ -0,0 +1,74 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithStringEnumProperty(
+ @JvmField final val property: Expression,
+) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeExpression(key = "property", value = property, converter = { v: Property -> Property.toString(v) })
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_string_enum_property"
+
+ private val TYPE_HELPER_PROPERTY = TypeHelper.from(default = EntityWithStringEnumProperty.Property.values().first()) { it is EntityWithStringEnumProperty.Property }
+
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): EntityWithStringEnumProperty {
+ val logger = env.logger
+ return EntityWithStringEnumProperty(
+ property = JsonParser.readExpression(json, "property", Property.Converter.FROM_STRING, logger, env, TYPE_HELPER_PROPERTY)
+ )
+ }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithStringEnumProperty(env, json = it) }
+ }
+
+
+ enum class Property(private val value: String) {
+ FIRST("first"),
+ SECOND("second");
+
+ companion object Converter {
+ fun toString(obj: Property): String {
+ return obj.value
+ }
+
+ fun fromString(string: String): Property? {
+ return when (string) {
+ FIRST.value -> FIRST
+ SECOND.value -> SECOND
+ else -> null
+ }
+ }
+
+ val FROM_STRING = { string: String ->
+ when (string) {
+ FIRST.value -> FIRST
+ SECOND.value -> SECOND
+ else -> null
+ }
+ }
+ }
+ }
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithStringEnumPropertyTemplate.kt b/api_generator/tests/references/kotlin/EntityWithStringEnumPropertyTemplate.kt
new file mode 100644
index 000000000..5ca008709
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithStringEnumPropertyTemplate.kt
@@ -0,0 +1,56 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithStringEnumPropertyTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val property: Field>
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: EntityWithStringEnumPropertyTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ property = JsonTemplateParser.readFieldWithExpression(json, "property", topLevel, parent?.property, EntityWithStringEnumProperty.Property.Converter.FROM_STRING, logger, env, TYPE_HELPER_PROPERTY)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithStringEnumProperty {
+ return EntityWithStringEnumProperty(
+ property = property.resolve(env = env, key = "property", data = data, reader = PROPERTY_READER)
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.writeFieldWithExpression(key = "property", field = property, converter = { v: EntityWithStringEnumProperty.Property -> EntityWithStringEnumProperty.Property.toString(v) })
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_string_enum_property"
+
+ private val TYPE_HELPER_PROPERTY = TypeHelper.from(default = EntityWithStringEnumProperty.Property.values().first()) { it is EntityWithStringEnumProperty.Property }
+
+ val PROPERTY_READER: Reader> = { key, json, env -> JsonParser.readExpression(json, key, EntityWithStringEnumProperty.Property.Converter.FROM_STRING, env.logger, env, TYPE_HELPER_PROPERTY) }
+ val TYPE_READER: Reader = { key, json, env -> JsonParser.read(json, key, env.logger, env) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithStringEnumPropertyTemplate(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithStringEnumPropertyWithDefaultValue.kt b/api_generator/tests/references/kotlin/EntityWithStringEnumPropertyWithDefaultValue.kt
new file mode 100644
index 000000000..b7623dbc1
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithStringEnumPropertyWithDefaultValue.kt
@@ -0,0 +1,79 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithStringEnumPropertyWithDefaultValue(
+ @JvmField final val value: Expression = VALUE_DEFAULT_VALUE, // default value: second
+) : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.write(key = "type", value = TYPE)
+ json.writeExpression(key = "value", value = value, converter = { v: Value -> Value.toString(v) })
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_string_enum_property_with_default_value"
+
+ private val VALUE_DEFAULT_VALUE = Expression.constant(Value.SECOND)
+
+ private val TYPE_HELPER_VALUE = TypeHelper.from(default = EntityWithStringEnumPropertyWithDefaultValue.Value.values().first()) { it is EntityWithStringEnumPropertyWithDefaultValue.Value }
+
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): EntityWithStringEnumPropertyWithDefaultValue {
+ val logger = env.logger
+ return EntityWithStringEnumPropertyWithDefaultValue(
+ value = JsonParser.readOptionalExpression(json, "value", Value.Converter.FROM_STRING, logger, env, VALUE_DEFAULT_VALUE, TYPE_HELPER_VALUE) ?: VALUE_DEFAULT_VALUE
+ )
+ }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithStringEnumPropertyWithDefaultValue(env, json = it) }
+ }
+
+
+ enum class Value(private val value: String) {
+ FIRST("first"),
+ SECOND("second"),
+ THIRD("third");
+
+ companion object Converter {
+ fun toString(obj: Value): String {
+ return obj.value
+ }
+
+ fun fromString(string: String): Value? {
+ return when (string) {
+ FIRST.value -> FIRST
+ SECOND.value -> SECOND
+ THIRD.value -> THIRD
+ else -> null
+ }
+ }
+
+ val FROM_STRING = { string: String ->
+ when (string) {
+ FIRST.value -> FIRST
+ SECOND.value -> SECOND
+ THIRD.value -> THIRD
+ else -> null
+ }
+ }
+ }
+ }
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithStringEnumPropertyWithDefaultValueTemplate.kt b/api_generator/tests/references/kotlin/EntityWithStringEnumPropertyWithDefaultValueTemplate.kt
new file mode 100644
index 000000000..e270704db
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithStringEnumPropertyWithDefaultValueTemplate.kt
@@ -0,0 +1,58 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithStringEnumPropertyWithDefaultValueTemplate : JSONSerializable, JsonTemplate {
+ @JvmField final val value: Field> // default value: second
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: EntityWithStringEnumPropertyWithDefaultValueTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ value = JsonTemplateParser.readOptionalFieldWithExpression(json, "value", topLevel, parent?.value, EntityWithStringEnumPropertyWithDefaultValue.Value.Converter.FROM_STRING, logger, env, TYPE_HELPER_VALUE)
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithStringEnumPropertyWithDefaultValue {
+ return EntityWithStringEnumPropertyWithDefaultValue(
+ value = value.resolveOptional(env = env, key = "value", data = data, reader = VALUE_READER) ?: VALUE_DEFAULT_VALUE
+ )
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.write(key = "type", value = TYPE)
+ json.writeFieldWithExpression(key = "value", field = value, converter = { v: EntityWithStringEnumPropertyWithDefaultValue.Value -> EntityWithStringEnumPropertyWithDefaultValue.Value.toString(v) })
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_with_string_enum_property_with_default_value"
+
+ private val VALUE_DEFAULT_VALUE = Expression.constant(EntityWithStringEnumPropertyWithDefaultValue.Value.SECOND)
+
+ private val TYPE_HELPER_VALUE = TypeHelper.from(default = EntityWithStringEnumPropertyWithDefaultValue.Value.values().first()) { it is EntityWithStringEnumPropertyWithDefaultValue.Value }
+
+ val TYPE_READER: Reader = { key, json, env -> JsonParser.read(json, key, env.logger, env) }
+ val VALUE_READER: Reader?> = { key, json, env -> JsonParser.readOptionalExpression(json, key, EntityWithStringEnumPropertyWithDefaultValue.Value.Converter.FROM_STRING, env.logger, env, VALUE_DEFAULT_VALUE, TYPE_HELPER_VALUE) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithStringEnumPropertyWithDefaultValueTemplate(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithoutProperties.kt b/api_generator/tests/references/kotlin/EntityWithoutProperties.kt
new file mode 100644
index 000000000..ed346e810
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithoutProperties.kt
@@ -0,0 +1,41 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithoutProperties() : JSONSerializable {
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_without_properties"
+
+ @JvmStatic
+ @JvmName("fromJson")
+ operator fun invoke(env: ParsingEnvironment, json: JSONObject): EntityWithoutProperties {
+ val logger = env.logger
+ return EntityWithoutProperties(
+ )
+ }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithoutProperties(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/kotlin/EntityWithoutPropertiesTemplate.kt b/api_generator/tests/references/kotlin/EntityWithoutPropertiesTemplate.kt
new file mode 100644
index 000000000..c98cf9f41
--- /dev/null
+++ b/api_generator/tests/references/kotlin/EntityWithoutPropertiesTemplate.kt
@@ -0,0 +1,48 @@
+// Generated code. Do not modify.
+
+package com.yandex.div2
+
+import android.graphics.Color
+import android.net.Uri
+import androidx.annotation.ColorInt
+import com.yandex.div.json.*
+import com.yandex.div.json.expressions.Expression
+import com.yandex.div.json.expressions.ExpressionsList
+import com.yandex.div.json.schema.*
+import com.yandex.div.core.annotations.Mockable
+import java.io.IOException
+import java.util.BitSet
+import org.json.JSONObject
+import com.yandex.div.data.*
+
+@Mockable
+class EntityWithoutPropertiesTemplate : JSONSerializable, JsonTemplate {
+
+ constructor (
+ env: ParsingEnvironment,
+ parent: EntityWithoutPropertiesTemplate? = null,
+ topLevel: Boolean = false,
+ json: JSONObject
+ ) {
+ val logger = env.logger
+ }
+
+ override fun resolve(env: ParsingEnvironment, data: JSONObject): EntityWithoutProperties {
+ return EntityWithoutProperties()
+ }
+
+ override fun writeToJSON(): JSONObject {
+ val json = JSONObject()
+ json.write(key = "type", value = TYPE)
+ return json
+ }
+
+ companion object {
+ const val TYPE = "entity_without_properties"
+
+ val TYPE_READER: Reader = { key, json, env -> JsonParser.read(json, key, env.logger, env) }
+
+ val CREATOR = { env: ParsingEnvironment, it: JSONObject -> EntityWithoutPropertiesTemplate(env, json = it) }
+ }
+
+}
diff --git a/api_generator/tests/references/reference_clean_unused_definitions.json b/api_generator/tests/references/reference_clean_unused_definitions.json
new file mode 100644
index 000000000..39ee26994
--- /dev/null
+++ b/api_generator/tests/references/reference_clean_unused_definitions.json
@@ -0,0 +1,822 @@
+{
+ "test_schema": [
+ {
+ "common.json": {
+ "non_empty_string": {
+ "type": "string",
+ "minLength": 1,
+ "clientMinLength": 1
+ },
+ "color": {
+ "type": "string",
+ "format": "color"
+ },
+ "url": {
+ "type": "string",
+ "format": "uri"
+ },
+ "positive_integer": {
+ "type": "integer",
+ "constraint": "number > 0"
+ },
+ "non_negative_integer": {
+ "type": "integer",
+ "constraint": "number >= 0"
+ },
+ "boolean_int": {
+ "type": "integer",
+ "enum": [
+ 0,
+ 1
+ ],
+ "format": "boolean"
+ }
+ }
+ },
+ {
+ "entity.json": {
+ "anyOf": [
+ {
+ "$resolved_refs": [
+ "entity_with_array.json"
+ ],
+ "type": "$defined_entity_with_array"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_array_of_nested_items.json"
+ ],
+ "type": "$defined_entity_with_array_of_nested_items"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_array_with_transform.json"
+ ],
+ "type": "$defined_entity_with_array_with_transform"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_complex_property.json"
+ ],
+ "type": "$defined_entity_with_complex_property"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_complex_property_with_default_value.json"
+ ],
+ "type": "$defined_entity_with_complex_property_with_default_value"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_entity_property.json"
+ ],
+ "type": "$defined_entity_with_entity_property"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_optional_complex_property.json"
+ ],
+ "type": "$defined_entity_with_optional_complex_property"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_optional_property.json"
+ ],
+ "type": "$defined_entity_with_optional_property"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_optional_string_enum_property.json"
+ ],
+ "type": "$defined_entity_with_optional_string_enum_property"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_property_with_default_value.json"
+ ],
+ "type": "$defined_entity_with_property_with_default_value"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_required_property.json"
+ ],
+ "type": "$defined_entity_with_required_property"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_simple_properties.json"
+ ],
+ "type": "$defined_entity_with_simple_properties"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_strict_array.json"
+ ],
+ "type": "$defined_entity_with_strict_array"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_string_array_property.json"
+ ],
+ "type": "$defined_entity_with_string_array_property"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_string_enum_property.json"
+ ],
+ "type": "$defined_entity_with_string_enum_property"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_string_enum_property_with_default_value.json"
+ ],
+ "type": "$defined_entity_with_string_enum_property_with_default_value"
+ },
+ {
+ "$resolved_refs": [
+ "entity_without_properties.json"
+ ],
+ "type": "$defined_entity_without_properties"
+ }
+ ]
+ }
+ },
+ {
+ "entity_with_array.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_array"
+ ]
+ },
+ "array": {
+ "type": "array",
+ "items": {
+ "$resolved_refs": [
+ "entity.json"
+ ],
+ "type": "$defined_entity"
+ },
+ "minItems": 1,
+ "clientMinItems": 1
+ }
+ },
+ "required": [
+ "type",
+ "array"
+ ]
+ }
+ },
+ {
+ "entity_with_array_of_nested_items.json": {
+ "type": "object",
+ "definitions": {},
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_array_of_nested_items"
+ ]
+ },
+ "items": {
+ "type": "array",
+ "items": {
+ "$resolved_refs": [
+ "#/definitions/item"
+ ],
+ "type": "object",
+ "properties": {
+ "property": {
+ "$resolved_refs": [
+ "common.json#/non_empty_string"
+ ],
+ "type": "string",
+ "minLength": 1,
+ "clientMinLength": 1
+ },
+ "entity": {
+ "$resolved_refs": [
+ "entity.json"
+ ],
+ "type": "$defined_entity"
+ }
+ },
+ "required": [
+ "property",
+ "entity"
+ ],
+ "$typename": "item"
+ },
+ "minItems": 1,
+ "clientMinItems": 1
+ }
+ },
+ "required": [
+ "type",
+ "items"
+ ]
+ }
+ },
+ {
+ "entity_with_array_with_transform.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_array_with_transform"
+ ]
+ },
+ "array": {
+ "type": "array",
+ "items": {
+ "$resolved_refs": [
+ "common.json#/color"
+ ],
+ "type": "string",
+ "format": "color"
+ },
+ "minItems": 1,
+ "clientMinItems": 1
+ }
+ },
+ "required": [
+ "type",
+ "array"
+ ]
+ }
+ },
+ {
+ "entity_with_complex_property.json": {
+ "type": "object",
+ "definitions": {},
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_complex_property"
+ ]
+ },
+ "property": {
+ "$resolved_refs": [
+ "#/definitions/complex_property"
+ ],
+ "type": "object",
+ "properties": {
+ "value": {
+ "$resolved_refs": [
+ "common.json#/url"
+ ],
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "required": [
+ "value"
+ ],
+ "$typename": "complex_property"
+ }
+ },
+ "required": [
+ "type",
+ "property"
+ ]
+ }
+ },
+ {
+ "entity_with_complex_property_with_default_value.json": {
+ "type": "object",
+ "definitions": {},
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_complex_property_with_default_value"
+ ]
+ },
+ "property": {
+ "default_value": "{ \"value\": \"Default text\" }",
+ "$resolved_refs": [
+ "#/definitions/complex_property"
+ ],
+ "type": "object",
+ "properties": {
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "value"
+ ],
+ "$typename": "complex_property"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_entity_property.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_entity_property"
+ ]
+ },
+ "entity": {
+ "default_value": "{ \"type\": \"entity_with_string_enum_property\", \"property\": \"second\" }",
+ "$resolved_refs": [
+ "entity.json"
+ ],
+ "type": "$defined_entity"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_optional_complex_property.json": {
+ "type": "object",
+ "definitions": {},
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_optional_complex_property"
+ ]
+ },
+ "property": {
+ "$resolved_refs": [
+ "#/definitions/complex_property"
+ ],
+ "type": "object",
+ "properties": {
+ "value": {
+ "$resolved_refs": [
+ "common.json#/url"
+ ],
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "required": [
+ "value"
+ ],
+ "$typename": "complex_property"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_optional_property.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_optional_property"
+ ]
+ },
+ "property": {
+ "$resolved_refs": [
+ "common.json#/non_empty_string"
+ ],
+ "type": "string",
+ "minLength": 1,
+ "clientMinLength": 1
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_optional_string_enum_property.json": {
+ "type": "object",
+ "definitions": {},
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_optional_string_enum_property"
+ ]
+ },
+ "property": {
+ "$resolved_refs": [
+ "#/definitions/string_enum_property"
+ ],
+ "type": "string",
+ "enum": [
+ "first",
+ "second"
+ ],
+ "$typename": "string_enum_property"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_required_property.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_required_property"
+ ]
+ },
+ "property": {
+ "$resolved_refs": [
+ "common.json#/non_empty_string"
+ ],
+ "type": "string",
+ "minLength": 1,
+ "clientMinLength": 1
+ }
+ },
+ "required": [
+ "type",
+ "property"
+ ]
+ }
+ },
+ {
+ "entity_with_strict_array.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_strict_array"
+ ]
+ },
+ "array": {
+ "type": "array",
+ "items": {
+ "$resolved_refs": [
+ "entity.json"
+ ],
+ "type": "$defined_entity"
+ },
+ "minItems": 1,
+ "clientMinItems": 1,
+ "strictParsing": true
+ }
+ },
+ "required": [
+ "type",
+ "array"
+ ]
+ }
+ },
+ {
+ "entity_with_string_array_property.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_string_array_property"
+ ]
+ },
+ "array": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minItems": 1,
+ "clientMinItems": 1
+ }
+ },
+ "required": [
+ "type",
+ "array"
+ ]
+ }
+ },
+ {
+ "entity_with_string_enum_property.json": {
+ "type": "object",
+ "definitions": {},
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_string_enum_property"
+ ]
+ },
+ "property": {
+ "$resolved_refs": [
+ "#/definitions/string_enum_property"
+ ],
+ "type": "string",
+ "enum": [
+ "first",
+ "second"
+ ],
+ "$typename": "string_enum_property"
+ }
+ },
+ "required": [
+ "type",
+ "property"
+ ]
+ }
+ },
+ {
+ "entity_with_string_enum_property_with_default_value.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_string_enum_property_with_default_value"
+ ]
+ },
+ "value": {
+ "type": "string",
+ "enum": [
+ "first",
+ "second",
+ "third"
+ ],
+ "default_value": "second"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_without_properties.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_without_properties"
+ ]
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_property_with_default_value.json": {
+ "type": "object",
+ "definitions": {},
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_property_with_default_value"
+ ]
+ },
+ "int": {
+ "$resolved_refs": [
+ "#/definitions/int_prop",
+ "common.json#/non_negative_integer"
+ ],
+ "default_value": "0",
+ "type": "integer",
+ "constraint": "number >= 0"
+ },
+ "url": {
+ "$resolved_refs": [
+ "#/definitions/url_prop",
+ "common.json#/url"
+ ],
+ "schemes": [
+ "https"
+ ],
+ "default_value": "https://yandex.ru",
+ "type": "string",
+ "format": "uri"
+ },
+ "nested": {
+ "$resolved_refs": [
+ "#/definitions/nested"
+ ],
+ "type": "object",
+ "properties": {
+ "int": {
+ "$resolved_refs": [
+ "#/definitions/int_prop",
+ "common.json#/non_negative_integer"
+ ],
+ "default_value": "0",
+ "type": "integer",
+ "constraint": "number >= 0",
+ "$typename": "int_prop"
+ },
+ "url": {
+ "$resolved_refs": [
+ "#/definitions/url_prop",
+ "common.json#/url"
+ ],
+ "schemes": [
+ "https"
+ ],
+ "default_value": "https://yandex.ru",
+ "type": "string",
+ "format": "uri",
+ "$typename": "url_prop"
+ },
+ "non_optional": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "non_optional"
+ ],
+ "$typename": "nested",
+ "description_translations": {
+ "en": "non_optional is used to suppress auto-generation of default value for object with all-optional fields.",
+ "ru": "non_optional используется, чтобы запретить автогенерацию дефолтного значения для объекта, все свойства которого опциональны."
+ }
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_simple_properties.json": {
+ "type": "object",
+ "swift_protocols": "EntityProtocol",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_simple_properties"
+ ]
+ },
+ "id": {
+ "type": "integer",
+ "supports_expressions": false,
+ "description_translations": {
+ "en": "ID. Can't contain expressions.",
+ "ru": "Идентификатор. Не может содержать выражение."
+ }
+ },
+ "string": {
+ "platforms": [
+ "android",
+ "ios",
+ "web"
+ ],
+ "description_translations": {
+ "en": "String.",
+ "ru": "Строка."
+ },
+ "$resolved_refs": [
+ "common.json#/non_empty_string"
+ ],
+ "type": "string",
+ "minLength": 1,
+ "clientMinLength": 1
+ },
+ "integer": {
+ "type": "integer",
+ "description_translations": {
+ "en": "Integer.",
+ "ru": "Целое число."
+ }
+ },
+ "double": {
+ "type": "number",
+ "description_translations": {
+ "en": "Floating point number.",
+ "ru": "Число с плавающей точкой."
+ }
+ },
+ "boolean": {
+ "type": "boolean",
+ "description_translations": {
+ "en": "Boolean property.",
+ "ru": "Логическое значение."
+ }
+ },
+ "boolean_int": {
+ "deprecated": true,
+ "platforms": [
+ "android"
+ ],
+ "description_translations": {
+ "en": "Boolean value in numeric format.",
+ "ru": "Логическое значение в числовом формате."
+ },
+ "$resolved_refs": [
+ "common.json#/boolean_int"
+ ],
+ "type": "integer",
+ "enum": [
+ 0,
+ 1
+ ],
+ "format": "boolean"
+ },
+ "positive_integer": {
+ "platforms": [],
+ "description_translations": {
+ "en": "Positive integer.",
+ "ru": "Положительное целое число."
+ },
+ "$resolved_refs": [
+ "common.json#/positive_integer"
+ ],
+ "type": "integer",
+ "constraint": "number > 0"
+ },
+ "color": {
+ "description_translations": {
+ "en": "Color.",
+ "ru": "Цвет."
+ },
+ "$resolved_refs": [
+ "common.json#/color"
+ ],
+ "type": "string",
+ "format": "color"
+ },
+ "url": {
+ "$resolved_refs": [
+ "common.json#/url"
+ ],
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "description_translations": {
+ "en": "Entity with simple properties.",
+ "ru": "Объект с простыми свойствами."
+ }
+ }
+ },
+ {
+ "translations.json": {
+ "entity_with_property_with_default_value_nested": {
+ "en": "non_optional is used to suppress auto-generation of default value for object with all-optional fields.",
+ "ru": "non_optional используется, чтобы запретить автогенерацию дефолтного значения для объекта, все свойства которого опциональны."
+ },
+ "entity_with_simple_properties": {
+ "en": "Entity with simple properties.",
+ "ru": "Объект с простыми свойствами."
+ },
+ "entity_with_simple_properties_id": {
+ "en": "ID. Can't contain expressions.",
+ "ru": "Идентификатор. Не может содержать выражение."
+ },
+ "entity_with_simple_properties_string": {
+ "en": "String.",
+ "ru": "Строка."
+ },
+ "entity_with_simple_properties_integer": {
+ "en": "Integer.",
+ "ru": "Целое число."
+ },
+ "entity_with_simple_properties_double": {
+ "en": "Floating point number.",
+ "ru": "Число с плавающей точкой."
+ },
+ "entity_with_simple_properties_boolean": {
+ "en": "Boolean property.",
+ "ru": "Логическое значение."
+ },
+ "entity_with_simple_properties_boolean_int": {
+ "en": "Boolean value in numeric format.",
+ "ru": "Логическое значение в числовом формате."
+ },
+ "entity_with_simple_properties_positive_integer": {
+ "en": "Positive integer.",
+ "ru": "Положительное целое число."
+ },
+ "entity_with_simple_properties_color": {
+ "en": "Color.",
+ "ru": "Цвет."
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/api_generator/tests/references/reference_merge_allOfs.json b/api_generator/tests/references/reference_merge_allOfs.json
new file mode 100644
index 000000000..e6b5a737f
--- /dev/null
+++ b/api_generator/tests/references/reference_merge_allOfs.json
@@ -0,0 +1,656 @@
+{
+ "test_schema": [
+ {
+ "common.json": {
+ "non_empty_string": {
+ "type": "string",
+ "minLength": 1,
+ "clientMinLength": 1
+ },
+ "color": {
+ "type": "string",
+ "format": "color"
+ },
+ "url": {
+ "type": "string",
+ "format": "uri"
+ },
+ "positive_integer": {
+ "type": "integer",
+ "constraint": "number > 0"
+ },
+ "non_negative_integer": {
+ "type": "integer",
+ "constraint": "number >= 0"
+ },
+ "boolean_int": {
+ "type": "integer",
+ "enum": [
+ 0,
+ 1
+ ],
+ "format": "boolean"
+ }
+ }
+ },
+ {
+ "entity.json": {
+ "anyOf": [
+ {
+ "$ref": "entity_with_array.json"
+ },
+ {
+ "$ref": "entity_with_array_of_nested_items.json"
+ },
+ {
+ "$ref": "entity_with_array_with_transform.json"
+ },
+ {
+ "$ref": "entity_with_complex_property.json"
+ },
+ {
+ "$ref": "entity_with_complex_property_with_default_value.json"
+ },
+ {
+ "$ref": "entity_with_entity_property.json"
+ },
+ {
+ "$ref": "entity_with_optional_complex_property.json"
+ },
+ {
+ "$ref": "entity_with_optional_property.json"
+ },
+ {
+ "$ref": "entity_with_optional_string_enum_property.json"
+ },
+ {
+ "$ref": "entity_with_property_with_default_value.json"
+ },
+ {
+ "$ref": "entity_with_required_property.json"
+ },
+ {
+ "$ref": "entity_with_simple_properties.json"
+ },
+ {
+ "$ref": "entity_with_strict_array.json"
+ },
+ {
+ "$ref": "entity_with_string_array_property.json"
+ },
+ {
+ "$ref": "entity_with_string_enum_property.json"
+ },
+ {
+ "$ref": "entity_with_string_enum_property_with_default_value.json"
+ },
+ {
+ "$ref": "entity_without_properties.json"
+ }
+ ]
+ }
+ },
+ {
+ "entity_with_array.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_array"
+ ]
+ },
+ "array": {
+ "type": "array",
+ "items": {
+ "$ref": "entity.json"
+ },
+ "minItems": 1,
+ "clientMinItems": 1
+ }
+ },
+ "required": [
+ "type",
+ "array"
+ ]
+ }
+ },
+ {
+ "entity_with_array_of_nested_items.json": {
+ "type": "object",
+ "definitions": {
+ "item": {
+ "type": "object",
+ "properties": {
+ "property": {
+ "$ref": "common.json#/non_empty_string"
+ },
+ "entity": {
+ "$ref": "entity.json"
+ }
+ },
+ "required": [
+ "property",
+ "entity"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_array_of_nested_items"
+ ]
+ },
+ "items": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/item"
+ },
+ "minItems": 1,
+ "clientMinItems": 1
+ }
+ },
+ "required": [
+ "type",
+ "items"
+ ]
+ }
+ },
+ {
+ "entity_with_array_with_transform.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_array_with_transform"
+ ]
+ },
+ "array": {
+ "type": "array",
+ "items": {
+ "$ref": "common.json#/color"
+ },
+ "minItems": 1,
+ "clientMinItems": 1
+ }
+ },
+ "required": [
+ "type",
+ "array"
+ ]
+ }
+ },
+ {
+ "entity_with_complex_property.json": {
+ "type": "object",
+ "definitions": {
+ "complex_property": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "$ref": "common.json#/url"
+ }
+ },
+ "required": [
+ "value"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_complex_property"
+ ]
+ },
+ "property": {
+ "$ref": "#/definitions/complex_property"
+ }
+ },
+ "required": [
+ "type",
+ "property"
+ ]
+ }
+ },
+ {
+ "entity_with_complex_property_with_default_value.json": {
+ "type": "object",
+ "definitions": {
+ "complex_property": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "value"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_complex_property_with_default_value"
+ ]
+ },
+ "property": {
+ "$ref": "#/definitions/complex_property",
+ "default_value": "{ \"value\": \"Default text\" }"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_entity_property.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_entity_property"
+ ]
+ },
+ "entity": {
+ "$ref": "entity.json",
+ "default_value": "{ \"type\": \"entity_with_string_enum_property\", \"property\": \"second\" }"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_optional_complex_property.json": {
+ "type": "object",
+ "definitions": {
+ "complex_property": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "$ref": "common.json#/url"
+ }
+ },
+ "required": [
+ "value"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_optional_complex_property"
+ ]
+ },
+ "property": {
+ "$ref": "#/definitions/complex_property"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_optional_property.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_optional_property"
+ ]
+ },
+ "property": {
+ "$ref": "common.json#/non_empty_string"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_optional_string_enum_property.json": {
+ "type": "object",
+ "definitions": {
+ "string_enum_property": {
+ "type": "string",
+ "enum": [
+ "first",
+ "second"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_optional_string_enum_property"
+ ]
+ },
+ "property": {
+ "$ref": "#/definitions/string_enum_property"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_required_property.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_required_property"
+ ]
+ },
+ "property": {
+ "$ref": "common.json#/non_empty_string"
+ }
+ },
+ "required": [
+ "type",
+ "property"
+ ]
+ }
+ },
+ {
+ "entity_with_strict_array.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_strict_array"
+ ]
+ },
+ "array": {
+ "type": "array",
+ "items": {
+ "$ref": "entity.json"
+ },
+ "minItems": 1,
+ "clientMinItems": 1,
+ "strictParsing": true
+ }
+ },
+ "required": [
+ "type",
+ "array"
+ ]
+ }
+ },
+ {
+ "entity_with_string_array_property.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_string_array_property"
+ ]
+ },
+ "array": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minItems": 1,
+ "clientMinItems": 1
+ }
+ },
+ "required": [
+ "type",
+ "array"
+ ]
+ }
+ },
+ {
+ "entity_with_string_enum_property.json": {
+ "type": "object",
+ "definitions": {
+ "string_enum_property": {
+ "type": "string",
+ "enum": [
+ "first",
+ "second"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_string_enum_property"
+ ]
+ },
+ "property": {
+ "$ref": "#/definitions/string_enum_property"
+ }
+ },
+ "required": [
+ "type",
+ "property"
+ ]
+ }
+ },
+ {
+ "entity_with_string_enum_property_with_default_value.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_string_enum_property_with_default_value"
+ ]
+ },
+ "value": {
+ "type": "string",
+ "enum": [
+ "first",
+ "second",
+ "third"
+ ],
+ "default_value": "second"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_without_properties.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_without_properties"
+ ]
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_property_with_default_value.json": {
+ "type": "object",
+ "definitions": {
+ "int_prop": {
+ "$ref": "common.json#/non_negative_integer",
+ "default_value": "0"
+ },
+ "url_prop": {
+ "$ref": "common.json#/url",
+ "schemes": [
+ "https"
+ ],
+ "default_value": "https://yandex.ru"
+ },
+ "nested": {
+ "type": "object",
+ "properties": {
+ "int": {
+ "$ref": "#/definitions/int_prop"
+ },
+ "url": {
+ "$ref": "#/definitions/url_prop"
+ },
+ "non_optional": {
+ "type": "string"
+ }
+ },
+ "$description": "translations.json#/entity_with_property_with_default_value_nested",
+ "required": [
+ "non_optional"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_property_with_default_value"
+ ]
+ },
+ "int": {
+ "$ref": "#/definitions/int_prop"
+ },
+ "url": {
+ "$ref": "#/definitions/url_prop"
+ },
+ "nested": {
+ "$ref": "#/definitions/nested"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_simple_properties.json": {
+ "type": "object",
+ "$description": "translations.json#/entity_with_simple_properties",
+ "swift_protocols": "EntityProtocol",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_simple_properties"
+ ]
+ },
+ "id": {
+ "type": "integer",
+ "supports_expressions": false,
+ "$description": "translations.json#/entity_with_simple_properties_id"
+ },
+ "string": {
+ "$ref": "common.json#/non_empty_string",
+ "$description": "translations.json#/entity_with_simple_properties_string",
+ "platforms": [
+ "android",
+ "ios",
+ "web"
+ ]
+ },
+ "integer": {
+ "type": "integer",
+ "$description": "translations.json#/entity_with_simple_properties_integer"
+ },
+ "double": {
+ "type": "number",
+ "$description": "translations.json#/entity_with_simple_properties_double"
+ },
+ "boolean": {
+ "type": "boolean",
+ "$description": "translations.json#/entity_with_simple_properties_boolean"
+ },
+ "boolean_int": {
+ "$ref": "common.json#/boolean_int",
+ "$description": "translations.json#/entity_with_simple_properties_boolean_int",
+ "deprecated": true,
+ "platforms": [
+ "android"
+ ]
+ },
+ "positive_integer": {
+ "$ref": "common.json#/positive_integer",
+ "$description": "translations.json#/entity_with_simple_properties_positive_integer",
+ "platforms": []
+ },
+ "color": {
+ "$ref": "common.json#/color",
+ "$description": "translations.json#/entity_with_simple_properties_color"
+ },
+ "url": {
+ "$ref": "common.json#/url"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "translations.json": {
+ "entity_with_property_with_default_value_nested": {
+ "en": "non_optional is used to suppress auto-generation of default value for object with all-optional fields.",
+ "ru": "non_optional используется, чтобы запретить автогенерацию дефолтного значения для объекта, все свойства которого опциональны."
+ },
+ "entity_with_simple_properties": {
+ "en": "Entity with simple properties.",
+ "ru": "Объект с простыми свойствами."
+ },
+ "entity_with_simple_properties_id": {
+ "en": "ID. Can't contain expressions.",
+ "ru": "Идентификатор. Не может содержать выражение."
+ },
+ "entity_with_simple_properties_string": {
+ "en": "String.",
+ "ru": "Строка."
+ },
+ "entity_with_simple_properties_integer": {
+ "en": "Integer.",
+ "ru": "Целое число."
+ },
+ "entity_with_simple_properties_double": {
+ "en": "Floating point number.",
+ "ru": "Число с плавающей точкой."
+ },
+ "entity_with_simple_properties_boolean": {
+ "en": "Boolean property.",
+ "ru": "Логическое значение."
+ },
+ "entity_with_simple_properties_boolean_int": {
+ "en": "Boolean value in numeric format.",
+ "ru": "Логическое значение в числовом формате."
+ },
+ "entity_with_simple_properties_positive_integer": {
+ "en": "Positive integer.",
+ "ru": "Положительное целое число."
+ },
+ "entity_with_simple_properties_color": {
+ "en": "Color.",
+ "ru": "Цвет."
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/api_generator/tests/references/reference_resolve_references.json b/api_generator/tests/references/reference_resolve_references.json
new file mode 100644
index 000000000..4ca4b7503
--- /dev/null
+++ b/api_generator/tests/references/reference_resolve_references.json
@@ -0,0 +1,962 @@
+{
+ "test_schema": [
+ {
+ "common.json": {
+ "non_empty_string": {
+ "type": "string",
+ "minLength": 1,
+ "clientMinLength": 1
+ },
+ "color": {
+ "type": "string",
+ "format": "color"
+ },
+ "url": {
+ "type": "string",
+ "format": "uri"
+ },
+ "positive_integer": {
+ "type": "integer",
+ "constraint": "number > 0"
+ },
+ "non_negative_integer": {
+ "type": "integer",
+ "constraint": "number >= 0"
+ },
+ "boolean_int": {
+ "type": "integer",
+ "enum": [
+ 0,
+ 1
+ ],
+ "format": "boolean"
+ }
+ }
+ },
+ {
+ "entity.json": {
+ "anyOf": [
+ {
+ "$resolved_refs": [
+ "entity_with_array.json"
+ ],
+ "type": "$defined_entity_with_array"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_array_of_nested_items.json"
+ ],
+ "type": "$defined_entity_with_array_of_nested_items"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_array_with_transform.json"
+ ],
+ "type": "$defined_entity_with_array_with_transform"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_complex_property.json"
+ ],
+ "type": "$defined_entity_with_complex_property"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_complex_property_with_default_value.json"
+ ],
+ "type": "$defined_entity_with_complex_property_with_default_value"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_entity_property.json"
+ ],
+ "type": "$defined_entity_with_entity_property"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_optional_complex_property.json"
+ ],
+ "type": "$defined_entity_with_optional_complex_property"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_optional_property.json"
+ ],
+ "type": "$defined_entity_with_optional_property"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_optional_string_enum_property.json"
+ ],
+ "type": "$defined_entity_with_optional_string_enum_property"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_property_with_default_value.json"
+ ],
+ "type": "$defined_entity_with_property_with_default_value"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_required_property.json"
+ ],
+ "type": "$defined_entity_with_required_property"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_simple_properties.json"
+ ],
+ "type": "$defined_entity_with_simple_properties"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_strict_array.json"
+ ],
+ "type": "$defined_entity_with_strict_array"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_string_array_property.json"
+ ],
+ "type": "$defined_entity_with_string_array_property"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_string_enum_property.json"
+ ],
+ "type": "$defined_entity_with_string_enum_property"
+ },
+ {
+ "$resolved_refs": [
+ "entity_with_string_enum_property_with_default_value.json"
+ ],
+ "type": "$defined_entity_with_string_enum_property_with_default_value"
+ },
+ {
+ "$resolved_refs": [
+ "entity_without_properties.json"
+ ],
+ "type": "$defined_entity_without_properties"
+ }
+ ]
+ }
+ },
+ {
+ "entity_with_array.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_array"
+ ]
+ },
+ "array": {
+ "type": "array",
+ "items": {
+ "$resolved_refs": [
+ "entity.json"
+ ],
+ "type": "$defined_entity"
+ },
+ "minItems": 1,
+ "clientMinItems": 1
+ }
+ },
+ "required": [
+ "type",
+ "array"
+ ]
+ }
+ },
+ {
+ "entity_with_array_of_nested_items.json": {
+ "type": "object",
+ "definitions": {
+ "item": {
+ "type": "object",
+ "properties": {
+ "property": {
+ "$resolved_refs": [
+ "common.json#/non_empty_string"
+ ],
+ "type": "string",
+ "minLength": 1,
+ "clientMinLength": 1
+ },
+ "entity": {
+ "$resolved_refs": [
+ "entity.json"
+ ],
+ "type": "$defined_entity"
+ }
+ },
+ "required": [
+ "property",
+ "entity"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_array_of_nested_items"
+ ]
+ },
+ "items": {
+ "type": "array",
+ "items": {
+ "$resolved_refs": [
+ "#/definitions/item"
+ ],
+ "type": "object",
+ "properties": {
+ "property": {
+ "$resolved_refs": [
+ "common.json#/non_empty_string"
+ ],
+ "type": "string",
+ "minLength": 1,
+ "clientMinLength": 1
+ },
+ "entity": {
+ "$resolved_refs": [
+ "entity.json"
+ ],
+ "type": "$defined_entity"
+ }
+ },
+ "required": [
+ "property",
+ "entity"
+ ],
+ "$typename": "item"
+ },
+ "minItems": 1,
+ "clientMinItems": 1
+ }
+ },
+ "required": [
+ "type",
+ "items"
+ ]
+ }
+ },
+ {
+ "entity_with_array_with_transform.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_array_with_transform"
+ ]
+ },
+ "array": {
+ "type": "array",
+ "items": {
+ "$resolved_refs": [
+ "common.json#/color"
+ ],
+ "type": "string",
+ "format": "color"
+ },
+ "minItems": 1,
+ "clientMinItems": 1
+ }
+ },
+ "required": [
+ "type",
+ "array"
+ ]
+ }
+ },
+ {
+ "entity_with_complex_property.json": {
+ "type": "object",
+ "definitions": {
+ "complex_property": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "$resolved_refs": [
+ "common.json#/url"
+ ],
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "required": [
+ "value"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_complex_property"
+ ]
+ },
+ "property": {
+ "$resolved_refs": [
+ "#/definitions/complex_property"
+ ],
+ "type": "object",
+ "properties": {
+ "value": {
+ "$resolved_refs": [
+ "common.json#/url"
+ ],
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "required": [
+ "value"
+ ],
+ "$typename": "complex_property"
+ }
+ },
+ "required": [
+ "type",
+ "property"
+ ]
+ }
+ },
+ {
+ "entity_with_complex_property_with_default_value.json": {
+ "type": "object",
+ "definitions": {
+ "complex_property": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "value"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_complex_property_with_default_value"
+ ]
+ },
+ "property": {
+ "default_value": "{ \"value\": \"Default text\" }",
+ "$resolved_refs": [
+ "#/definitions/complex_property"
+ ],
+ "type": "object",
+ "properties": {
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "value"
+ ],
+ "$typename": "complex_property"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_entity_property.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_entity_property"
+ ]
+ },
+ "entity": {
+ "default_value": "{ \"type\": \"entity_with_string_enum_property\", \"property\": \"second\" }",
+ "$resolved_refs": [
+ "entity.json"
+ ],
+ "type": "$defined_entity"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_optional_complex_property.json": {
+ "type": "object",
+ "definitions": {
+ "complex_property": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "$resolved_refs": [
+ "common.json#/url"
+ ],
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "required": [
+ "value"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_optional_complex_property"
+ ]
+ },
+ "property": {
+ "$resolved_refs": [
+ "#/definitions/complex_property"
+ ],
+ "type": "object",
+ "properties": {
+ "value": {
+ "$resolved_refs": [
+ "common.json#/url"
+ ],
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "required": [
+ "value"
+ ],
+ "$typename": "complex_property"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_optional_property.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_optional_property"
+ ]
+ },
+ "property": {
+ "$resolved_refs": [
+ "common.json#/non_empty_string"
+ ],
+ "type": "string",
+ "minLength": 1,
+ "clientMinLength": 1
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_optional_string_enum_property.json": {
+ "type": "object",
+ "definitions": {
+ "string_enum_property": {
+ "type": "string",
+ "enum": [
+ "first",
+ "second"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_optional_string_enum_property"
+ ]
+ },
+ "property": {
+ "$resolved_refs": [
+ "#/definitions/string_enum_property"
+ ],
+ "type": "string",
+ "enum": [
+ "first",
+ "second"
+ ],
+ "$typename": "string_enum_property"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_required_property.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_required_property"
+ ]
+ },
+ "property": {
+ "$resolved_refs": [
+ "common.json#/non_empty_string"
+ ],
+ "type": "string",
+ "minLength": 1,
+ "clientMinLength": 1
+ }
+ },
+ "required": [
+ "type",
+ "property"
+ ]
+ }
+ },
+ {
+ "entity_with_strict_array.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_strict_array"
+ ]
+ },
+ "array": {
+ "type": "array",
+ "items": {
+ "$resolved_refs": [
+ "entity.json"
+ ],
+ "type": "$defined_entity"
+ },
+ "minItems": 1,
+ "clientMinItems": 1,
+ "strictParsing": true
+ }
+ },
+ "required": [
+ "type",
+ "array"
+ ]
+ }
+ },
+ {
+ "entity_with_string_array_property.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_string_array_property"
+ ]
+ },
+ "array": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minItems": 1,
+ "clientMinItems": 1
+ }
+ },
+ "required": [
+ "type",
+ "array"
+ ]
+ }
+ },
+ {
+ "entity_with_string_enum_property.json": {
+ "type": "object",
+ "definitions": {
+ "string_enum_property": {
+ "type": "string",
+ "enum": [
+ "first",
+ "second"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_string_enum_property"
+ ]
+ },
+ "property": {
+ "$resolved_refs": [
+ "#/definitions/string_enum_property"
+ ],
+ "type": "string",
+ "enum": [
+ "first",
+ "second"
+ ],
+ "$typename": "string_enum_property"
+ }
+ },
+ "required": [
+ "type",
+ "property"
+ ]
+ }
+ },
+ {
+ "entity_with_string_enum_property_with_default_value.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_string_enum_property_with_default_value"
+ ]
+ },
+ "value": {
+ "type": "string",
+ "enum": [
+ "first",
+ "second",
+ "third"
+ ],
+ "default_value": "second"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_without_properties.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_without_properties"
+ ]
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_property_with_default_value.json": {
+ "type": "object",
+ "definitions": {
+ "int_prop": {
+ "default_value": "0",
+ "$resolved_refs": [
+ "common.json#/non_negative_integer"
+ ],
+ "type": "integer",
+ "constraint": "number >= 0"
+ },
+ "url_prop": {
+ "schemes": [
+ "https"
+ ],
+ "default_value": "https://yandex.ru",
+ "$resolved_refs": [
+ "common.json#/url"
+ ],
+ "type": "string",
+ "format": "uri"
+ },
+ "nested": {
+ "type": "object",
+ "properties": {
+ "int": {
+ "$resolved_refs": [
+ "#/definitions/int_prop",
+ "common.json#/non_negative_integer"
+ ],
+ "default_value": "0",
+ "type": "integer",
+ "constraint": "number >= 0"
+ },
+ "url": {
+ "$resolved_refs": [
+ "#/definitions/url_prop",
+ "common.json#/url"
+ ],
+ "schemes": [
+ "https"
+ ],
+ "default_value": "https://yandex.ru",
+ "type": "string",
+ "format": "uri"
+ },
+ "non_optional": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "non_optional"
+ ],
+ "description_translations": {
+ "en": "non_optional is used to suppress auto-generation of default value for object with all-optional fields.",
+ "ru": "non_optional используется, чтобы запретить автогенерацию дефолтного значения для объекта, все свойства которого опциональны."
+ }
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_property_with_default_value"
+ ]
+ },
+ "int": {
+ "$resolved_refs": [
+ "#/definitions/int_prop",
+ "common.json#/non_negative_integer"
+ ],
+ "default_value": "0",
+ "type": "integer",
+ "constraint": "number >= 0"
+ },
+ "url": {
+ "$resolved_refs": [
+ "#/definitions/url_prop",
+ "common.json#/url"
+ ],
+ "schemes": [
+ "https"
+ ],
+ "default_value": "https://yandex.ru",
+ "type": "string",
+ "format": "uri"
+ },
+ "nested": {
+ "$resolved_refs": [
+ "#/definitions/nested"
+ ],
+ "type": "object",
+ "properties": {
+ "int": {
+ "$resolved_refs": [
+ "#/definitions/int_prop",
+ "common.json#/non_negative_integer"
+ ],
+ "default_value": "0",
+ "type": "integer",
+ "constraint": "number >= 0",
+ "$typename": "int_prop"
+ },
+ "url": {
+ "$resolved_refs": [
+ "#/definitions/url_prop",
+ "common.json#/url"
+ ],
+ "schemes": [
+ "https"
+ ],
+ "default_value": "https://yandex.ru",
+ "type": "string",
+ "format": "uri",
+ "$typename": "url_prop"
+ },
+ "non_optional": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "non_optional"
+ ],
+ "$typename": "nested",
+ "description_translations": {
+ "en": "non_optional is used to suppress auto-generation of default value for object with all-optional fields.",
+ "ru": "non_optional используется, чтобы запретить автогенерацию дефолтного значения для объекта, все свойства которого опциональны."
+ }
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_simple_properties.json": {
+ "type": "object",
+ "swift_protocols": "EntityProtocol",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_simple_properties"
+ ]
+ },
+ "id": {
+ "type": "integer",
+ "supports_expressions": false,
+ "description_translations": {
+ "en": "ID. Can't contain expressions.",
+ "ru": "Идентификатор. Не может содержать выражение."
+ }
+ },
+ "string": {
+ "platforms": [
+ "android",
+ "ios",
+ "web"
+ ],
+ "description_translations": {
+ "en": "String.",
+ "ru": "Строка."
+ },
+ "$resolved_refs": [
+ "common.json#/non_empty_string"
+ ],
+ "type": "string",
+ "minLength": 1,
+ "clientMinLength": 1
+ },
+ "integer": {
+ "type": "integer",
+ "description_translations": {
+ "en": "Integer.",
+ "ru": "Целое число."
+ }
+ },
+ "double": {
+ "type": "number",
+ "description_translations": {
+ "en": "Floating point number.",
+ "ru": "Число с плавающей точкой."
+ }
+ },
+ "boolean": {
+ "type": "boolean",
+ "description_translations": {
+ "en": "Boolean property.",
+ "ru": "Логическое значение."
+ }
+ },
+ "boolean_int": {
+ "deprecated": true,
+ "platforms": [
+ "android"
+ ],
+ "description_translations": {
+ "en": "Boolean value in numeric format.",
+ "ru": "Логическое значение в числовом формате."
+ },
+ "$resolved_refs": [
+ "common.json#/boolean_int"
+ ],
+ "type": "integer",
+ "enum": [
+ 0,
+ 1
+ ],
+ "format": "boolean"
+ },
+ "positive_integer": {
+ "platforms": [],
+ "description_translations": {
+ "en": "Positive integer.",
+ "ru": "Положительное целое число."
+ },
+ "$resolved_refs": [
+ "common.json#/positive_integer"
+ ],
+ "type": "integer",
+ "constraint": "number > 0"
+ },
+ "color": {
+ "description_translations": {
+ "en": "Color.",
+ "ru": "Цвет."
+ },
+ "$resolved_refs": [
+ "common.json#/color"
+ ],
+ "type": "string",
+ "format": "color"
+ },
+ "url": {
+ "$resolved_refs": [
+ "common.json#/url"
+ ],
+ "type": "string",
+ "format": "uri"
+ }
+ },
+ "required": [
+ "type"
+ ],
+ "description_translations": {
+ "en": "Entity with simple properties.",
+ "ru": "Объект с простыми свойствами."
+ }
+ }
+ },
+ {
+ "translations.json": {
+ "entity_with_property_with_default_value_nested": {
+ "en": "non_optional is used to suppress auto-generation of default value for object with all-optional fields.",
+ "ru": "non_optional используется, чтобы запретить автогенерацию дефолтного значения для объекта, все свойства которого опциональны."
+ },
+ "entity_with_simple_properties": {
+ "en": "Entity with simple properties.",
+ "ru": "Объект с простыми свойствами."
+ },
+ "entity_with_simple_properties_id": {
+ "en": "ID. Can't contain expressions.",
+ "ru": "Идентификатор. Не может содержать выражение."
+ },
+ "entity_with_simple_properties_string": {
+ "en": "String.",
+ "ru": "Строка."
+ },
+ "entity_with_simple_properties_integer": {
+ "en": "Integer.",
+ "ru": "Целое число."
+ },
+ "entity_with_simple_properties_double": {
+ "en": "Floating point number.",
+ "ru": "Число с плавающей точкой."
+ },
+ "entity_with_simple_properties_boolean": {
+ "en": "Boolean property.",
+ "ru": "Логическое значение."
+ },
+ "entity_with_simple_properties_boolean_int": {
+ "en": "Boolean value in numeric format.",
+ "ru": "Логическое значение в числовом формате."
+ },
+ "entity_with_simple_properties_positive_integer": {
+ "en": "Positive integer.",
+ "ru": "Положительное целое число."
+ },
+ "entity_with_simple_properties_color": {
+ "en": "Color.",
+ "ru": "Цвет."
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/api_generator/tests/references/reference_resolve_structure.json b/api_generator/tests/references/reference_resolve_structure.json
new file mode 100644
index 000000000..e6b5a737f
--- /dev/null
+++ b/api_generator/tests/references/reference_resolve_structure.json
@@ -0,0 +1,656 @@
+{
+ "test_schema": [
+ {
+ "common.json": {
+ "non_empty_string": {
+ "type": "string",
+ "minLength": 1,
+ "clientMinLength": 1
+ },
+ "color": {
+ "type": "string",
+ "format": "color"
+ },
+ "url": {
+ "type": "string",
+ "format": "uri"
+ },
+ "positive_integer": {
+ "type": "integer",
+ "constraint": "number > 0"
+ },
+ "non_negative_integer": {
+ "type": "integer",
+ "constraint": "number >= 0"
+ },
+ "boolean_int": {
+ "type": "integer",
+ "enum": [
+ 0,
+ 1
+ ],
+ "format": "boolean"
+ }
+ }
+ },
+ {
+ "entity.json": {
+ "anyOf": [
+ {
+ "$ref": "entity_with_array.json"
+ },
+ {
+ "$ref": "entity_with_array_of_nested_items.json"
+ },
+ {
+ "$ref": "entity_with_array_with_transform.json"
+ },
+ {
+ "$ref": "entity_with_complex_property.json"
+ },
+ {
+ "$ref": "entity_with_complex_property_with_default_value.json"
+ },
+ {
+ "$ref": "entity_with_entity_property.json"
+ },
+ {
+ "$ref": "entity_with_optional_complex_property.json"
+ },
+ {
+ "$ref": "entity_with_optional_property.json"
+ },
+ {
+ "$ref": "entity_with_optional_string_enum_property.json"
+ },
+ {
+ "$ref": "entity_with_property_with_default_value.json"
+ },
+ {
+ "$ref": "entity_with_required_property.json"
+ },
+ {
+ "$ref": "entity_with_simple_properties.json"
+ },
+ {
+ "$ref": "entity_with_strict_array.json"
+ },
+ {
+ "$ref": "entity_with_string_array_property.json"
+ },
+ {
+ "$ref": "entity_with_string_enum_property.json"
+ },
+ {
+ "$ref": "entity_with_string_enum_property_with_default_value.json"
+ },
+ {
+ "$ref": "entity_without_properties.json"
+ }
+ ]
+ }
+ },
+ {
+ "entity_with_array.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_array"
+ ]
+ },
+ "array": {
+ "type": "array",
+ "items": {
+ "$ref": "entity.json"
+ },
+ "minItems": 1,
+ "clientMinItems": 1
+ }
+ },
+ "required": [
+ "type",
+ "array"
+ ]
+ }
+ },
+ {
+ "entity_with_array_of_nested_items.json": {
+ "type": "object",
+ "definitions": {
+ "item": {
+ "type": "object",
+ "properties": {
+ "property": {
+ "$ref": "common.json#/non_empty_string"
+ },
+ "entity": {
+ "$ref": "entity.json"
+ }
+ },
+ "required": [
+ "property",
+ "entity"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_array_of_nested_items"
+ ]
+ },
+ "items": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/item"
+ },
+ "minItems": 1,
+ "clientMinItems": 1
+ }
+ },
+ "required": [
+ "type",
+ "items"
+ ]
+ }
+ },
+ {
+ "entity_with_array_with_transform.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_array_with_transform"
+ ]
+ },
+ "array": {
+ "type": "array",
+ "items": {
+ "$ref": "common.json#/color"
+ },
+ "minItems": 1,
+ "clientMinItems": 1
+ }
+ },
+ "required": [
+ "type",
+ "array"
+ ]
+ }
+ },
+ {
+ "entity_with_complex_property.json": {
+ "type": "object",
+ "definitions": {
+ "complex_property": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "$ref": "common.json#/url"
+ }
+ },
+ "required": [
+ "value"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_complex_property"
+ ]
+ },
+ "property": {
+ "$ref": "#/definitions/complex_property"
+ }
+ },
+ "required": [
+ "type",
+ "property"
+ ]
+ }
+ },
+ {
+ "entity_with_complex_property_with_default_value.json": {
+ "type": "object",
+ "definitions": {
+ "complex_property": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "value"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_complex_property_with_default_value"
+ ]
+ },
+ "property": {
+ "$ref": "#/definitions/complex_property",
+ "default_value": "{ \"value\": \"Default text\" }"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_entity_property.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_entity_property"
+ ]
+ },
+ "entity": {
+ "$ref": "entity.json",
+ "default_value": "{ \"type\": \"entity_with_string_enum_property\", \"property\": \"second\" }"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_optional_complex_property.json": {
+ "type": "object",
+ "definitions": {
+ "complex_property": {
+ "type": "object",
+ "properties": {
+ "value": {
+ "$ref": "common.json#/url"
+ }
+ },
+ "required": [
+ "value"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_optional_complex_property"
+ ]
+ },
+ "property": {
+ "$ref": "#/definitions/complex_property"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_optional_property.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_optional_property"
+ ]
+ },
+ "property": {
+ "$ref": "common.json#/non_empty_string"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_optional_string_enum_property.json": {
+ "type": "object",
+ "definitions": {
+ "string_enum_property": {
+ "type": "string",
+ "enum": [
+ "first",
+ "second"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_optional_string_enum_property"
+ ]
+ },
+ "property": {
+ "$ref": "#/definitions/string_enum_property"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_required_property.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_required_property"
+ ]
+ },
+ "property": {
+ "$ref": "common.json#/non_empty_string"
+ }
+ },
+ "required": [
+ "type",
+ "property"
+ ]
+ }
+ },
+ {
+ "entity_with_strict_array.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_strict_array"
+ ]
+ },
+ "array": {
+ "type": "array",
+ "items": {
+ "$ref": "entity.json"
+ },
+ "minItems": 1,
+ "clientMinItems": 1,
+ "strictParsing": true
+ }
+ },
+ "required": [
+ "type",
+ "array"
+ ]
+ }
+ },
+ {
+ "entity_with_string_array_property.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_string_array_property"
+ ]
+ },
+ "array": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minItems": 1,
+ "clientMinItems": 1
+ }
+ },
+ "required": [
+ "type",
+ "array"
+ ]
+ }
+ },
+ {
+ "entity_with_string_enum_property.json": {
+ "type": "object",
+ "definitions": {
+ "string_enum_property": {
+ "type": "string",
+ "enum": [
+ "first",
+ "second"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_string_enum_property"
+ ]
+ },
+ "property": {
+ "$ref": "#/definitions/string_enum_property"
+ }
+ },
+ "required": [
+ "type",
+ "property"
+ ]
+ }
+ },
+ {
+ "entity_with_string_enum_property_with_default_value.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_string_enum_property_with_default_value"
+ ]
+ },
+ "value": {
+ "type": "string",
+ "enum": [
+ "first",
+ "second",
+ "third"
+ ],
+ "default_value": "second"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_without_properties.json": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_without_properties"
+ ]
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_property_with_default_value.json": {
+ "type": "object",
+ "definitions": {
+ "int_prop": {
+ "$ref": "common.json#/non_negative_integer",
+ "default_value": "0"
+ },
+ "url_prop": {
+ "$ref": "common.json#/url",
+ "schemes": [
+ "https"
+ ],
+ "default_value": "https://yandex.ru"
+ },
+ "nested": {
+ "type": "object",
+ "properties": {
+ "int": {
+ "$ref": "#/definitions/int_prop"
+ },
+ "url": {
+ "$ref": "#/definitions/url_prop"
+ },
+ "non_optional": {
+ "type": "string"
+ }
+ },
+ "$description": "translations.json#/entity_with_property_with_default_value_nested",
+ "required": [
+ "non_optional"
+ ]
+ }
+ },
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_property_with_default_value"
+ ]
+ },
+ "int": {
+ "$ref": "#/definitions/int_prop"
+ },
+ "url": {
+ "$ref": "#/definitions/url_prop"
+ },
+ "nested": {
+ "$ref": "#/definitions/nested"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "entity_with_simple_properties.json": {
+ "type": "object",
+ "$description": "translations.json#/entity_with_simple_properties",
+ "swift_protocols": "EntityProtocol",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": [
+ "entity_with_simple_properties"
+ ]
+ },
+ "id": {
+ "type": "integer",
+ "supports_expressions": false,
+ "$description": "translations.json#/entity_with_simple_properties_id"
+ },
+ "string": {
+ "$ref": "common.json#/non_empty_string",
+ "$description": "translations.json#/entity_with_simple_properties_string",
+ "platforms": [
+ "android",
+ "ios",
+ "web"
+ ]
+ },
+ "integer": {
+ "type": "integer",
+ "$description": "translations.json#/entity_with_simple_properties_integer"
+ },
+ "double": {
+ "type": "number",
+ "$description": "translations.json#/entity_with_simple_properties_double"
+ },
+ "boolean": {
+ "type": "boolean",
+ "$description": "translations.json#/entity_with_simple_properties_boolean"
+ },
+ "boolean_int": {
+ "$ref": "common.json#/boolean_int",
+ "$description": "translations.json#/entity_with_simple_properties_boolean_int",
+ "deprecated": true,
+ "platforms": [
+ "android"
+ ]
+ },
+ "positive_integer": {
+ "$ref": "common.json#/positive_integer",
+ "$description": "translations.json#/entity_with_simple_properties_positive_integer",
+ "platforms": []
+ },
+ "color": {
+ "$ref": "common.json#/color",
+ "$description": "translations.json#/entity_with_simple_properties_color"
+ },
+ "url": {
+ "$ref": "common.json#/url"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ },
+ {
+ "translations.json": {
+ "entity_with_property_with_default_value_nested": {
+ "en": "non_optional is used to suppress auto-generation of default value for object with all-optional fields.",
+ "ru": "non_optional используется, чтобы запретить автогенерацию дефолтного значения для объекта, все свойства которого опциональны."
+ },
+ "entity_with_simple_properties": {
+ "en": "Entity with simple properties.",
+ "ru": "Объект с простыми свойствами."
+ },
+ "entity_with_simple_properties_id": {
+ "en": "ID. Can't contain expressions.",
+ "ru": "Идентификатор. Не может содержать выражение."
+ },
+ "entity_with_simple_properties_string": {
+ "en": "String.",
+ "ru": "Строка."
+ },
+ "entity_with_simple_properties_integer": {
+ "en": "Integer.",
+ "ru": "Целое число."
+ },
+ "entity_with_simple_properties_double": {
+ "en": "Floating point number.",
+ "ru": "Число с плавающей точкой."
+ },
+ "entity_with_simple_properties_boolean": {
+ "en": "Boolean property.",
+ "ru": "Логическое значение."
+ },
+ "entity_with_simple_properties_boolean_int": {
+ "en": "Boolean value in numeric format.",
+ "ru": "Логическое значение в числовом формате."
+ },
+ "entity_with_simple_properties_positive_integer": {
+ "en": "Positive integer.",
+ "ru": "Положительное целое число."
+ },
+ "entity_with_simple_properties_color": {
+ "en": "Color.",
+ "ru": "Цвет."
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/api_generator/tests/references/swift/Entity.swift b/api_generator/tests/references/swift/Entity.swift
new file mode 100644
index 000000000..489d32df0
--- /dev/null
+++ b/api_generator/tests/references/swift/Entity.swift
@@ -0,0 +1,115 @@
+// Generated code. Do not modify.
+
+import CommonCore
+import Foundation
+import Serialization
+
+public enum Entity {
+ case entityWithArray(EntityWithArray)
+ case entityWithArrayOfNestedItems(EntityWithArrayOfNestedItems)
+ case entityWithArrayWithTransform(EntityWithArrayWithTransform)
+ case entityWithComplexProperty(EntityWithComplexProperty)
+ case entityWithComplexPropertyWithDefaultValue(EntityWithComplexPropertyWithDefaultValue)
+ case entityWithEntityProperty(EntityWithEntityProperty)
+ case entityWithOptionalComplexProperty(EntityWithOptionalComplexProperty)
+ case entityWithOptionalProperty(EntityWithOptionalProperty)
+ case entityWithOptionalStringEnumProperty(EntityWithOptionalStringEnumProperty)
+ case entityWithPropertyWithDefaultValue(EntityWithPropertyWithDefaultValue)
+ case entityWithRequiredProperty(EntityWithRequiredProperty)
+ case entityWithSimpleProperties(EntityWithSimpleProperties)
+ case entityWithStrictArray(EntityWithStrictArray)
+ case entityWithStringArrayProperty(EntityWithStringArrayProperty)
+ case entityWithStringEnumProperty(EntityWithStringEnumProperty)
+ case entityWithStringEnumPropertyWithDefaultValue(EntityWithStringEnumPropertyWithDefaultValue)
+ case entityWithoutProperties(EntityWithoutProperties)
+
+ public var value: Serializable {
+ switch self {
+ case let .entityWithArray(value):
+ return value
+ case let .entityWithArrayOfNestedItems(value):
+ return value
+ case let .entityWithArrayWithTransform(value):
+ return value
+ case let .entityWithComplexProperty(value):
+ return value
+ case let .entityWithComplexPropertyWithDefaultValue(value):
+ return value
+ case let .entityWithEntityProperty(value):
+ return value
+ case let .entityWithOptionalComplexProperty(value):
+ return value
+ case let .entityWithOptionalProperty(value):
+ return value
+ case let .entityWithOptionalStringEnumProperty(value):
+ return value
+ case let .entityWithPropertyWithDefaultValue(value):
+ return value
+ case let .entityWithRequiredProperty(value):
+ return value
+ case let .entityWithSimpleProperties(value):
+ return value
+ case let .entityWithStrictArray(value):
+ return value
+ case let .entityWithStringArrayProperty(value):
+ return value
+ case let .entityWithStringEnumProperty(value):
+ return value
+ case let .entityWithStringEnumPropertyWithDefaultValue(value):
+ return value
+ case let .entityWithoutProperties(value):
+ return value
+ }
+ }
+}
+
+#if DEBUG
+extension Entity: Equatable {
+ public static func ==(lhs: Entity, rhs: Entity) -> Bool {
+ switch (lhs, rhs) {
+ case let (.entityWithArray(l), .entityWithArray(r)):
+ return l == r
+ case let (.entityWithArrayOfNestedItems(l), .entityWithArrayOfNestedItems(r)):
+ return l == r
+ case let (.entityWithArrayWithTransform(l), .entityWithArrayWithTransform(r)):
+ return l == r
+ case let (.entityWithComplexProperty(l), .entityWithComplexProperty(r)):
+ return l == r
+ case let (.entityWithComplexPropertyWithDefaultValue(l), .entityWithComplexPropertyWithDefaultValue(r)):
+ return l == r
+ case let (.entityWithEntityProperty(l), .entityWithEntityProperty(r)):
+ return l == r
+ case let (.entityWithOptionalComplexProperty(l), .entityWithOptionalComplexProperty(r)):
+ return l == r
+ case let (.entityWithOptionalProperty(l), .entityWithOptionalProperty(r)):
+ return l == r
+ case let (.entityWithOptionalStringEnumProperty(l), .entityWithOptionalStringEnumProperty(r)):
+ return l == r
+ case let (.entityWithPropertyWithDefaultValue(l), .entityWithPropertyWithDefaultValue(r)):
+ return l == r
+ case let (.entityWithRequiredProperty(l), .entityWithRequiredProperty(r)):
+ return l == r
+ case let (.entityWithSimpleProperties(l), .entityWithSimpleProperties(r)):
+ return l == r
+ case let (.entityWithStrictArray(l), .entityWithStrictArray(r)):
+ return l == r
+ case let (.entityWithStringArrayProperty(l), .entityWithStringArrayProperty(r)):
+ return l == r
+ case let (.entityWithStringEnumProperty(l), .entityWithStringEnumProperty(r)):
+ return l == r
+ case let (.entityWithStringEnumPropertyWithDefaultValue(l), .entityWithStringEnumPropertyWithDefaultValue(r)):
+ return l == r
+ case let (.entityWithoutProperties(l), .entityWithoutProperties(r)):
+ return l == r
+ default:
+ return false
+ }
+ }
+}
+#endif
+
+extension Entity: Serializable {
+ public func toDictionary() -> [String: ValidSerializationValue] {
+ return value.toDictionary()
+ }
+}
diff --git a/api_generator/tests/references/swift/EntityTemplate.swift b/api_generator/tests/references/swift/EntityTemplate.swift
new file mode 100644
index 000000000..95d667e1d
--- /dev/null
+++ b/api_generator/tests/references/swift/EntityTemplate.swift
@@ -0,0 +1,444 @@
+// Generated code. Do not modify.
+
+import CommonCore
+import Foundation
+import Serialization
+
+public enum EntityTemplate: TemplateValue {
+ case entityWithArrayTemplate(EntityWithArrayTemplate)
+ case entityWithArrayOfNestedItemsTemplate(EntityWithArrayOfNestedItemsTemplate)
+ case entityWithArrayWithTransformTemplate(EntityWithArrayWithTransformTemplate)
+ case entityWithComplexPropertyTemplate(EntityWithComplexPropertyTemplate)
+ case entityWithComplexPropertyWithDefaultValueTemplate(EntityWithComplexPropertyWithDefaultValueTemplate)
+ case entityWithEntityPropertyTemplate(EntityWithEntityPropertyTemplate)
+ case entityWithOptionalComplexPropertyTemplate(EntityWithOptionalComplexPropertyTemplate)
+ case entityWithOptionalPropertyTemplate(EntityWithOptionalPropertyTemplate)
+ case entityWithOptionalStringEnumPropertyTemplate(EntityWithOptionalStringEnumPropertyTemplate)
+ case entityWithPropertyWithDefaultValueTemplate(EntityWithPropertyWithDefaultValueTemplate)
+ case entityWithRequiredPropertyTemplate(EntityWithRequiredPropertyTemplate)
+ case entityWithSimplePropertiesTemplate(EntityWithSimplePropertiesTemplate)
+ case entityWithStrictArrayTemplate(EntityWithStrictArrayTemplate)
+ case entityWithStringArrayPropertyTemplate(EntityWithStringArrayPropertyTemplate)
+ case entityWithStringEnumPropertyTemplate(EntityWithStringEnumPropertyTemplate)
+ case entityWithStringEnumPropertyWithDefaultValueTemplate(EntityWithStringEnumPropertyWithDefaultValueTemplate)
+ case entityWithoutPropertiesTemplate(EntityWithoutPropertiesTemplate)
+
+ public var value: Any {
+ switch self {
+ case let .entityWithArrayTemplate(value):
+ return value
+ case let .entityWithArrayOfNestedItemsTemplate(value):
+ return value
+ case let .entityWithArrayWithTransformTemplate(value):
+ return value
+ case let .entityWithComplexPropertyTemplate(value):
+ return value
+ case let .entityWithComplexPropertyWithDefaultValueTemplate(value):
+ return value
+ case let .entityWithEntityPropertyTemplate(value):
+ return value
+ case let .entityWithOptionalComplexPropertyTemplate(value):
+ return value
+ case let .entityWithOptionalPropertyTemplate(value):
+ return value
+ case let .entityWithOptionalStringEnumPropertyTemplate(value):
+ return value
+ case let .entityWithPropertyWithDefaultValueTemplate(value):
+ return value
+ case let .entityWithRequiredPropertyTemplate(value):
+ return value
+ case let .entityWithSimplePropertiesTemplate(value):
+ return value
+ case let .entityWithStrictArrayTemplate(value):
+ return value
+ case let .entityWithStringArrayPropertyTemplate(value):
+ return value
+ case let .entityWithStringEnumPropertyTemplate(value):
+ return value
+ case let .entityWithStringEnumPropertyWithDefaultValueTemplate(value):
+ return value
+ case let .entityWithoutPropertiesTemplate(value):
+ return value
+ }
+ }
+
+ public func resolveParent(templates: Templates) throws -> EntityTemplate {
+ switch self {
+ case let .entityWithArrayTemplate(value):
+ return .entityWithArrayTemplate(try value.resolveParent(templates: templates))
+ case let .entityWithArrayOfNestedItemsTemplate(value):
+ return .entityWithArrayOfNestedItemsTemplate(try value.resolveParent(templates: templates))
+ case let .entityWithArrayWithTransformTemplate(value):
+ return .entityWithArrayWithTransformTemplate(try value.resolveParent(templates: templates))
+ case let .entityWithComplexPropertyTemplate(value):
+ return .entityWithComplexPropertyTemplate(try value.resolveParent(templates: templates))
+ case let .entityWithComplexPropertyWithDefaultValueTemplate(value):
+ return .entityWithComplexPropertyWithDefaultValueTemplate(try value.resolveParent(templates: templates))
+ case let .entityWithEntityPropertyTemplate(value):
+ return .entityWithEntityPropertyTemplate(try value.resolveParent(templates: templates))
+ case let .entityWithOptionalComplexPropertyTemplate(value):
+ return .entityWithOptionalComplexPropertyTemplate(try value.resolveParent(templates: templates))
+ case let .entityWithOptionalPropertyTemplate(value):
+ return .entityWithOptionalPropertyTemplate(try value.resolveParent(templates: templates))
+ case let .entityWithOptionalStringEnumPropertyTemplate(value):
+ return .entityWithOptionalStringEnumPropertyTemplate(try value.resolveParent(templates: templates))
+ case let .entityWithPropertyWithDefaultValueTemplate(value):
+ return .entityWithPropertyWithDefaultValueTemplate(try value.resolveParent(templates: templates))
+ case let .entityWithRequiredPropertyTemplate(value):
+ return .entityWithRequiredPropertyTemplate(try value.resolveParent(templates: templates))
+ case let .entityWithSimplePropertiesTemplate(value):
+ return .entityWithSimplePropertiesTemplate(try value.resolveParent(templates: templates))
+ case let .entityWithStrictArrayTemplate(value):
+ return .entityWithStrictArrayTemplate(try value.resolveParent(templates: templates))
+ case let .entityWithStringArrayPropertyTemplate(value):
+ return .entityWithStringArrayPropertyTemplate(try value.resolveParent(templates: templates))
+ case let .entityWithStringEnumPropertyTemplate(value):
+ return .entityWithStringEnumPropertyTemplate(try value.resolveParent(templates: templates))
+ case let .entityWithStringEnumPropertyWithDefaultValueTemplate(value):
+ return .entityWithStringEnumPropertyWithDefaultValueTemplate(try value.resolveParent(templates: templates))
+ case let .entityWithoutPropertiesTemplate(value):
+ return .entityWithoutPropertiesTemplate(try value.resolveParent(templates: templates))
+ }
+ }
+
+ public static func resolveValue(context: Context, parent: EntityTemplate?, useOnlyLinks: Bool) -> DeserializationResult {
+ guard let parent = parent else {
+ if useOnlyLinks {
+ return .failure(NonEmptyArray(.missingType(representation: context.templateData)))
+ } else {
+ return resolveUnknownValue(context: context, useOnlyLinks: useOnlyLinks)
+ }
+ }
+
+ switch parent {
+ case let .entityWithArrayTemplate(value):
+ let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithArray(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithArray(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case let .entityWithArrayOfNestedItemsTemplate(value):
+ let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithArrayOfNestedItems(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithArrayOfNestedItems(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case let .entityWithArrayWithTransformTemplate(value):
+ let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithArrayWithTransform(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithArrayWithTransform(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case let .entityWithComplexPropertyTemplate(value):
+ let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithComplexProperty(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithComplexProperty(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case let .entityWithComplexPropertyWithDefaultValueTemplate(value):
+ let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithComplexPropertyWithDefaultValue(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithComplexPropertyWithDefaultValue(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case let .entityWithEntityPropertyTemplate(value):
+ let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithEntityProperty(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithEntityProperty(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case let .entityWithOptionalComplexPropertyTemplate(value):
+ let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithOptionalComplexProperty(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithOptionalComplexProperty(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case let .entityWithOptionalPropertyTemplate(value):
+ let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithOptionalProperty(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithOptionalProperty(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case let .entityWithOptionalStringEnumPropertyTemplate(value):
+ let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithOptionalStringEnumProperty(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithOptionalStringEnumProperty(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case let .entityWithPropertyWithDefaultValueTemplate(value):
+ let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithPropertyWithDefaultValue(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithPropertyWithDefaultValue(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case let .entityWithRequiredPropertyTemplate(value):
+ let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithRequiredProperty(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithRequiredProperty(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case let .entityWithSimplePropertiesTemplate(value):
+ let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithSimpleProperties(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithSimpleProperties(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case let .entityWithStrictArrayTemplate(value):
+ let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithStrictArray(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithStrictArray(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case let .entityWithStringArrayPropertyTemplate(value):
+ let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithStringArrayProperty(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithStringArrayProperty(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case let .entityWithStringEnumPropertyTemplate(value):
+ let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithStringEnumProperty(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithStringEnumProperty(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case let .entityWithStringEnumPropertyWithDefaultValueTemplate(value):
+ let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithStringEnumPropertyWithDefaultValue(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithStringEnumPropertyWithDefaultValue(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case let .entityWithoutPropertiesTemplate(value):
+ let result = value.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithoutProperties(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithoutProperties(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ }
+ }
+
+ private static func resolveUnknownValue(context: Context, useOnlyLinks: Bool) -> DeserializationResult {
+ guard let type = (context.templateData["type"] as? String).flatMap({ context.templateToType[$0] ?? $0 }) else {
+ return .failure(NonEmptyArray(FieldError(fieldName: "type", level: .error, error: .requiredFieldIsMissing)))
+ }
+
+ switch type {
+ case EntityWithArray.type:
+ let result = EntityWithArrayTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithArray(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithArray(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case EntityWithArrayOfNestedItems.type:
+ let result = EntityWithArrayOfNestedItemsTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithArrayOfNestedItems(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithArrayOfNestedItems(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case EntityWithArrayWithTransform.type:
+ let result = EntityWithArrayWithTransformTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithArrayWithTransform(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithArrayWithTransform(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case EntityWithComplexProperty.type:
+ let result = EntityWithComplexPropertyTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithComplexProperty(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithComplexProperty(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case EntityWithComplexPropertyWithDefaultValue.type:
+ let result = EntityWithComplexPropertyWithDefaultValueTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithComplexPropertyWithDefaultValue(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithComplexPropertyWithDefaultValue(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case EntityWithEntityProperty.type:
+ let result = EntityWithEntityPropertyTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithEntityProperty(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithEntityProperty(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case EntityWithOptionalComplexProperty.type:
+ let result = EntityWithOptionalComplexPropertyTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithOptionalComplexProperty(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithOptionalComplexProperty(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case EntityWithOptionalProperty.type:
+ let result = EntityWithOptionalPropertyTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithOptionalProperty(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithOptionalProperty(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case EntityWithOptionalStringEnumProperty.type:
+ let result = EntityWithOptionalStringEnumPropertyTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithOptionalStringEnumProperty(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithOptionalStringEnumProperty(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case EntityWithPropertyWithDefaultValue.type:
+ let result = EntityWithPropertyWithDefaultValueTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithPropertyWithDefaultValue(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithPropertyWithDefaultValue(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case EntityWithRequiredProperty.type:
+ let result = EntityWithRequiredPropertyTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithRequiredProperty(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithRequiredProperty(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case EntityWithSimpleProperties.type:
+ let result = EntityWithSimplePropertiesTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithSimpleProperties(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithSimpleProperties(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case EntityWithStrictArray.type:
+ let result = EntityWithStrictArrayTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithStrictArray(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithStrictArray(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case EntityWithStringArrayProperty.type:
+ let result = EntityWithStringArrayPropertyTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithStringArrayProperty(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithStringArrayProperty(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case EntityWithStringEnumProperty.type:
+ let result = EntityWithStringEnumPropertyTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithStringEnumProperty(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithStringEnumProperty(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case EntityWithStringEnumPropertyWithDefaultValue.type:
+ let result = EntityWithStringEnumPropertyWithDefaultValueTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithStringEnumPropertyWithDefaultValue(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithStringEnumPropertyWithDefaultValue(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ case EntityWithoutProperties.type:
+ let result = EntityWithoutPropertiesTemplate.resolveValue(context: context, useOnlyLinks: useOnlyLinks)
+ switch result {
+ case let .success(value): return .success(.entityWithoutProperties(value))
+ case let .partialSuccess(value, warnings): return .partialSuccess(.entityWithoutProperties(value), warnings: warnings)
+ case let .failure(errors): return .failure(errors)
+ case .noValue: return .noValue
+ }
+ default:
+ return .failure(NonEmptyArray(FieldError(fieldName: "type", level: .error, error: .requiredFieldIsMissing)))
+ }
+ }
+}
+
+extension EntityTemplate: TemplateDeserializable {
+ public init(dictionary: [String: Any], templateToType: TemplateToType) throws {
+ let receivedType = try dictionary.getField("type") as String
+ let blockType = templateToType[receivedType] ?? receivedType
+ switch blockType {
+ case EntityWithArrayTemplate.type:
+ self = .entityWithArrayTemplate(try EntityWithArrayTemplate(dictionary: dictionary, templateToType: templateToType))
+ case EntityWithArrayOfNestedItemsTemplate.type:
+ self = .entityWithArrayOfNestedItemsTemplate(try EntityWithArrayOfNestedItemsTemplate(dictionary: dictionary, templateToType: templateToType))
+ case EntityWithArrayWithTransformTemplate.type:
+ self = .entityWithArrayWithTransformTemplate(try EntityWithArrayWithTransformTemplate(dictionary: dictionary, templateToType: templateToType))
+ case EntityWithComplexPropertyTemplate.type:
+ self = .entityWithComplexPropertyTemplate(try EntityWithComplexPropertyTemplate(dictionary: dictionary, templateToType: templateToType))
+ case EntityWithComplexPropertyWithDefaultValueTemplate.type:
+ self = .entityWithComplexPropertyWithDefaultValueTemplate(try EntityWithComplexPropertyWithDefaultValueTemplate(dictionary: dictionary, templateToType: templateToType))
+ case EntityWithEntityPropertyTemplate.type:
+ self = .entityWithEntityPropertyTemplate(try EntityWithEntityPropertyTemplate(dictionary: dictionary, templateToType: templateToType))
+ case EntityWithOptionalComplexPropertyTemplate.type:
+ self = .entityWithOptionalComplexPropertyTemplate(try EntityWithOptionalComplexPropertyTemplate(dictionary: dictionary, templateToType: templateToType))
+ case EntityWithOptionalPropertyTemplate.type:
+ self = .entityWithOptionalPropertyTemplate(try EntityWithOptionalPropertyTemplate(dictionary: dictionary, templateToType: templateToType))
+ case EntityWithOptionalStringEnumPropertyTemplate.type:
+ self = .entityWithOptionalStringEnumPropertyTemplate(try EntityWithOptionalStringEnumPropertyTemplate(dictionary: dictionary, templateToType: templateToType))
+ case EntityWithPropertyWithDefaultValueTemplate.type:
+ self = .entityWithPropertyWithDefaultValueTemplate(try EntityWithPropertyWithDefaultValueTemplate(dictionary: dictionary, templateToType: templateToType))
+ case EntityWithRequiredPropertyTemplate.type:
+ self = .entityWithRequiredPropertyTemplate(try EntityWithRequiredPropertyTemplate(dictionary: dictionary, templateToType: templateToType))
+ case EntityWithSimplePropertiesTemplate.type:
+ self = .entityWithSimplePropertiesTemplate(try EntityWithSimplePropertiesTemplate(dictionary: dictionary, templateToType: templateToType))
+ case EntityWithStrictArrayTemplate.type:
+ self = .entityWithStrictArrayTemplate(try EntityWithStrictArrayTemplate(dictionary: dictionary, templateToType: templateToType))
+ case EntityWithStringArrayPropertyTemplate.type:
+ self = .entityWithStringArrayPropertyTemplate(try EntityWithStringArrayPropertyTemplate(dictionary: dictionary, templateToType: templateToType))
+ case EntityWithStringEnumPropertyTemplate.type:
+ self = .entityWithStringEnumPropertyTemplate(try EntityWithStringEnumPropertyTemplate(dictionary: dictionary, templateToType: templateToType))
+ case EntityWithStringEnumPropertyWithDefaultValueTemplate.type:
+ self = .entityWithStringEnumPropertyWithDefaultValueTemplate(try EntityWithStringEnumPropertyWithDefaultValueTemplate(dictionary: dictionary, templateToType: templateToType))
+ case EntityWithoutPropertiesTemplate.type:
+ self = .entityWithoutPropertiesTemplate(try EntityWithoutPropertiesTemplate(dictionary: dictionary, templateToType: templateToType))
+ default:
+ throw DeserializationError.invalidFieldRepresentation(field: "entity_template", representation: dictionary)
+ }
+ }
+}
diff --git a/api_generator/tests/references/swift/EntityWithArray.swift b/api_generator/tests/references/swift/EntityWithArray.swift
new file mode 100644
index 000000000..6a67f2802
--- /dev/null
+++ b/api_generator/tests/references/swift/EntityWithArray.swift
@@ -0,0 +1,41 @@
+// Generated code. Do not modify.
+
+import CommonCore
+import Foundation
+import Serialization
+
+public final class EntityWithArray {
+ public static let type: String = "entity_with_array"
+ public let array: [Entity] // at least 1 elements
+
+ static let arrayValidator: AnyArrayValueValidator =
+ makeArrayValidator(minItems: 1)
+
+ init(
+ array: [Entity]
+ ) {
+ self.array = array
+ }
+}
+
+#if DEBUG
+extension EntityWithArray: Equatable {
+ public static func ==(lhs: EntityWithArray, rhs: EntityWithArray) -> Bool {
+ guard
+ lhs.array == rhs.array
+ else {
+ return false
+ }
+ return true
+ }
+}
+#endif
+
+extension EntityWithArray: Serializable {
+ public func toDictionary() -> [String: ValidSerializationValue] {
+ var result: [String: ValidSerializationValue] = [:]
+ result["type"] = Self.type
+ result["array"] = array.map { $0.toDictionary() }
+ return result
+ }
+}
diff --git a/api_generator/tests/references/swift/EntityWithArrayOfNestedItems.swift b/api_generator/tests/references/swift/EntityWithArrayOfNestedItems.swift
new file mode 100644
index 000000000..edcf6ac3c
--- /dev/null
+++ b/api_generator/tests/references/swift/EntityWithArrayOfNestedItems.swift
@@ -0,0 +1,84 @@
+// Generated code. Do not modify.
+
+import CommonCore
+import Foundation
+import Serialization
+
+public final class EntityWithArrayOfNestedItems {
+ public final class Item {
+ public let entity: Entity
+ public let property: Expression // at least 1 char
+
+ public func resolveProperty(_ resolver: ExpressionResolver) -> String? {
+ resolver.resolveStringBasedValue(expression: property, initializer: { $0 })
+ }
+
+ static let propertyValidator: AnyValueValidator =
+ makeStringValidator(minLength: 1)
+
+ init(
+ entity: Entity,
+ property: Expression
+ ) {
+ self.entity = entity
+ self.property = property
+ }
+ }
+
+ public static let type: String = "entity_with_array_of_nested_items"
+ public let items: [Item] // at least 1 elements
+
+ static let itemsValidator: AnyArrayValueValidator =
+ makeArrayValidator(minItems: 1)
+
+ init(
+ items: [Item]
+ ) {
+ self.items = items
+ }
+}
+
+#if DEBUG
+extension EntityWithArrayOfNestedItems: Equatable {
+ public static func ==(lhs: EntityWithArrayOfNestedItems, rhs: EntityWithArrayOfNestedItems) -> Bool {
+ guard
+ lhs.items == rhs.items
+ else {
+ return false
+ }
+ return true
+ }
+}
+#endif
+
+extension EntityWithArrayOfNestedItems: Serializable {
+ public func toDictionary() -> [String: ValidSerializationValue] {
+ var result: [String: ValidSerializationValue] = [:]
+ result["type"] = Self.type
+ result["items"] = items.map { $0.toDictionary() }
+ return result
+ }
+}
+
+#if DEBUG
+extension EntityWithArrayOfNestedItems.Item: Equatable {
+ public static func ==(lhs: EntityWithArrayOfNestedItems.Item, rhs: EntityWithArrayOfNestedItems.Item) -> Bool {
+ guard
+ lhs.entity == rhs.entity,
+ lhs.property == rhs.property
+ else {
+ return false
+ }
+ return true
+ }
+}
+#endif
+
+extension EntityWithArrayOfNestedItems.Item: Serializable {
+ public func toDictionary() -> [String: ValidSerializationValue] {
+ var result: [String: ValidSerializationValue] = [:]
+ result["entity"] = entity.toDictionary()
+ result["property"] = property.toValidSerializationValue()
+ return result
+ }
+}
diff --git a/api_generator/tests/references/swift/EntityWithArrayOfNestedItemsTemplate.swift b/api_generator/tests/references/swift/EntityWithArrayOfNestedItemsTemplate.swift
new file mode 100644
index 000000000..1d691ac8b
--- /dev/null
+++ b/api_generator/tests/references/swift/EntityWithArrayOfNestedItemsTemplate.swift
@@ -0,0 +1,216 @@
+// Generated code. Do not modify.
+
+import CommonCore
+import Foundation
+import Serialization
+
+public final class EntityWithArrayOfNestedItemsTemplate: TemplateValue, TemplateDeserializable {
+ public final class ItemTemplate: TemplateValue, TemplateDeserializable {
+ public let entity: Field?
+ public let property: Field>? // at least 1 char
+
+ public convenience init(dictionary: [String: Any], templateToType: TemplateToType) throws {
+ do {
+ self.init(
+ entity: try dictionary.getOptionalField("entity", templateToType: templateToType),
+ property: try dictionary.getOptionalExpressionField("property")
+ )
+ } catch let DeserializationError.invalidFieldRepresentation(field: field, representation: representation) {
+ throw DeserializationError.invalidFieldRepresentation(field: "item_template." + field, representation: representation)
+ }
+ }
+
+ init(
+ entity: Field? = nil,
+ property: Field>? = nil
+ ) {
+ self.entity = entity
+ self.property = property
+ }
+
+ private static func resolveOnlyLinks(context: Context, parent: ItemTemplate?) -> DeserializationResult {
+ let entityValue = parent?.entity?.resolveValue(context: context, useOnlyLinks: true) ?? .noValue
+ let propertyValue = parent?.property?.resolveValue(context: context, validator: ResolvedValue.propertyValidator) ?? .noValue
+ var errors = mergeErrors(
+ entityValue.errorsOrWarnings?.map { .right($0.asError(deserializing: "entity", level: .error)) },
+ propertyValue.errorsOrWarnings?.map { .right($0.asError(deserializing: "property", level: .error)) }
+ )
+ if case .noValue = entityValue {
+ errors.append(.right(FieldError(fieldName: "entity", level: .error, error: .requiredFieldIsMissing)))
+ }
+ if case .noValue = propertyValue {
+ errors.append(.right(FieldError(fieldName: "property", level: .error, error: .requiredFieldIsMissing)))
+ }
+ guard
+ let entityNonNil = entityValue.value,
+ let propertyNonNil = propertyValue.value
+ else {
+ return .failure(NonEmptyArray(errors)!)
+ }
+ let result = EntityWithArrayOfNestedItems.Item(
+ entity: entityNonNil,
+ property: propertyNonNil
+ )
+ return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
+ }
+
+ public static func resolveValue(context: Context, parent: ItemTemplate?, useOnlyLinks: Bool) -> DeserializationResult {
+ if useOnlyLinks {
+ return resolveOnlyLinks(context: context, parent: parent)
+ }
+ var entityValue: DeserializationResult = .noValue
+ var propertyValue: DeserializationResult> = parent?.property?.value() ?? .noValue
+ context.templateData.forEach { key, __dictValue in
+ switch key {
+ case "entity":
+ entityValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, type: EntityTemplate.self).merged(with: entityValue)
+ case "property":
+ propertyValue = deserialize(__dictValue, validator: ResolvedValue.propertyValidator).merged(with: propertyValue)
+ case parent?.entity?.link:
+ entityValue = entityValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, type: EntityTemplate.self))
+ case parent?.property?.link:
+ propertyValue = propertyValue.merged(with: deserialize(__dictValue, validator: ResolvedValue.propertyValidator))
+ default: break
+ }
+ }
+ if let parent = parent {
+ entityValue = entityValue.merged(with: parent.entity?.resolveValue(context: context, useOnlyLinks: true))
+ }
+ var errors = mergeErrors(
+ entityValue.errorsOrWarnings?.map { Either.right($0.asError(deserializing: "entity", level: .error)) },
+ propertyValue.errorsOrWarnings?.map { Either.right($0.asError(deserializing: "property", level: .error)) }
+ )
+ if case .noValue = entityValue {
+ errors.append(.right(FieldError(fieldName: "entity", level: .error, error: .requiredFieldIsMissing)))
+ }
+ if case .noValue = propertyValue {
+ errors.append(.right(FieldError(fieldName: "property", level: .error, error: .requiredFieldIsMissing)))
+ }
+ guard
+ let entityNonNil = entityValue.value,
+ let propertyNonNil = propertyValue.value
+ else {
+ return .failure(NonEmptyArray(errors)!)
+ }
+ let result = EntityWithArrayOfNestedItems.Item(
+ entity: entityNonNil,
+ property: propertyNonNil
+ )
+ return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
+ }
+
+ private func mergedWithParent(templates: Templates) throws -> ItemTemplate {
+ return self
+ }
+
+ public func resolveParent(templates: Templates) throws -> ItemTemplate {
+ let merged = try mergedWithParent(templates: templates)
+
+ return ItemTemplate(
+ entity: try merged.entity?.resolveParent(templates: templates),
+ property: merged.property
+ )
+ }
+ }
+
+ public static let type: String = "entity_with_array_of_nested_items"
+ public let parent: String? // at least 1 char
+ public let items: Field<[ItemTemplate]>? // at least 1 elements
+
+ static let parentValidator: AnyValueValidator =
+ makeStringValidator(minLength: 1)
+
+ public convenience init(dictionary: [String: Any], templateToType: TemplateToType) throws {
+ do {
+ self.init(
+ parent: try dictionary.getOptionalField("type", validator: Self.parentValidator),
+ items: try dictionary.getOptionalArray("items", templateToType: templateToType)
+ )
+ } catch let DeserializationError.invalidFieldRepresentation(field: field, representation: representation) {
+ throw DeserializationError.invalidFieldRepresentation(field: "entity_with_array_of_nested_items_template." + field, representation: representation)
+ }
+ }
+
+ init(
+ parent: String?,
+ items: Field<[ItemTemplate]>? = nil
+ ) {
+ self.parent = parent
+ self.items = items
+ }
+
+ private static func resolveOnlyLinks(context: Context, parent: EntityWithArrayOfNestedItemsTemplate?) -> DeserializationResult {
+ let itemsValue = parent?.items?.resolveValue(context: context, validator: ResolvedValue.itemsValidator, useOnlyLinks: true) ?? .noValue
+ var errors = mergeErrors(
+ itemsValue.errorsOrWarnings?.map { .right($0.asError(deserializing: "items", level: .error)) }
+ )
+ if case .noValue = itemsValue {
+ errors.append(.right(FieldError(fieldName: "items", level: .error, error: .requiredFieldIsMissing)))
+ }
+ guard
+ let itemsNonNil = itemsValue.value
+ else {
+ return .failure(NonEmptyArray(errors)!)
+ }
+ let result = EntityWithArrayOfNestedItems(
+ items: itemsNonNil
+ )
+ return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
+ }
+
+ public static func resolveValue(context: Context, parent: EntityWithArrayOfNestedItemsTemplate?, useOnlyLinks: Bool) -> DeserializationResult {
+ if useOnlyLinks {
+ return resolveOnlyLinks(context: context, parent: parent)
+ }
+ var itemsValue: DeserializationResult<[EntityWithArrayOfNestedItems.Item]> = .noValue
+ context.templateData.forEach { key, __dictValue in
+ switch key {
+ case "items":
+ itemsValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.itemsValidator, type: EntityWithArrayOfNestedItemsTemplate.ItemTemplate.self).merged(with: itemsValue)
+ case parent?.items?.link:
+ itemsValue = itemsValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.itemsValidator, type: EntityWithArrayOfNestedItemsTemplate.ItemTemplate.self))
+ default: break
+ }
+ }
+ if let parent = parent {
+ itemsValue = itemsValue.merged(with: parent.items?.resolveValue(context: context, validator: ResolvedValue.itemsValidator, useOnlyLinks: true))
+ }
+ var errors = mergeErrors(
+ itemsValue.errorsOrWarnings?.map { Either.right($0.asError(deserializing: "items", level: .error)) }
+ )
+ if case .noValue = itemsValue {
+ errors.append(.right(FieldError(fieldName: "items", level: .error, error: .requiredFieldIsMissing)))
+ }
+ guard
+ let itemsNonNil = itemsValue.value
+ else {
+ return .failure(NonEmptyArray(errors)!)
+ }
+ let result = EntityWithArrayOfNestedItems(
+ items: itemsNonNil
+ )
+ return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
+ }
+
+ private func mergedWithParent(templates: Templates) throws -> EntityWithArrayOfNestedItemsTemplate {
+ guard let parent = parent, parent != Self.type else { return self }
+ guard let parentTemplate = templates[parent] as? EntityWithArrayOfNestedItemsTemplate else {
+ throw DeserializationError.unknownType(type: parent)
+ }
+ let mergedParent = try parentTemplate.mergedWithParent(templates: templates)
+
+ return EntityWithArrayOfNestedItemsTemplate(
+ parent: nil,
+ items: items ?? mergedParent.items
+ )
+ }
+
+ public func resolveParent(templates: Templates) throws -> EntityWithArrayOfNestedItemsTemplate {
+ let merged = try mergedWithParent(templates: templates)
+
+ return EntityWithArrayOfNestedItemsTemplate(
+ parent: nil,
+ items: try merged.items?.resolveParent(templates: templates)
+ )
+ }
+}
diff --git a/api_generator/tests/references/swift/EntityWithArrayTemplate.swift b/api_generator/tests/references/swift/EntityWithArrayTemplate.swift
new file mode 100644
index 000000000..acdc42bda
--- /dev/null
+++ b/api_generator/tests/references/swift/EntityWithArrayTemplate.swift
@@ -0,0 +1,108 @@
+// Generated code. Do not modify.
+
+import CommonCore
+import Foundation
+import Serialization
+
+public final class EntityWithArrayTemplate: TemplateValue, TemplateDeserializable {
+ public static let type: String = "entity_with_array"
+ public let parent: String? // at least 1 char
+ public let array: Field<[EntityTemplate]>? // at least 1 elements
+
+ static let parentValidator: AnyValueValidator =
+ makeStringValidator(minLength: 1)
+
+ public convenience init(dictionary: [String: Any], templateToType: TemplateToType) throws {
+ do {
+ self.init(
+ parent: try dictionary.getOptionalField("type", validator: Self.parentValidator),
+ array: try dictionary.getOptionalArray("array", templateToType: templateToType)
+ )
+ } catch let DeserializationError.invalidFieldRepresentation(field: field, representation: representation) {
+ throw DeserializationError.invalidFieldRepresentation(field: "entity_with_array_template." + field, representation: representation)
+ }
+ }
+
+ init(
+ parent: String?,
+ array: Field<[EntityTemplate]>? = nil
+ ) {
+ self.parent = parent
+ self.array = array
+ }
+
+ private static func resolveOnlyLinks(context: Context, parent: EntityWithArrayTemplate?) -> DeserializationResult {
+ let arrayValue = parent?.array?.resolveValue(context: context, validator: ResolvedValue.arrayValidator, useOnlyLinks: true) ?? .noValue
+ var errors = mergeErrors(
+ arrayValue.errorsOrWarnings?.map { .right($0.asError(deserializing: "array", level: .error)) }
+ )
+ if case .noValue = arrayValue {
+ errors.append(.right(FieldError(fieldName: "array", level: .error, error: .requiredFieldIsMissing)))
+ }
+ guard
+ let arrayNonNil = arrayValue.value
+ else {
+ return .failure(NonEmptyArray(errors)!)
+ }
+ let result = EntityWithArray(
+ array: arrayNonNil
+ )
+ return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
+ }
+
+ public static func resolveValue(context: Context, parent: EntityWithArrayTemplate?, useOnlyLinks: Bool) -> DeserializationResult {
+ if useOnlyLinks {
+ return resolveOnlyLinks(context: context, parent: parent)
+ }
+ var arrayValue: DeserializationResult<[Entity]> = .noValue
+ context.templateData.forEach { key, __dictValue in
+ switch key {
+ case "array":
+ arrayValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.arrayValidator, type: EntityTemplate.self).merged(with: arrayValue)
+ case parent?.array?.link:
+ arrayValue = arrayValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.arrayValidator, type: EntityTemplate.self))
+ default: break
+ }
+ }
+ if let parent = parent {
+ arrayValue = arrayValue.merged(with: parent.array?.resolveValue(context: context, validator: ResolvedValue.arrayValidator, useOnlyLinks: true))
+ }
+ var errors = mergeErrors(
+ arrayValue.errorsOrWarnings?.map { Either.right($0.asError(deserializing: "array", level: .error)) }
+ )
+ if case .noValue = arrayValue {
+ errors.append(.right(FieldError(fieldName: "array", level: .error, error: .requiredFieldIsMissing)))
+ }
+ guard
+ let arrayNonNil = arrayValue.value
+ else {
+ return .failure(NonEmptyArray(errors)!)
+ }
+ let result = EntityWithArray(
+ array: arrayNonNil
+ )
+ return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
+ }
+
+ private func mergedWithParent(templates: Templates) throws -> EntityWithArrayTemplate {
+ guard let parent = parent, parent != Self.type else { return self }
+ guard let parentTemplate = templates[parent] as? EntityWithArrayTemplate else {
+ throw DeserializationError.unknownType(type: parent)
+ }
+ let mergedParent = try parentTemplate.mergedWithParent(templates: templates)
+
+ return EntityWithArrayTemplate(
+ parent: nil,
+ array: array ?? mergedParent.array
+ )
+ }
+
+ public func resolveParent(templates: Templates) throws -> EntityWithArrayTemplate {
+ let merged = try mergedWithParent(templates: templates)
+
+ return EntityWithArrayTemplate(
+ parent: nil,
+ array: try merged.array?.resolveParent(templates: templates)
+ )
+ }
+}
diff --git a/api_generator/tests/references/swift/EntityWithArrayWithTransform.swift b/api_generator/tests/references/swift/EntityWithArrayWithTransform.swift
new file mode 100644
index 000000000..bd53c4228
--- /dev/null
+++ b/api_generator/tests/references/swift/EntityWithArrayWithTransform.swift
@@ -0,0 +1,45 @@
+// Generated code. Do not modify.
+
+import CommonCore
+import Foundation
+import Serialization
+
+public final class EntityWithArrayWithTransform {
+ public static let type: String = "entity_with_array_with_transform"
+ public let array: [Expression] // at least 1 elements
+
+ public func resolveArray(_ resolver: ExpressionResolver) -> [Color]? {
+ array.map { resolver.resolveStringBasedValue(expression: $0, initializer: Color.color(withHexString:)) }.compactMap { $0 }
+ }
+
+ static let arrayValidator: AnyArrayValueValidator> =
+ makeArrayValidator(minItems: 1)
+
+ init(
+ array: [Expression]
+ ) {
+ self.array = array
+ }
+}
+
+#if DEBUG
+extension EntityWithArrayWithTransform: Equatable {
+ public static func ==(lhs: EntityWithArrayWithTransform, rhs: EntityWithArrayWithTransform) -> Bool {
+ guard
+ lhs.array == rhs.array
+ else {
+ return false
+ }
+ return true
+ }
+}
+#endif
+
+extension EntityWithArrayWithTransform: Serializable {
+ public func toDictionary() -> [String: ValidSerializationValue] {
+ var result: [String: ValidSerializationValue] = [:]
+ result["type"] = Self.type
+ result["array"] = array.map { $0.toValidSerializationValue() }
+ return result
+ }
+}
diff --git a/api_generator/tests/references/swift/EntityWithArrayWithTransformTemplate.swift b/api_generator/tests/references/swift/EntityWithArrayWithTransformTemplate.swift
new file mode 100644
index 000000000..dc2416c5d
--- /dev/null
+++ b/api_generator/tests/references/swift/EntityWithArrayWithTransformTemplate.swift
@@ -0,0 +1,100 @@
+// Generated code. Do not modify.
+
+import CommonCore
+import Foundation
+import Serialization
+
+public final class EntityWithArrayWithTransformTemplate: TemplateValue, TemplateDeserializable {
+ public static let type: String = "entity_with_array_with_transform"
+ public let parent: String? // at least 1 char
+ public let array: Field<[Expression]>? // at least 1 elements
+
+ static let parentValidator: AnyValueValidator =
+ makeStringValidator(minLength: 1)
+
+ public convenience init(dictionary: [String: Any], templateToType: TemplateToType) throws {
+ do {
+ self.init(
+ parent: try dictionary.getOptionalField("type", validator: Self.parentValidator),
+ array: try dictionary.getOptionalExpressionArray("array", transform: Color.color(withHexString:))
+ )
+ } catch let DeserializationError.invalidFieldRepresentation(field: field, representation: representation) {
+ throw DeserializationError.invalidFieldRepresentation(field: "entity_with_array_with_transform_template." + field, representation: representation)
+ }
+ }
+
+ init(
+ parent: String?,
+ array: Field<[Expression]>? = nil
+ ) {
+ self.parent = parent
+ self.array = array
+ }
+
+ private static func resolveOnlyLinks(context: Context, parent: EntityWithArrayWithTransformTemplate?) -> DeserializationResult {
+ let arrayValue = parent?.array?.resolveValue(context: context, transform: Color.color(withHexString:), validator: ResolvedValue.arrayValidator) ?? .noValue
+ var errors = mergeErrors(
+ arrayValue.errorsOrWarnings?.map { .right($0.asError(deserializing: "array", level: .error)) }
+ )
+ if case .noValue = arrayValue {
+ errors.append(.right(FieldError(fieldName: "array", level: .error, error: .requiredFieldIsMissing)))
+ }
+ guard
+ let arrayNonNil = arrayValue.value
+ else {
+ return .failure(NonEmptyArray(errors)!)
+ }
+ let result = EntityWithArrayWithTransform(
+ array: arrayNonNil
+ )
+ return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
+ }
+
+ public static func resolveValue(context: Context, parent: EntityWithArrayWithTransformTemplate?, useOnlyLinks: Bool) -> DeserializationResult {
+ if useOnlyLinks {
+ return resolveOnlyLinks(context: context, parent: parent)
+ }
+ var arrayValue: DeserializationResult<[Expression]> = parent?.array?.value() ?? .noValue
+ context.templateData.forEach { key, __dictValue in
+ switch key {
+ case "array":
+ arrayValue = deserialize(__dictValue, transform: Color.color(withHexString:), validator: ResolvedValue.arrayValidator).merged(with: arrayValue)
+ case parent?.array?.link:
+ arrayValue = arrayValue.merged(with: deserialize(__dictValue, transform: Color.color(withHexString:), validator: ResolvedValue.arrayValidator))
+ default: break
+ }
+ }
+ var errors = mergeErrors(
+ arrayValue.errorsOrWarnings?.map { Either.right($0.asError(deserializing: "array", level: .error)) }
+ )
+ if case .noValue = arrayValue {
+ errors.append(.right(FieldError(fieldName: "array", level: .error, error: .requiredFieldIsMissing)))
+ }
+ guard
+ let arrayNonNil = arrayValue.value
+ else {
+ return .failure(NonEmptyArray(errors)!)
+ }
+ let result = EntityWithArrayWithTransform(
+ array: arrayNonNil
+ )
+ return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
+ }
+
+ private func mergedWithParent(templates: Templates) throws -> EntityWithArrayWithTransformTemplate {
+ guard let parent = parent, parent != Self.type else { return self }
+ guard let parentTemplate = templates[parent] as? EntityWithArrayWithTransformTemplate else {
+ throw DeserializationError.unknownType(type: parent)
+ }
+ let mergedParent = try parentTemplate.mergedWithParent(templates: templates)
+
+ return EntityWithArrayWithTransformTemplate(
+ parent: nil,
+ array: array ?? mergedParent.array
+ )
+ }
+
+ public func resolveParent(templates: Templates) throws -> EntityWithArrayWithTransformTemplate {
+ return try mergedWithParent(templates: templates)
+ }
+}
diff --git a/api_generator/tests/references/swift/EntityWithComplexProperty.swift b/api_generator/tests/references/swift/EntityWithComplexProperty.swift
new file mode 100644
index 000000000..5642cc57f
--- /dev/null
+++ b/api_generator/tests/references/swift/EntityWithComplexProperty.swift
@@ -0,0 +1,73 @@
+// Generated code. Do not modify.
+
+import CommonCore
+import Foundation
+import Serialization
+
+public final class EntityWithComplexProperty {
+ public final class Property {
+ public let value: Expression
+
+ public func resolveValue(_ resolver: ExpressionResolver) -> URL? {
+ resolver.resolveStringBasedValue(expression: value, initializer: URL.init(string:))
+ }
+
+ init(
+ value: Expression
+ ) {
+ self.value = value
+ }
+ }
+
+ public static let type: String = "entity_with_complex_property"
+ public let property: Property
+
+ init(
+ property: Property
+ ) {
+ self.property = property
+ }
+}
+
+#if DEBUG
+extension EntityWithComplexProperty: Equatable {
+ public static func ==(lhs: EntityWithComplexProperty, rhs: EntityWithComplexProperty) -> Bool {
+ guard
+ lhs.property == rhs.property
+ else {
+ return false
+ }
+ return true
+ }
+}
+#endif
+
+extension EntityWithComplexProperty: Serializable {
+ public func toDictionary() -> [String: ValidSerializationValue] {
+ var result: [String: ValidSerializationValue] = [:]
+ result["type"] = Self.type
+ result["property"] = property.toDictionary()
+ return result
+ }
+}
+
+#if DEBUG
+extension EntityWithComplexProperty.Property: Equatable {
+ public static func ==(lhs: EntityWithComplexProperty.Property, rhs: EntityWithComplexProperty.Property) -> Bool {
+ guard
+ lhs.value == rhs.value
+ else {
+ return false
+ }
+ return true
+ }
+}
+#endif
+
+extension EntityWithComplexProperty.Property: Serializable {
+ public func toDictionary() -> [String: ValidSerializationValue] {
+ var result: [String: ValidSerializationValue] = [:]
+ result["value"] = value.toValidSerializationValue()
+ return result
+ }
+}
diff --git a/api_generator/tests/references/swift/EntityWithComplexPropertyTemplate.swift b/api_generator/tests/references/swift/EntityWithComplexPropertyTemplate.swift
new file mode 100644
index 000000000..c185504bc
--- /dev/null
+++ b/api_generator/tests/references/swift/EntityWithComplexPropertyTemplate.swift
@@ -0,0 +1,186 @@
+// Generated code. Do not modify.
+
+import CommonCore
+import Foundation
+import Serialization
+
+public final class EntityWithComplexPropertyTemplate: TemplateValue, TemplateDeserializable {
+ public final class PropertyTemplate: TemplateValue, TemplateDeserializable {
+ public let value: Field>?
+
+ public convenience init(dictionary: [String: Any], templateToType: TemplateToType) throws {
+ do {
+ self.init(
+ value: try dictionary.getOptionalExpressionField("value", transform: URL.init(string:))
+ )
+ } catch let DeserializationError.invalidFieldRepresentation(field: field, representation: representation) {
+ throw DeserializationError.invalidFieldRepresentation(field: "property_template." + field, representation: representation)
+ }
+ }
+
+ init(
+ value: Field>? = nil
+ ) {
+ self.value = value
+ }
+
+ private static func resolveOnlyLinks(context: Context, parent: PropertyTemplate?) -> DeserializationResult {
+ let valueValue = parent?.value?.resolveValue(context: context, transform: URL.init(string:)) ?? .noValue
+ var errors = mergeErrors(
+ valueValue.errorsOrWarnings?.map { .right($0.asError(deserializing: "value", level: .error)) }
+ )
+ if case .noValue = valueValue {
+ errors.append(.right(FieldError(fieldName: "value", level: .error, error: .requiredFieldIsMissing)))
+ }
+ guard
+ let valueNonNil = valueValue.value
+ else {
+ return .failure(NonEmptyArray(errors)!)
+ }
+ let result = EntityWithComplexProperty.Property(
+ value: valueNonNil
+ )
+ return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
+ }
+
+ public static func resolveValue(context: Context, parent: PropertyTemplate?, useOnlyLinks: Bool) -> DeserializationResult {
+ if useOnlyLinks {
+ return resolveOnlyLinks(context: context, parent: parent)
+ }
+ var valueValue: DeserializationResult> = parent?.value?.value() ?? .noValue
+ context.templateData.forEach { key, __dictValue in
+ switch key {
+ case "value":
+ valueValue = deserialize(__dictValue, transform: URL.init(string:)).merged(with: valueValue)
+ case parent?.value?.link:
+ valueValue = valueValue.merged(with: deserialize(__dictValue, transform: URL.init(string:)))
+ default: break
+ }
+ }
+ var errors = mergeErrors(
+ valueValue.errorsOrWarnings?.map { Either.right($0.asError(deserializing: "value", level: .error)) }
+ )
+ if case .noValue = valueValue {
+ errors.append(.right(FieldError(fieldName: "value", level: .error, error: .requiredFieldIsMissing)))
+ }
+ guard
+ let valueNonNil = valueValue.value
+ else {
+ return .failure(NonEmptyArray(errors)!)
+ }
+ let result = EntityWithComplexProperty.Property(
+ value: valueNonNil
+ )
+ return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
+ }
+
+ private func mergedWithParent(templates: Templates) throws -> PropertyTemplate {
+ return self
+ }
+
+ public func resolveParent(templates: Templates) throws -> PropertyTemplate {
+ return try mergedWithParent(templates: templates)
+ }
+ }
+
+ public static let type: String = "entity_with_complex_property"
+ public let parent: String? // at least 1 char
+ public let property: Field?
+
+ static let parentValidator: AnyValueValidator =
+ makeStringValidator(minLength: 1)
+
+ public convenience init(dictionary: [String: Any], templateToType: TemplateToType) throws {
+ do {
+ self.init(
+ parent: try dictionary.getOptionalField("type", validator: Self.parentValidator),
+ property: try dictionary.getOptionalField("property", templateToType: templateToType)
+ )
+ } catch let DeserializationError.invalidFieldRepresentation(field: field, representation: representation) {
+ throw DeserializationError.invalidFieldRepresentation(field: "entity_with_complex_property_template." + field, representation: representation)
+ }
+ }
+
+ init(
+ parent: String?,
+ property: Field? = nil
+ ) {
+ self.parent = parent
+ self.property = property
+ }
+
+ private static func resolveOnlyLinks(context: Context, parent: EntityWithComplexPropertyTemplate?) -> DeserializationResult {
+ let propertyValue = parent?.property?.resolveValue(context: context, useOnlyLinks: true) ?? .noValue
+ var errors = mergeErrors(
+ propertyValue.errorsOrWarnings?.map { .right($0.asError(deserializing: "property", level: .error)) }
+ )
+ if case .noValue = propertyValue {
+ errors.append(.right(FieldError(fieldName: "property", level: .error, error: .requiredFieldIsMissing)))
+ }
+ guard
+ let propertyNonNil = propertyValue.value
+ else {
+ return .failure(NonEmptyArray(errors)!)
+ }
+ let result = EntityWithComplexProperty(
+ property: propertyNonNil
+ )
+ return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
+ }
+
+ public static func resolveValue(context: Context, parent: EntityWithComplexPropertyTemplate?, useOnlyLinks: Bool) -> DeserializationResult {
+ if useOnlyLinks {
+ return resolveOnlyLinks(context: context, parent: parent)
+ }
+ var propertyValue: DeserializationResult = .noValue
+ context.templateData.forEach { key, __dictValue in
+ switch key {
+ case "property":
+ propertyValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, type: EntityWithComplexPropertyTemplate.PropertyTemplate.self).merged(with: propertyValue)
+ case parent?.property?.link:
+ propertyValue = propertyValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, type: EntityWithComplexPropertyTemplate.PropertyTemplate.self))
+ default: break
+ }
+ }
+ if let parent = parent {
+ propertyValue = propertyValue.merged(with: parent.property?.resolveValue(context: context, useOnlyLinks: true))
+ }
+ var errors = mergeErrors(
+ propertyValue.errorsOrWarnings?.map { Either.right($0.asError(deserializing: "property", level: .error)) }
+ )
+ if case .noValue = propertyValue {
+ errors.append(.right(FieldError(fieldName: "property", level: .error, error: .requiredFieldIsMissing)))
+ }
+ guard
+ let propertyNonNil = propertyValue.value
+ else {
+ return .failure(NonEmptyArray(errors)!)
+ }
+ let result = EntityWithComplexProperty(
+ property: propertyNonNil
+ )
+ return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
+ }
+
+ private func mergedWithParent(templates: Templates) throws -> EntityWithComplexPropertyTemplate {
+ guard let parent = parent, parent != Self.type else { return self }
+ guard let parentTemplate = templates[parent] as? EntityWithComplexPropertyTemplate else {
+ throw DeserializationError.unknownType(type: parent)
+ }
+ let mergedParent = try parentTemplate.mergedWithParent(templates: templates)
+
+ return EntityWithComplexPropertyTemplate(
+ parent: nil,
+ property: property ?? mergedParent.property
+ )
+ }
+
+ public func resolveParent(templates: Templates) throws -> EntityWithComplexPropertyTemplate {
+ let merged = try mergedWithParent(templates: templates)
+
+ return EntityWithComplexPropertyTemplate(
+ parent: nil,
+ property: try merged.property?.resolveParent(templates: templates)
+ )
+ }
+}
diff --git a/api_generator/tests/references/swift/EntityWithComplexPropertyWithDefaultValue.swift b/api_generator/tests/references/swift/EntityWithComplexPropertyWithDefaultValue.swift
new file mode 100644
index 000000000..c6186b1b2
--- /dev/null
+++ b/api_generator/tests/references/swift/EntityWithComplexPropertyWithDefaultValue.swift
@@ -0,0 +1,76 @@
+// Generated code. Do not modify.
+
+import CommonCore
+import Foundation
+import Serialization
+
+public final class EntityWithComplexPropertyWithDefaultValue {
+ public final class Property {
+ public let value: Expression
+
+ public func resolveValue(_ resolver: ExpressionResolver) -> String? {
+ resolver.resolveStringBasedValue(expression: value, initializer: { $0 })
+ }
+
+ init(
+ value: Expression
+ ) {
+ self.value = value
+ }
+ }
+
+ public static let type: String = "entity_with_complex_property_with_default_value"
+ public let property: Property // default value: EntityWithComplexPropertyWithDefaultValue.Property(value: .value("Default text"))
+
+ static let propertyValidator: AnyValueValidator =
+ makeNoOpValueValidator()
+
+ init(
+ property: Property? = nil
+ ) {
+ self.property = property ?? EntityWithComplexPropertyWithDefaultValue.Property(value: .value("Default text"))
+ }
+}
+
+#if DEBUG
+extension EntityWithComplexPropertyWithDefaultValue: Equatable {
+ public static func ==(lhs: EntityWithComplexPropertyWithDefaultValue, rhs: EntityWithComplexPropertyWithDefaultValue) -> Bool {
+ guard
+ lhs.property == rhs.property
+ else {
+ return false
+ }
+ return true
+ }
+}
+#endif
+
+extension EntityWithComplexPropertyWithDefaultValue: Serializable {
+ public func toDictionary() -> [String: ValidSerializationValue] {
+ var result: [String: ValidSerializationValue] = [:]
+ result["type"] = Self.type
+ result["property"] = property.toDictionary()
+ return result
+ }
+}
+
+#if DEBUG
+extension EntityWithComplexPropertyWithDefaultValue.Property: Equatable {
+ public static func ==(lhs: EntityWithComplexPropertyWithDefaultValue.Property, rhs: EntityWithComplexPropertyWithDefaultValue.Property) -> Bool {
+ guard
+ lhs.value == rhs.value
+ else {
+ return false
+ }
+ return true
+ }
+}
+#endif
+
+extension EntityWithComplexPropertyWithDefaultValue.Property: Serializable {
+ public func toDictionary() -> [String: ValidSerializationValue] {
+ var result: [String: ValidSerializationValue] = [:]
+ result["value"] = value.toValidSerializationValue()
+ return result
+ }
+}
diff --git a/api_generator/tests/references/swift/EntityWithComplexPropertyWithDefaultValueTemplate.swift b/api_generator/tests/references/swift/EntityWithComplexPropertyWithDefaultValueTemplate.swift
new file mode 100644
index 000000000..f78671a64
--- /dev/null
+++ b/api_generator/tests/references/swift/EntityWithComplexPropertyWithDefaultValueTemplate.swift
@@ -0,0 +1,166 @@
+// Generated code. Do not modify.
+
+import CommonCore
+import Foundation
+import Serialization
+
+public final class EntityWithComplexPropertyWithDefaultValueTemplate: TemplateValue, TemplateDeserializable {
+ public final class PropertyTemplate: TemplateValue, TemplateDeserializable {
+ public let value: Field>?
+
+ public convenience init(dictionary: [String: Any], templateToType: TemplateToType) throws {
+ do {
+ self.init(
+ value: try dictionary.getOptionalExpressionField("value")
+ )
+ } catch let DeserializationError.invalidFieldRepresentation(field: field, representation: representation) {
+ throw DeserializationError.invalidFieldRepresentation(field: "property_template." + field, representation: representation)
+ }
+ }
+
+ init(
+ value: Field>? = nil
+ ) {
+ self.value = value
+ }
+
+ private static func resolveOnlyLinks(context: Context, parent: PropertyTemplate?) -> DeserializationResult {
+ let valueValue = parent?.value?.resolveValue(context: context) ?? .noValue
+ var errors = mergeErrors(
+ valueValue.errorsOrWarnings?.map { .right($0.asError(deserializing: "value", level: .error)) }
+ )
+ if case .noValue = valueValue {
+ errors.append(.right(FieldError(fieldName: "value", level: .error, error: .requiredFieldIsMissing)))
+ }
+ guard
+ let valueNonNil = valueValue.value
+ else {
+ return .failure(NonEmptyArray(errors)!)
+ }
+ let result = EntityWithComplexPropertyWithDefaultValue.Property(
+ value: valueNonNil
+ )
+ return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
+ }
+
+ public static func resolveValue(context: Context, parent: PropertyTemplate?, useOnlyLinks: Bool) -> DeserializationResult {
+ if useOnlyLinks {
+ return resolveOnlyLinks(context: context, parent: parent)
+ }
+ var valueValue: DeserializationResult> = parent?.value?.value() ?? .noValue
+ context.templateData.forEach { key, __dictValue in
+ switch key {
+ case "value":
+ valueValue = deserialize(__dictValue).merged(with: valueValue)
+ case parent?.value?.link:
+ valueValue = valueValue.merged(with: deserialize(__dictValue))
+ default: break
+ }
+ }
+ var errors = mergeErrors(
+ valueValue.errorsOrWarnings?.map { Either.right($0.asError(deserializing: "value", level: .error)) }
+ )
+ if case .noValue = valueValue {
+ errors.append(.right(FieldError(fieldName: "value", level: .error, error: .requiredFieldIsMissing)))
+ }
+ guard
+ let valueNonNil = valueValue.value
+ else {
+ return .failure(NonEmptyArray(errors)!)
+ }
+ let result = EntityWithComplexPropertyWithDefaultValue.Property(
+ value: valueNonNil
+ )
+ return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
+ }
+
+ private func mergedWithParent(templates: Templates) throws -> PropertyTemplate {
+ return self
+ }
+
+ public func resolveParent(templates: Templates) throws -> PropertyTemplate {
+ return try mergedWithParent(templates: templates)
+ }
+ }
+
+ public static let type: String = "entity_with_complex_property_with_default_value"
+ public let parent: String? // at least 1 char
+ public let property: Field? // default value: EntityWithComplexPropertyWithDefaultValueTemplate.Property(value: .value("Default text"))
+
+ static let parentValidator: AnyValueValidator =
+ makeStringValidator(minLength: 1)
+
+ public convenience init(dictionary: [String: Any], templateToType: TemplateToType) throws {
+ self.init(
+ parent: try dictionary.getOptionalField("type", validator: Self.parentValidator),
+ property: try dictionary.getOptionalField("property", templateToType: templateToType)
+ )
+ }
+
+ init(
+ parent: String?,
+ property: Field? = nil
+ ) {
+ self.parent = parent
+ self.property = property
+ }
+
+ private static func resolveOnlyLinks(context: Context, parent: EntityWithComplexPropertyWithDefaultValueTemplate?) -> DeserializationResult {
+ let propertyValue = parent?.property?.resolveOptionalValue(context: context, validator: ResolvedValue.propertyValidator, useOnlyLinks: true) ?? .noValue
+ let errors = mergeErrors(
+ propertyValue.errorsOrWarnings?.map { .right($0.asError(deserializing: "property", level: .warning)) }
+ )
+ let result = EntityWithComplexPropertyWithDefaultValue(
+ property: propertyValue.value
+ )
+ return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
+ }
+
+ public static func resolveValue(context: Context, parent: EntityWithComplexPropertyWithDefaultValueTemplate?, useOnlyLinks: Bool) -> DeserializationResult {
+ if useOnlyLinks {
+ return resolveOnlyLinks(context: context, parent: parent)
+ }
+ var propertyValue: DeserializationResult = .noValue
+ context.templateData.forEach { key, __dictValue in
+ switch key {
+ case "property":
+ propertyValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.propertyValidator, type: EntityWithComplexPropertyWithDefaultValueTemplate.PropertyTemplate.self).merged(with: propertyValue)
+ case parent?.property?.link:
+ propertyValue = propertyValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.propertyValidator, type: EntityWithComplexPropertyWithDefaultValueTemplate.PropertyTemplate.self))
+ default: break
+ }
+ }
+ if let parent = parent {
+ propertyValue = propertyValue.merged(with: parent.property?.resolveOptionalValue(context: context, validator: ResolvedValue.propertyValidator, useOnlyLinks: true))
+ }
+ let errors = mergeErrors(
+ propertyValue.errorsOrWarnings?.map { Either.right($0.asError(deserializing: "property", level: .warning)) }
+ )
+ let result = EntityWithComplexPropertyWithDefaultValue(
+ property: propertyValue.value
+ )
+ return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
+ }
+
+ private func mergedWithParent(templates: Templates) throws -> EntityWithComplexPropertyWithDefaultValueTemplate {
+ guard let parent = parent, parent != Self.type else { return self }
+ guard let parentTemplate = templates[parent] as? EntityWithComplexPropertyWithDefaultValueTemplate else {
+ throw DeserializationError.unknownType(type: parent)
+ }
+ let mergedParent = try parentTemplate.mergedWithParent(templates: templates)
+
+ return EntityWithComplexPropertyWithDefaultValueTemplate(
+ parent: nil,
+ property: property ?? mergedParent.property
+ )
+ }
+
+ public func resolveParent(templates: Templates) throws -> EntityWithComplexPropertyWithDefaultValueTemplate {
+ let merged = try mergedWithParent(templates: templates)
+
+ return EntityWithComplexPropertyWithDefaultValueTemplate(
+ parent: nil,
+ property: merged.property?.tryResolveParent(templates: templates)
+ )
+ }
+}
diff --git a/api_generator/tests/references/swift/EntityWithEntityProperty.swift b/api_generator/tests/references/swift/EntityWithEntityProperty.swift
new file mode 100644
index 000000000..1a12ca854
--- /dev/null
+++ b/api_generator/tests/references/swift/EntityWithEntityProperty.swift
@@ -0,0 +1,41 @@
+// Generated code. Do not modify.
+
+import CommonCore
+import Foundation
+import Serialization
+
+public final class EntityWithEntityProperty {
+ public static let type: String = "entity_with_entity_property"
+ public let entity: Entity // default value: .entityWithStringEnumProperty(EntityWithStringEnumProperty(property: .value(.second)))
+
+ static let entityValidator: AnyValueValidator =
+ makeNoOpValueValidator()
+
+ init(
+ entity: Entity? = nil
+ ) {
+ self.entity = entity ?? .entityWithStringEnumProperty(EntityWithStringEnumProperty(property: .value(.second)))
+ }
+}
+
+#if DEBUG
+extension EntityWithEntityProperty: Equatable {
+ public static func ==(lhs: EntityWithEntityProperty, rhs: EntityWithEntityProperty) -> Bool {
+ guard
+ lhs.entity == rhs.entity
+ else {
+ return false
+ }
+ return true
+ }
+}
+#endif
+
+extension EntityWithEntityProperty: Serializable {
+ public func toDictionary() -> [String: ValidSerializationValue] {
+ var result: [String: ValidSerializationValue] = [:]
+ result["type"] = Self.type
+ result["entity"] = entity.toDictionary()
+ return result
+ }
+}
diff --git a/api_generator/tests/references/swift/EntityWithEntityPropertyTemplate.swift b/api_generator/tests/references/swift/EntityWithEntityPropertyTemplate.swift
new file mode 100644
index 000000000..5e8a196c1
--- /dev/null
+++ b/api_generator/tests/references/swift/EntityWithEntityPropertyTemplate.swift
@@ -0,0 +1,88 @@
+// Generated code. Do not modify.
+
+import CommonCore
+import Foundation
+import Serialization
+
+public final class EntityWithEntityPropertyTemplate: TemplateValue, TemplateDeserializable {
+ public static let type: String = "entity_with_entity_property"
+ public let parent: String? // at least 1 char
+ public let entity: Field? // default value: .entityWithStringEnumProperty(EntityWithStringEnumProperty(property: .value(.second)))
+
+ static let parentValidator: AnyValueValidator =
+ makeStringValidator(minLength: 1)
+
+ public convenience init(dictionary: [String: Any], templateToType: TemplateToType) throws {
+ self.init(
+ parent: try dictionary.getOptionalField("type", validator: Self.parentValidator),
+ entity: try dictionary.getOptionalField("entity", templateToType: templateToType)
+ )
+ }
+
+ init(
+ parent: String?,
+ entity: Field? = nil
+ ) {
+ self.parent = parent
+ self.entity = entity
+ }
+
+ private static func resolveOnlyLinks(context: Context, parent: EntityWithEntityPropertyTemplate?) -> DeserializationResult {
+ let entityValue = parent?.entity?.resolveOptionalValue(context: context, validator: ResolvedValue.entityValidator, useOnlyLinks: true) ?? .noValue
+ let errors = mergeErrors(
+ entityValue.errorsOrWarnings?.map { .right($0.asError(deserializing: "entity", level: .warning)) }
+ )
+ let result = EntityWithEntityProperty(
+ entity: entityValue.value
+ )
+ return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
+ }
+
+ public static func resolveValue(context: Context, parent: EntityWithEntityPropertyTemplate?, useOnlyLinks: Bool) -> DeserializationResult {
+ if useOnlyLinks {
+ return resolveOnlyLinks(context: context, parent: parent)
+ }
+ var entityValue: DeserializationResult = .noValue
+ context.templateData.forEach { key, __dictValue in
+ switch key {
+ case "entity":
+ entityValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.entityValidator, type: EntityTemplate.self).merged(with: entityValue)
+ case parent?.entity?.link:
+ entityValue = entityValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.entityValidator, type: EntityTemplate.self))
+ default: break
+ }
+ }
+ if let parent = parent {
+ entityValue = entityValue.merged(with: parent.entity?.resolveOptionalValue(context: context, validator: ResolvedValue.entityValidator, useOnlyLinks: true))
+ }
+ let errors = mergeErrors(
+ entityValue.errorsOrWarnings?.map { Either.right($0.asError(deserializing: "entity", level: .warning)) }
+ )
+ let result = EntityWithEntityProperty(
+ entity: entityValue.value
+ )
+ return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!)
+ }
+
+ private func mergedWithParent(templates: Templates) throws -> EntityWithEntityPropertyTemplate {
+ guard let parent = parent, parent != Self.type else { return self }
+ guard let parentTemplate = templates[parent] as? EntityWithEntityPropertyTemplate else {
+ throw DeserializationError.unknownType(type: parent)
+ }
+ let mergedParent = try parentTemplate.mergedWithParent(templates: templates)
+
+ return EntityWithEntityPropertyTemplate(
+ parent: nil,
+ entity: entity ?? mergedParent.entity
+ )
+ }
+
+ public func resolveParent(templates: Templates) throws -> EntityWithEntityPropertyTemplate {
+ let merged = try mergedWithParent(templates: templates)
+
+ return EntityWithEntityPropertyTemplate(
+ parent: nil,
+ entity: merged.entity?.tryResolveParent(templates: templates)
+ )
+ }
+}
diff --git a/api_generator/tests/references/swift/EntityWithOptionalComplexProperty.swift b/api_generator/tests/references/swift/EntityWithOptionalComplexProperty.swift
new file mode 100644
index 000000000..c4be2fe9b
--- /dev/null
+++ b/api_generator/tests/references/swift/EntityWithOptionalComplexProperty.swift
@@ -0,0 +1,76 @@
+// Generated code. Do not modify.
+
+import CommonCore
+import Foundation
+import Serialization
+
+public final class EntityWithOptionalComplexProperty {
+ public final class Property {
+ public let value: Expression
+
+ public func resolveValue(_ resolver: ExpressionResolver) -> URL? {
+ resolver.resolveStringBasedValue(expression: value, initializer: URL.init(string:))
+ }
+
+ init(
+ value: Expression