xref: /aosp_15_r20/external/flatbuffers/src/idl_gen_json_schema.cpp (revision 890232f25432b36107d06881e0a25aaa6b473652)
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 
27 namespace {
28 
29 template<class T>
GenFullName(const T * enum_def)30 static std::string GenFullName(const T *enum_def) {
31   std::string full_name;
32   const auto &name_spaces = enum_def->defined_namespace->components;
33   for (auto ns = name_spaces.cbegin(); ns != name_spaces.cend(); ++ns) {
34     full_name.append(*ns + "_");
35   }
36   full_name.append(enum_def->name);
37   return full_name;
38 }
39 
40 template<class T>
GenTypeRef(const T * enum_def)41 static std::string GenTypeRef(const T *enum_def) {
42   return "\"$ref\" : \"#/definitions/" + GenFullName(enum_def) + "\"";
43 }
44 
GenType(const std::string & name)45 static std::string GenType(const std::string &name) {
46   return "\"type\" : \"" + name + "\"";
47 }
48 
GenType(BaseType type)49 static std::string GenType(BaseType type) {
50   switch (type) {
51     case BASE_TYPE_BOOL: return "\"type\" : \"boolean\"";
52     case BASE_TYPE_CHAR:
53       return "\"type\" : \"integer\", \"minimum\" : " +
54              NumToString(std::numeric_limits<int8_t>::min()) +
55              ", \"maximum\" : " +
56              NumToString(std::numeric_limits<int8_t>::max());
57     case BASE_TYPE_UCHAR:
58       return "\"type\" : \"integer\", \"minimum\" : 0, \"maximum\" :" +
59              NumToString(std::numeric_limits<uint8_t>::max());
60     case BASE_TYPE_SHORT:
61       return "\"type\" : \"integer\", \"minimum\" : " +
62              NumToString(std::numeric_limits<int16_t>::min()) +
63              ", \"maximum\" : " +
64              NumToString(std::numeric_limits<int16_t>::max());
65     case BASE_TYPE_USHORT:
66       return "\"type\" : \"integer\", \"minimum\" : 0, \"maximum\" : " +
67              NumToString(std::numeric_limits<uint16_t>::max());
68     case BASE_TYPE_INT:
69       return "\"type\" : \"integer\", \"minimum\" : " +
70              NumToString(std::numeric_limits<int32_t>::min()) +
71              ", \"maximum\" : " +
72              NumToString(std::numeric_limits<int32_t>::max());
73     case BASE_TYPE_UINT:
74       return "\"type\" : \"integer\", \"minimum\" : 0, \"maximum\" : " +
75              NumToString(std::numeric_limits<uint32_t>::max());
76     case BASE_TYPE_LONG:
77       return "\"type\" : \"integer\", \"minimum\" : " +
78              NumToString(std::numeric_limits<int64_t>::min()) +
79              ", \"maximum\" : " +
80              NumToString(std::numeric_limits<int64_t>::max());
81     case BASE_TYPE_ULONG:
82       return "\"type\" : \"integer\", \"minimum\" : 0, \"maximum\" : " +
83              NumToString(std::numeric_limits<uint64_t>::max());
84     case BASE_TYPE_FLOAT:
85     case BASE_TYPE_DOUBLE: return "\"type\" : \"number\"";
86     case BASE_TYPE_STRING: return "\"type\" : \"string\"";
87     default: return "";
88   }
89 }
90 
GenBaseType(const Type & type)91 static std::string GenBaseType(const Type &type) {
92   if (type.struct_def != nullptr) { return GenTypeRef(type.struct_def); }
93   if (type.enum_def != nullptr) { return GenTypeRef(type.enum_def); }
94   return GenType(type.base_type);
95 }
96 
GenArrayType(const Type & type)97 static std::string GenArrayType(const Type &type) {
98   std::string element_type;
99   if (type.struct_def != nullptr) {
100     element_type = GenTypeRef(type.struct_def);
101   } else if (type.enum_def != nullptr) {
102     element_type = GenTypeRef(type.enum_def);
103   } else {
104     element_type = GenType(type.element);
105   }
106 
107   return "\"type\" : \"array\", \"items\" : {" + element_type + "}";
108 }
109 
GenType(const Type & type)110 static std::string GenType(const Type &type) {
111   switch (type.base_type) {
112     case BASE_TYPE_ARRAY: FLATBUFFERS_FALLTHROUGH();  // fall thru
113     case BASE_TYPE_VECTOR: {
114       return GenArrayType(type);
115     }
116     case BASE_TYPE_STRUCT: {
117       return GenTypeRef(type.struct_def);
118     }
119     case BASE_TYPE_UNION: {
120       std::string union_type_string("\"anyOf\": [");
121       const auto &union_types = type.enum_def->Vals();
122       for (auto ut = union_types.cbegin(); ut < union_types.cend(); ++ut) {
123         const auto &union_type = *ut;
124         if (union_type->union_type.base_type == BASE_TYPE_NONE) { continue; }
125         if (union_type->union_type.base_type == BASE_TYPE_STRUCT) {
126           union_type_string.append(
127               "{ " + GenTypeRef(union_type->union_type.struct_def) + " }");
128         }
129         if (union_type != *type.enum_def->Vals().rbegin()) {
130           union_type_string.append(",");
131         }
132       }
133       union_type_string.append("]");
134       return union_type_string;
135     }
136     case BASE_TYPE_UTYPE: return GenTypeRef(type.enum_def);
137     default: {
138       return GenBaseType(type);
139     }
140   }
141 }
142 
143 } // namespace
144 
145 class JsonSchemaGenerator : public BaseGenerator {
146  private:
147   std::string code_;
148 
149  public:
JsonSchemaGenerator(const Parser & parser,const std::string & path,const std::string & file_name)150   JsonSchemaGenerator(const Parser &parser, const std::string &path,
151                       const std::string &file_name)
152       : BaseGenerator(parser, path, file_name, "", "", "json") {}
153 
JsonSchemaGenerator(const BaseGenerator & base_generator)154   explicit JsonSchemaGenerator(const BaseGenerator &base_generator)
155       : BaseGenerator(base_generator) {}
156 
GeneratedFileName(const std::string & path,const std::string & file_name,const IDLOptions & options) const157   std::string GeneratedFileName(const std::string &path,
158                                 const std::string &file_name,
159                                 const IDLOptions &options /* unused */) const {
160     (void)options;
161     return path + file_name + ".schema.json";
162   }
163 
164   // If indentation is less than 0, that indicates we don't want any newlines
165   // either.
NewLine() const166   std::string NewLine() const {
167     return parser_.opts.indent_step >= 0 ? "\n" : "";
168   }
169 
Indent(int indent) const170   std::string Indent(int indent) const {
171     const auto num_spaces = indent * std::max(parser_.opts.indent_step, 0);
172     return std::string(num_spaces, ' ');
173   }
174 
PrepareDescription(const std::vector<std::string> & comment_lines)175   std::string PrepareDescription(
176       const std::vector<std::string> &comment_lines) {
177     std::string comment;
178     for (auto line_iterator = comment_lines.cbegin();
179          line_iterator != comment_lines.cend(); ++line_iterator) {
180       const auto &comment_line = *line_iterator;
181 
182       // remove leading and trailing spaces from comment line
183       const auto start = std::find_if(comment_line.begin(), comment_line.end(),
184                                       [](char c) { return !isspace(c); });
185       const auto end =
186           std::find_if(comment_line.rbegin(), comment_line.rend(), [](char c) {
187             return !isspace(c);
188           }).base();
189       if (start < end) {
190         comment.append(start, end);
191       } else {
192         comment.append(comment_line);
193       }
194 
195       if (line_iterator + 1 != comment_lines.cend()) comment.append("\n");
196     }
197     if (!comment.empty()) {
198       std::string description;
199       if (EscapeString(comment.c_str(), comment.length(), &description, true,
200                        true)) {
201         return description;
202       }
203       return "";
204     }
205     return "";
206   }
207 
generate()208   bool generate() {
209     code_ = "";
210     if (parser_.root_struct_def_ == nullptr) {
211       std::cerr << "Error: Binary schema not generated, no root struct found\n";
212       return false;
213     }
214     code_ += "{" + NewLine();
215     code_ += Indent(1) +
216              "\"$schema\": \"https://json-schema.org/draft/2019-09/schema\"," +
217              NewLine();
218     code_ += Indent(1) + "\"definitions\": {" + NewLine();
219     for (auto e = parser_.enums_.vec.cbegin(); e != parser_.enums_.vec.cend();
220          ++e) {
221       code_ += Indent(2) + "\"" + GenFullName(*e) + "\" : {" + NewLine();
222       code_ += Indent(3) + GenType("string") + "," + NewLine();
223       auto enumdef(Indent(3) + "\"enum\": [");
224       for (auto enum_value = (*e)->Vals().begin();
225            enum_value != (*e)->Vals().end(); ++enum_value) {
226         enumdef.append("\"" + (*enum_value)->name + "\"");
227         if (*enum_value != (*e)->Vals().back()) { enumdef.append(", "); }
228       }
229       enumdef.append("]");
230       code_ += enumdef + NewLine();
231       code_ += Indent(2) + "}," + NewLine();  // close type
232     }
233     for (auto s = parser_.structs_.vec.cbegin();
234          s != parser_.structs_.vec.cend(); ++s) {
235       const auto &structure = *s;
236       code_ += Indent(2) + "\"" + GenFullName(structure) + "\" : {" + NewLine();
237       code_ += Indent(3) + GenType("object") + "," + NewLine();
238       const auto &comment_lines = structure->doc_comment;
239       auto comment = PrepareDescription(comment_lines);
240       if (comment != "") {
241         code_ += Indent(3) + "\"description\" : " + comment + "," + NewLine();
242       }
243 
244       code_ += Indent(3) + "\"properties\" : {" + NewLine();
245 
246       const auto &properties = structure->fields.vec;
247       for (auto prop = properties.cbegin(); prop != properties.cend(); ++prop) {
248         const auto &property = *prop;
249         std::string arrayInfo = "";
250         if (IsArray(property->value.type)) {
251           arrayInfo = "," + NewLine() + Indent(8) + "\"minItems\": " +
252                       NumToString(property->value.type.fixed_length) + "," +
253                       NewLine() + Indent(8) + "\"maxItems\": " +
254                       NumToString(property->value.type.fixed_length);
255         }
256         std::string deprecated_info = "";
257         if (property->deprecated) {
258           deprecated_info =
259               "," + NewLine() + Indent(8) + "\"deprecated\" : true";
260         }
261         std::string typeLine = Indent(4) + "\"" + property->name + "\"";
262         typeLine += " : {" + NewLine() + Indent(8);
263         typeLine += GenType(property->value.type);
264         typeLine += arrayInfo;
265         typeLine += deprecated_info;
266         auto description = PrepareDescription(property->doc_comment);
267         if (description != "") {
268           typeLine +=
269               "," + NewLine() + Indent(8) + "\"description\" : " + description;
270         }
271 
272         typeLine += NewLine() + Indent(7) + "}";
273         if (property != properties.back()) { typeLine.append(","); }
274         code_ += typeLine + NewLine();
275       }
276       code_ += Indent(3) + "}," + NewLine();  // close properties
277 
278       std::vector<FieldDef *> requiredProperties;
279       std::copy_if(properties.begin(), properties.end(),
280                    back_inserter(requiredProperties),
281                    [](FieldDef const *prop) { return prop->IsRequired(); });
282       if (!requiredProperties.empty()) {
283         auto required_string(Indent(3) + "\"required\" : [");
284         for (auto req_prop = requiredProperties.cbegin();
285              req_prop != requiredProperties.cend(); ++req_prop) {
286           required_string.append("\"" + (*req_prop)->name + "\"");
287           if (*req_prop != requiredProperties.back()) {
288             required_string.append(", ");
289           }
290         }
291         required_string.append("],");
292         code_ += required_string + NewLine();
293       }
294       code_ += Indent(3) + "\"additionalProperties\" : false" + NewLine();
295       auto closeType(Indent(2) + "}");
296       if (*s != parser_.structs_.vec.back()) { closeType.append(","); }
297       code_ += closeType + NewLine();  // close type
298     }
299     code_ += Indent(1) + "}," + NewLine();  // close definitions
300 
301     // mark root type
302     code_ += Indent(1) + "\"$ref\" : \"#/definitions/" +
303              GenFullName(parser_.root_struct_def_) + "\"" + NewLine();
304 
305     code_ += "}" + NewLine();  // close schema root
306     return true;
307   }
308 
save() const309   bool save() const {
310     const auto file_path = GeneratedFileName(path_, file_name_, parser_.opts);
311     return SaveFile(file_path.c_str(), code_, false);
312   }
313 
getJson()314   const std::string getJson() { return code_; }
315 };
316 }  // namespace jsons
317 
GenerateJsonSchema(const Parser & parser,const std::string & path,const std::string & file_name)318 bool GenerateJsonSchema(const Parser &parser, const std::string &path,
319                         const std::string &file_name) {
320   jsons::JsonSchemaGenerator generator(parser, path, file_name);
321   if (!generator.generate()) { return false; }
322   return generator.save();
323 }
324 
GenerateJsonSchema(const Parser & parser,std::string * json)325 bool GenerateJsonSchema(const Parser &parser, std::string *json) {
326   jsons::JsonSchemaGenerator generator(parser, "", "");
327   if (!generator.generate()) { return false; }
328   *json = generator.getJson();
329   return true;
330 }
331 }  // namespace flatbuffers
332