Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
file_util.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <string>
17#include <utility>
18
19#include "absl/log/check.h"
20#include "absl/log/log.h"
21#include "absl/status/status.h"
22#include "absl/status/statusor.h"
23#include "absl/strings/str_format.h"
24#include "absl/strings/string_view.h"
25#include "google/protobuf/io/tokenizer.h"
26#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
27#include "google/protobuf/json/json.h"
28#include "google/protobuf/message.h"
29#include "google/protobuf/text_format.h"
30#include "google/protobuf/util/json_util.h"
31#include "ortools/base/file.h"
37
38namespace operations_research {
39
40using google::protobuf::util::JsonParseOptions;
41using google::protobuf::util::JsonStringToMessage;
42
43absl::StatusOr<std::string> ReadFileToString(absl::string_view filename) {
44 std::string contents;
45 RETURN_IF_ERROR(file::GetContents(filename, &contents, file::Defaults()));
46 // Try decompressing it.
47 {
48 std::string uncompressed;
49 if (GunzipString(contents, &uncompressed)) contents.swap(uncompressed);
50 }
51 return contents;
52}
53
54absl::Status ReadFileToProto(absl::string_view filename,
55 google::protobuf::Message* proto,
56 bool allow_partial) {
57 std::string data;
59 return util::StatusBuilder(StringToProto(data, proto, allow_partial))
60 << " in file '" << filename << "'";
61}
62
63absl::Status StringToProto(absl::string_view data,
64 google::protobuf::Message* proto,
65 bool allow_partial) {
66 // Try decompressing it.
67 std::string uncompressed;
68 if (GunzipString(data, &uncompressed)) {
69 VLOG(1) << "ReadFileToProto(): input is gzipped";
70 data = uncompressed;
71 }
72 // Try binary format first, then text format, then JSON, then proto3 JSON,
73 // then give up.
74 // For some of those, like binary format and proto3 JSON, we perform
75 // additional checks to verify that we have the right proto: it can happen
76 // to try to read a proto of type Foo as a proto of type Bar, by mistake, and
77 // we'd rather have this function fail rather than silently accept it, because
78 // the proto parser is too lenient with unknown fields.
79 // We don't require ByteSizeLong(parsed) == input.size(), because it may be
80 // the case that the proto version changed and some fields are dropped.
81 // We just fail when the difference is too large.
82 constexpr double kMaxBinaryProtoParseShrinkFactor = 2;
83 std::string binary_format_error;
84 if (proto->ParsePartialFromString(data) &&
85 (allow_partial || proto->IsInitialized())) {
86 // NOTE(user): When using ParseFromString() from a generic
87 // google::protobuf::Message, like we do here, all fields are stored, even
88 // if they are unknown in the underlying proto type. Unless we explicitly
89 // discard those 'unknown fields' here, our call to ByteSizeLong() will
90 // still count the unknown payload.
91 proto->DiscardUnknownFields();
92 if (proto->ByteSizeLong() <
93 data.size() / kMaxBinaryProtoParseShrinkFactor) {
94 binary_format_error =
95 "The input may be a binary protobuf payload, but it"
96 " probably is from a different proto class";
97 } else {
98 VLOG(1) << "StringToProto(): input seems to be a binary proto";
99 return absl::OkStatus();
100 }
101 }
102
103 struct : public google::protobuf::io::ErrorCollector {
104 void RecordError(int line, google::protobuf::io::ColumnNumber column,
105 absl::string_view error_message) override {
106 if (!message.empty()) message += ", ";
107 absl::StrAppendFormat(&message, "%d:%d: %s",
108 // We convert the 0-indexed line to 1-indexed.
109 line + 1, column, error_message);
110 }
111 std::string message;
112 } text_format_error;
113 google::protobuf::TextFormat::Parser text_parser;
114 text_parser.RecordErrorsTo(&text_format_error);
115 text_parser.AllowPartialMessage(allow_partial);
116 if (text_parser.ParseFromString(data, proto)) {
117 VLOG(1) << "StringToProto(): input is a text proto";
118 return absl::OkStatus();
119 }
120 std::string json_error;
121 absl::Status json_status =
122 JsonStringToMessage(data, proto, JsonParseOptions());
123 if (json_status.ok()) {
124 // NOTE(user): We protect against the JSON proto3 parser being very lenient
125 // and easily accepting any JSON as a valid JSON for our proto: if the
126 // parsed proto's size is too small compared to the JSON, we probably parsed
127 // a JSON that wasn't representing a valid proto.
128 constexpr int kMaxJsonToBinaryShrinkFactor = 30;
129 if (proto->ByteSizeLong() < data.size() / kMaxJsonToBinaryShrinkFactor) {
130 json_status = absl::InvalidArgumentError(
131 "The input looks like valid JSON, but probably not"
132 " of the right proto class");
133 } else {
134 VLOG(1) << "StringToProto(): input is a proto JSON";
135 return absl::OkStatus();
136 }
137 }
138 return absl::InvalidArgumentError(absl::StrFormat(
139 "binary format error: '%s', text format error: '%s', json error: '%s'",
140 binary_format_error, text_format_error.message, json_status.message()));
141}
142
143absl::Status WriteProtoToFile(absl::string_view filename,
144 const google::protobuf::Message& proto,
145 ProtoWriteFormat proto_write_format, bool gzipped,
146 bool append_extension_to_file_name) {
147 std::string file_type_suffix;
148 std::string output_string;
149 google::protobuf::io::StringOutputStream stream(&output_string);
150 auto make_error = [filename](absl::string_view error_message) {
151 return absl::InternalError(absl::StrFormat(
152 "WriteProtoToFile('%s') failed: %s", filename, error_message));
153 };
154 switch (proto_write_format) {
156 if (!proto.SerializeToZeroCopyStream(&stream)) {
157 return make_error("SerializeToZeroCopyStream()");
158 }
159 file_type_suffix = ".bin";
160 break;
162 if (!google::protobuf::TextFormat::PrintToString(proto, &output_string)) {
163 return make_error("TextFormat::PrintToString()");
164 }
165 break;
167 google::protobuf::util::JsonPrintOptions options;
168 options.add_whitespace = true;
169 options.always_print_fields_with_no_presence = true;
170 options.preserve_proto_field_names = true;
171 if (!google::protobuf::util::MessageToJsonString(proto, &output_string,
172 options)
173 .ok()) {
174 LOG(WARNING) << "Printing to stream failed.";
175 return make_error("google::protobuf::util::MessageToJsonString()");
176 }
177 file_type_suffix = ".json";
178 break;
179 }
181 google::protobuf::util::JsonPrintOptions options;
182 options.add_whitespace = true;
183 if (!google::protobuf::util::MessageToJsonString(proto, &output_string,
184 options)
185 .ok()) {
186 LOG(WARNING) << "Printing to stream failed.";
187 return make_error("google::protobuf::util::MessageToJsonString()");
188 }
189 file_type_suffix = ".json";
190 break;
191 }
192 if (gzipped) {
193 std::string gzip_string;
194 GzipString(output_string, &gzip_string);
195 output_string.swap(gzip_string);
196 file_type_suffix += ".gz";
197 }
198 std::string output_filename(filename);
199 if (append_extension_to_file_name) output_filename += file_type_suffix;
200 VLOG(1) << "Writing " << output_string.size() << " bytes to '"
201 << output_filename << "'";
202 return file::SetContents(output_filename, output_string, file::Defaults());
203}
204
205} // namespace operations_research
#define RETURN_IF_ERROR(expr)
bool GunzipString(absl::string_view str, std::string *out)
Definition gzipstring.h:24
bool GzipString(absl::string_view uncompressed, std::string *compressed)
Definition gzipstring.h:65
absl::StatusOr< std::string > GetContents(absl::string_view path, Options options)
-— Content API -—
Definition file.cc:348
Options Defaults()
Definition file.h:85
absl::Status SetContents(absl::string_view file_name, absl::string_view contents, Options options)
Definition file.cc:388
In SWIG mode, we don't want anything besides these top-level includes.
absl::StatusOr< std::string > ReadFileToString(absl::string_view filename)
Reads a file, optionally gzipped, to a string.
Definition file_util.cc:43
absl::Status StringToProto(absl::string_view data, google::protobuf::Message *proto, bool allow_partial)
Exactly like ReadFileToProto(), but directly from the contents.
Definition file_util.cc:63
absl::Status WriteProtoToFile(absl::string_view filename, const google::protobuf::Message &proto, ProtoWriteFormat proto_write_format, bool gzipped, bool append_extension_to_file_name)
Definition file_util.cc:143
absl::Status ReadFileToProto(absl::string_view filename, google::protobuf::Message *proto, bool allow_partial)
Definition file_util.cc:54