• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014 Google Inc. All rights reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <iostream>
18 
19 #include "flatbuffers/code_generators.h"
20 #include "flatbuffers/idl.h"
21 #include "flatbuffers/util.h"
22 
23 namespace flatbuffers {
24 
25 namespace jsons {
26 
GenFullName(const T * enum_def)27 template<class T> std::string GenFullName(const T *enum_def) {
28   std::string full_name;
29   const auto &name_spaces = enum_def->defined_namespace->components;
30   for (auto ns = name_spaces.cbegin(); ns != name_spaces.cend(); ++ns) {
31     full_name.append(*ns + "_");
32   }
33   full_name.append(enum_def->name);
34   return full_name;
35 }
36 
GenTypeRef(const T * enum_def)37 template<class T> std::string GenTypeRef(const T *enum_def) {
38   return "\"$ref\" : \"#/definitions/" + GenFullName(enum_def) + "\"";
39 }
40 
GenType(const std::string & name)41 std::string GenType(const std::string &name) {
42   return "\"type\" : \"" + name + "\"";
43 }
44 
GenType(BaseType type)45 std::string GenType(BaseType type) {
46   switch (type) {
47     case BASE_TYPE_BOOL: return "\"type\" : \"boolean\"";
48     case BASE_TYPE_CHAR:
49       return "\"type\" : \"integer\", \"minimum\" : " +
50              NumToString(std::numeric_limits<int8_t>::min()) +
51              ", \"maximum\" : " +
52              NumToString(std::numeric_limits<int8_t>::max());
53     case BASE_TYPE_UCHAR:
54       return "\"type\" : \"integer\", \"minimum\" : 0, \"maximum\" :" +
55              NumToString(std::numeric_limits<uint8_t>::max());
56     case BASE_TYPE_SHORT:
57       return "\"type\" : \"integer\", \"minimum\" : " +
58              NumToString(std::numeric_limits<int16_t>::min()) +
59              ", \"maximum\" : " +
60              NumToString(std::numeric_limits<int16_t>::max());
61     case BASE_TYPE_USHORT:
62       return "\"type\" : \"integer\", \"minimum\" : 0, \"maximum\" : " +
63              NumToString(std::numeric_limits<uint16_t>::max());
64     case BASE_TYPE_INT:
65       return "\"type\" : \"integer\", \"minimum\" : " +
66              NumToString(std::numeric_limits<int32_t>::min()) +
67              ", \"maximum\" : " +
68              NumToString(std::numeric_limits<int32_t>::max());
69     case BASE_TYPE_UINT:
70       return "\"type\" : \"integer\", \"minimum\" : 0, \"maximum\" : " +
71              NumToString(std::numeric_limits<uint32_t>::max());
72     case BASE_TYPE_LONG:
73       return "\"type\" : \"integer\", \"minimum\" : " +
74              NumToString(std::numeric_limits<int64_t>::min()) +
75              ", \"maximum\" : " +
76              NumToString(std::numeric_limits<int64_t>::max());
77     case BASE_TYPE_ULONG:
78       return "\"type\" : \"integer\", \"minimum\" : 0, \"maximum\" : " +
79              NumToString(std::numeric_limits<uint64_t>::max());
80     case BASE_TYPE_FLOAT:
81     case BASE_TYPE_DOUBLE: return "\"type\" : \"number\"";
82     case BASE_TYPE_STRING: return "\"type\" : \"string\"";
83     default: return "";
84   }
85 }
86 
GenBaseType(const Type & type)87 std::string GenBaseType(const Type &type) {
88   if (type.struct_def != nullptr) { return GenTypeRef(type.struct_def); }
89   if (type.enum_def != nullptr) { return GenTypeRef(type.enum_def); }
90   return GenType(type.base_type);
91 }
92 
GenArrayType(const Type & type)93 std::string GenArrayType(const Type &type) {
94   std::string element_type;
95   if (type.struct_def != nullptr) {
96     element_type = GenTypeRef(type.struct_def);
97   } else if (type.enum_def != nullptr) {
98     element_type = GenTypeRef(type.enum_def);
99   } else {
100     element_type = GenType(type.element);
101   }
102 
103   return "\"type\" : \"array\", \"items\" : {" + element_type + "}";
104 }
105 
GenType(const Type & type)106 std::string GenType(const Type &type) {
107   switch (type.base_type) {
108     case BASE_TYPE_ARRAY: FLATBUFFERS_FALLTHROUGH();  // fall thru
109     case BASE_TYPE_VECTOR: {
110       return GenArrayType(type);
111     }
112     case BASE_TYPE_STRUCT: {
113       return GenTypeRef(type.struct_def);
114     }
115     case BASE_TYPE_UNION: {
116       std::string union_type_string("\"anyOf\": [");
117       const auto &union_types = type.enum_def->Vals();
118       for (auto ut = union_types.cbegin(); ut < union_types.cend(); ++ut) {
119         const auto &union_type = *ut;
120         if (union_type->union_type.base_type == BASE_TYPE_NONE) { continue; }
121         if (union_type->union_type.base_type == BASE_TYPE_STRUCT) {
122           union_type_string.append(
123               "{ " + GenTypeRef(union_type->union_type.struct_def) + " }");
124         }
125         if (union_type != *type.enum_def->Vals().rbegin()) {
126           union_type_string.append(",");
127         }
128       }
129       union_type_string.append("]");
130       return union_type_string;
131     }
132     case BASE_TYPE_UTYPE: return GenTypeRef(type.enum_def);
133     default: {
134       return GenBaseType(type);
135     }
136   }
137 }
138 
139 class JsonSchemaGenerator : public BaseGenerator {
140  private:
141   std::string code_;
142 
143  public:
JsonSchemaGenerator(const Parser & parser,const std::string & path,const std::string & file_name)144   JsonSchemaGenerator(const Parser &parser, const std::string &path,
145                       const std::string &file_name)
146       : BaseGenerator(parser, path, file_name, "", "", "json") {}
147 
JsonSchemaGenerator(const BaseGenerator & base_generator)148   explicit JsonSchemaGenerator(const BaseGenerator &base_generator)
149       : BaseGenerator(base_generator) {}
150 
GeneratedFileName(const std::string & path,const std::string & file_name,const IDLOptions & options) const151   std::string GeneratedFileName(const std::string &path,
152                                 const std::string &file_name,
153                                 const IDLOptions &options /* unused */) const {
154     (void)options;
155     return path + file_name + ".schema.json";
156   }
157 
158   // If indentation is less than 0, that indicates we don't want any newlines
159   // either.
NewLine() const160   std::string NewLine() const {
161     return parser_.opts.indent_step >= 0 ? "\n" : "";
162   }
163 
Indent(int indent) const164   std::string Indent(int indent) const {
165     const auto num_spaces = indent * std::max(parser_.opts.indent_step, 0);
166     return std::string(num_spaces, ' ');
167   }
168 
generate()169   bool generate() {
170     code_ = "";
171     if (parser_.root_struct_def_ == nullptr) { return false; }
172     code_ += "{" + NewLine();
173     code_ += Indent(1) +
174              "\"$schema\": \"https://json-schema.org/draft/2019-09/schema\"," +
175              NewLine();
176     code_ += Indent(1) + "\"definitions\": {" + NewLine();
177     for (auto e = parser_.enums_.vec.cbegin(); e != parser_.enums_.vec.cend();
178          ++e) {
179       code_ += Indent(2) + "\"" + GenFullName(*e) + "\" : {" + NewLine();
180       code_ += Indent(3) + GenType("string") + "," + NewLine();
181       auto enumdef(Indent(3) + "\"enum\": [");
182       for (auto enum_value = (*e)->Vals().begin();
183            enum_value != (*e)->Vals().end(); ++enum_value) {
184         enumdef.append("\"" + (*enum_value)->name + "\"");
185         if (*enum_value != (*e)->Vals().back()) { enumdef.append(", "); }
186       }
187       enumdef.append("]");
188       code_ += enumdef + NewLine();
189       code_ += Indent(2) + "}," + NewLine();  // close type
190     }
191     for (auto s = parser_.structs_.vec.cbegin();
192          s != parser_.structs_.vec.cend(); ++s) {
193       const auto &structure = *s;
194       code_ += Indent(2) + "\"" + GenFullName(structure) + "\" : {" + NewLine();
195       code_ += Indent(3) + GenType("object") + "," + NewLine();
196       std::string comment;
197       const auto &comment_lines = structure->doc_comment;
198       for (auto comment_line = comment_lines.cbegin();
199            comment_line != comment_lines.cend(); ++comment_line) {
200         comment.append(*comment_line);
201       }
202       if (!comment.empty()) {
203         std::string description;
204         if (!EscapeString(comment.c_str(), comment.length(), &description, true,
205                           true)) {
206           return false;
207         }
208         code_ +=
209             Indent(3) + "\"description\" : " + description + "," + NewLine();
210       }
211       code_ += Indent(3) + "\"properties\" : {" + NewLine();
212 
213       const auto &properties = structure->fields.vec;
214       for (auto prop = properties.cbegin(); prop != properties.cend(); ++prop) {
215         const auto &property = *prop;
216         std::string arrayInfo = "";
217         if (IsArray(property->value.type)) {
218           arrayInfo = "," + NewLine() + Indent(8) + "\"minItems\": " +
219                       NumToString(property->value.type.fixed_length) + "," +
220                       NewLine() + Indent(8) + "\"maxItems\": " +
221                       NumToString(property->value.type.fixed_length);
222         }
223         std::string deprecated_info = "";
224         if (property->deprecated) {
225           deprecated_info =
226               "," + NewLine() + Indent(8) + "\"deprecated\" : true,";
227         }
228         std::string typeLine = Indent(4) + "\"" + property->name + "\"";
229         typeLine += " : {" + NewLine() + Indent(8);
230         typeLine += GenType(property->value.type);
231         typeLine += arrayInfo;
232         typeLine += deprecated_info;
233         typeLine += NewLine() + Indent(7) + "}";
234         if (property != properties.back()) { typeLine.append(","); }
235         code_ += typeLine + NewLine();
236       }
237       code_ += Indent(3) + "}," + NewLine();  // close properties
238 
239       std::vector<FieldDef *> requiredProperties;
240       std::copy_if(properties.begin(), properties.end(),
241                    back_inserter(requiredProperties),
242                    [](FieldDef const *prop) { return prop->IsRequired(); });
243       if (!requiredProperties.empty()) {
244         auto required_string(Indent(3) + "\"required\" : [");
245         for (auto req_prop = requiredProperties.cbegin();
246              req_prop != requiredProperties.cend(); ++req_prop) {
247           required_string.append("\"" + (*req_prop)->name + "\"");
248           if (*req_prop != requiredProperties.back()) {
249             required_string.append(", ");
250           }
251         }
252         required_string.append("],");
253         code_ += required_string + NewLine();
254       }
255       code_ += Indent(3) + "\"additionalProperties\" : false" + NewLine();
256       auto closeType(Indent(2) + "}");
257       if (*s != parser_.structs_.vec.back()) { closeType.append(","); }
258       code_ += closeType + NewLine();  // close type
259     }
260     code_ += Indent(1) + "}," + NewLine();  // close definitions
261 
262     // mark root type
263     code_ += Indent(1) + "\"$ref\" : \"#/definitions/" +
264              GenFullName(parser_.root_struct_def_) + "\"" + NewLine();
265 
266     code_ += "}" + NewLine();  // close schema root
267     return true;
268   }
269 
save() const270   bool save() const {
271     const auto file_path = GeneratedFileName(path_, file_name_, parser_.opts);
272     return SaveFile(file_path.c_str(), code_, false);
273   }
274 
getJson()275   const std::string getJson() { return code_; }
276 };
277 }  // namespace jsons
278 
GenerateJsonSchema(const Parser & parser,const std::string & path,const std::string & file_name)279 bool GenerateJsonSchema(const Parser &parser, const std::string &path,
280                         const std::string &file_name) {
281   jsons::JsonSchemaGenerator generator(parser, path, file_name);
282   if (!generator.generate()) { return false; }
283   return generator.save();
284 }
285 
GenerateJsonSchema(const Parser & parser,std::string * json)286 bool GenerateJsonSchema(const Parser &parser, std::string *json) {
287   jsons::JsonSchemaGenerator generator(parser, "", "");
288   if (!generator.generate()) { return false; }
289   *json = generator.getJson();
290   return true;
291 }
292 }  // namespace flatbuffers
293