@@ -66,15 +66,17 @@ CURRENT_STRUCT(NamespaceToExpose) {
6666// TODO(dkorolev): Refactor `PrimitiveTypesList` to avoid copy-pasting of `operator()(const *_Primitive& p)`.
6767struct PrimitiveTypesListImpl final {
6868 std::map<TypeID, std::string> cpp_name;
69+ std::map<TypeID, std::string> rust_name;
6970 std::map<TypeID, std::string> fsharp_name;
7071 std::map<TypeID, std::string> markdown_name;
7172 std::map<TypeID, std::string> typescript_name;
7273 PrimitiveTypesListImpl () {
73- #define CURRENT_DECLARE_PRIMITIVE_TYPE (typeid_index, cpp_type, current_type, fs_type, md_type, typescript_type ) \
74+ #define CURRENT_DECLARE_PRIMITIVE_TYPE (typeid_index, cpp_type, current_type, rstype, fs_type, md_type, ts_type ) \
7475 cpp_name[static_cast <TypeID>(TYPEID_BASIC_TYPE + typeid_index)] = #cpp_type; \
76+ rust_name[static_cast <TypeID>(TYPEID_BASIC_TYPE + typeid_index)] = #rstype; \
7577 fsharp_name[static_cast <TypeID>(TYPEID_BASIC_TYPE + typeid_index)] = fs_type; \
7678 markdown_name[static_cast <TypeID>(TYPEID_BASIC_TYPE + typeid_index)] = md_type; \
77- typescript_name[static_cast <TypeID>(TYPEID_BASIC_TYPE + typeid_index)] = typescript_type ;
79+ typescript_name[static_cast <TypeID>(TYPEID_BASIC_TYPE + typeid_index)] = ts_type ;
7880#include " ../primitive_types.dsl.h"
7981#undef CURRENT_DECLARE_PRIMITIVE_TYPE
8082 }
@@ -95,6 +97,7 @@ enum class Language : int {
9597 Current, // C++, `CURRENT_STRUCT`-s.
9698 CPP, // C++, native `struct`-s.
9799 FSharp, // F#.
100+ Rust, // Rust.
98101 Markdown, // [GitHub] Markdown.
99102 JSON, // A compact JSON we use to describe schema to third parties.
100103 TypeScript, // TypeScript.
@@ -853,6 +856,167 @@ struct LanguageSyntaxImpl<Language::FSharp> final {
853856 // LCOV_EXCL_STOP
854857};
855858
859+ template <>
860+ struct LanguageSyntaxImpl <Language::Rust> final {
861+ static std::string Header (const std::string&) {
862+ return " #![allow(unused_imports)]\n "
863+ " use serde::{Deserialize, Serialize};\n "
864+ " use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};\n " ;
865+ }
866+
867+ static std::string Footer (const std::string&) { return " " ; }
868+
869+ static std::string SanitizeRustSymbol (const std::string& unsanitized_name) {
870+ // TODO(dkorolev): Definitely not a complete list.
871+ static std::set<std::string> fsharp_reserved_symbols{" type" ," pub" ," in" };
872+ return fsharp_reserved_symbols.count (unsanitized_name) ? " r#" + unsanitized_name : unsanitized_name;
873+ }
874+
875+ struct FullSchemaPrinter final {
876+ const std::map<TypeID, ReflectedType>& types_;
877+ std::ostream& os_;
878+ mutable std::unordered_set<TypeID, GenericHashFunction<TypeID>>
879+ empty_structs_; // To not print the type of a DU case for empty structs.
880+
881+ std::string TypeName (TypeID type_id) const {
882+ const auto cit = types_.find (type_id);
883+ if (cit == types_.end ()) {
884+ return " UNKNOWN_TYPE_" + current::ToString (type_id); // LCOV_EXCL_LINE
885+ } else {
886+ struct RustTypeNamePrinter final {
887+ const FullSchemaPrinter& self_;
888+ std::ostringstream& oss_;
889+
890+ RustTypeNamePrinter (const FullSchemaPrinter& self, std::ostringstream& oss) : self_(self), oss_(oss) {}
891+
892+ // `operator()(...)`-s of this block print F# type name only, without the expansion.
893+ // They assume the declaration order is respected, and any dependencies have already been listed.
894+ void operator ()(const ReflectedType_Primitive& p) const {
895+ const auto & globals = PrimitiveTypesList ();
896+ if (globals.rust_name .count (p.type_id )) {
897+ oss_ << globals.rust_name .at (p.type_id );
898+ } else {
899+ oss_ << " UNKNOWN_BASIC_TYPE_" + current::ToString (p.type_id ); // LCOV_EXCL_LINE
900+ }
901+ }
902+ void operator ()(const ReflectedType_Enum& e) const { oss_ << SanitizeRustSymbol (e.name ); }
903+ void operator ()(const ReflectedType_Array& a) const {
904+ oss_ << " Vec<" << SanitizeRustSymbol (self_.TypeName (a.element_type )) << ' >' ;
905+ }
906+ void operator ()(const ReflectedType_Vector& v) const {
907+ oss_ << " Vec<" << SanitizeRustSymbol (self_.TypeName (v.element_type )) << ' >' ;
908+ }
909+ void operator ()(const ReflectedType_Map& m) const {
910+ // TODO(dkorolev): Use an ordered dictionary in .NET one day.
911+ oss_ << " BTreeMap<" << SanitizeRustSymbol (self_.TypeName (m.key_type )) << " , "
912+ << self_.TypeName (m.value_type ) << ' >' ;
913+ }
914+ void operator ()(const ReflectedType_UnorderedMap& m) const {
915+ oss_ << " HashMap<" << SanitizeRustSymbol (self_.TypeName (m.key_type )) << " , "
916+ << self_.TypeName (m.value_type ) << ' >' ;
917+ }
918+ void operator ()(const ReflectedType_Set& s) const {
919+ // TODO(dkorolev): Wrong!
920+ oss_ << " BTreeSet<" << self_.TypeName (s.value_type ) << ' >' ;
921+ }
922+ void operator ()(const ReflectedType_UnorderedSet& s) const {
923+ oss_ << " HashSet<" << self_.TypeName (s.value_type ) << ' >' ;
924+ }
925+ void operator ()(const ReflectedType_Pair& p) const {
926+ oss_ << ' (' << SanitizeRustSymbol (self_.TypeName (p.first_type )) << " , "
927+ << SanitizeRustSymbol (self_.TypeName (p.second_type )) << ' )' ;
928+ }
929+ void operator ()(const ReflectedType_Optional& o) const {
930+ oss_ << " Option<" << SanitizeRustSymbol (self_.TypeName (o.optional_type )) << ' >' ;
931+ }
932+ void operator ()(const ReflectedType_Variant& v) const { oss_ << SanitizeRustSymbol (v.name ); }
933+ void operator ()(const ReflectedType_Struct& s) const {
934+ oss_ << SanitizeRustSymbol (s.TemplateInnerTypeExpandedName ());
935+ }
936+ };
937+
938+ std::ostringstream oss;
939+ cit->second .Call (RustTypeNamePrinter (*this , oss));
940+ return oss.str ();
941+ }
942+ }
943+
944+ FullSchemaPrinter (const std::map<TypeID, ReflectedType>& types,
945+ std::ostream& os,
946+ const std::string&,
947+ const Optional<NamespaceToExpose>&)
948+ : types_(types), os_(os) {}
949+
950+ // `operator()`-s of this block print complete declarations of F# types.
951+ // The types that require complete declarations in F# are records and discriminated unions.
952+ void operator ()(const ReflectedType_Primitive&) const {}
953+ void operator ()(const ReflectedType_Enum& e) const {
954+ os_ << " \n TODO(dkorolev): type " << SanitizeRustSymbol (e.name ) << " = " << TypeName (e.underlying_type ) << ' \n ' ;
955+ }
956+ void operator ()(const ReflectedType_Array&) const {}
957+ void operator ()(const ReflectedType_Vector&) const {}
958+ void operator ()(const ReflectedType_Pair&) const {}
959+ void operator ()(const ReflectedType_Map&) const {}
960+ void operator ()(const ReflectedType_UnorderedMap&) const {}
961+ void operator ()(const ReflectedType_Set&) const {}
962+ void operator ()(const ReflectedType_UnorderedSet&) const {}
963+ void operator ()(const ReflectedType_Optional&) const {}
964+ void operator ()(const ReflectedType_Variant& v) const {
965+ os_ << " \n "
966+ << " #[derive(Debug, Serialize, Deserialize)]\n "
967+ << " pub enum " << v.name << " {\n " ;
968+ for (TypeID c : v.cases ) {
969+ const auto name = TypeName (c);
970+ const auto & t = types_.at (c);
971+ CURRENT_ASSERT (Exists<ReflectedType_Struct>(t) || Exists<ReflectedType_Variant>(t)); // Must be one of.
972+ if (!empty_structs_.count (Value<ReflectedTypeBase>(t).type_id )) {
973+ os_ << " " << name << ' (' << name << " ),\n " ;
974+ }
975+ }
976+ os_ << " }\n " ;
977+ }
978+
979+ // When dumping a `CURRENT_STRUCT` as an F# record, since inheritance is not supported by Newtonsoft.JSON,
980+ // all base class fields are hoisted to the top of the record.
981+ void RecursivelyListStructFieldsForRust (std::ostringstream& os, const ReflectedType_Struct& s) const {
982+ if (Exists (s.super_id )) {
983+ RecursivelyListStructFieldsForRust (os, Value<ReflectedType_Struct>(types_.at (Value (s.super_id ))));
984+ }
985+ for (const auto & f : s.fields ) {
986+ if (Exists (f.description )) {
987+ AppendAsMultilineCommentIndentedTwoSpaces (os, Value (f.description ));
988+ }
989+ const auto & t = types_.at (f.type_id );
990+ if (Exists<ReflectedType_Struct>(t) || Exists<ReflectedType_Variant>(t)) {
991+ os << " pub " << SanitizeRustSymbol (f.name ) << " : Box<" << TypeName (f.type_id ) << " >,\n " ;
992+ } else {
993+ os << " pub " << SanitizeRustSymbol (f.name ) << " : " << TypeName (f.type_id ) << " ,\n " ;
994+ }
995+ }
996+ }
997+ void operator ()(const ReflectedType_Struct& s) const {
998+ std::ostringstream os;
999+ RecursivelyListStructFieldsForRust (os, s);
1000+ const std::string fields = os.str ();
1001+ if (!fields.empty ()) {
1002+ os_ << " \n "
1003+ << " #[derive(Debug, Serialize, Deserialize)]\n "
1004+ << " pub struct " << s.TemplateInnerTypeExpandedName () << " {\n "
1005+ << fields
1006+ << " }\n " ;
1007+ } else {
1008+ empty_structs_.insert (s.type_id );
1009+ }
1010+ }
1011+ }; // struct LanguageSyntax<Language::Rust>::FullSchemaPrinter
1012+
1013+ // LCOV_EXCL_START
1014+ static std::string ErrorMessageWithTypeId (TypeID type_id, FullSchemaPrinter&) {
1015+ return " #error \" Unknown struct with `type_id` = " + current::ToString (type_id) + " \"\n " ;
1016+ }
1017+ // LCOV_EXCL_STOP
1018+ };
1019+
8561020template <>
8571021struct LanguageSyntaxImpl <Language::Markdown> final {
8581022 static std::string Header (const std::string&) { return " # Data Dictionary\n " ; }
@@ -1641,6 +1805,8 @@ struct ToStringImpl<reflection::Language, false, true> final {
16411805 return " cpp" ;
16421806 case reflection::Language::FSharp:
16431807 return " fs" ;
1808+ case reflection::Language::Rust:
1809+ return " rs" ;
16441810 case reflection::Language::Markdown:
16451811 return " md" ;
16461812 case reflection::Language::JSON:
0 commit comments