Google OR-Tools v9.11
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-2024 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/status/status.h"
21#include "absl/status/statusor.h"
22#include "absl/strings/str_format.h"
23#include "absl/strings/string_view.h"
24#include "google/protobuf/io/tokenizer.h"
25#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
26#include "google/protobuf/json/json.h"
27#include "google/protobuf/message.h"
28#include "google/protobuf/text_format.h"
29#include "google/protobuf/util/json_util.h"
30#include "ortools/base/file.h"
36
37namespace operations_research {
38
39using google::protobuf::util::JsonParseOptions;
40using google::protobuf::util::JsonStringToMessage;
41
42absl::StatusOr<std::string> ReadFileToString(absl::string_view filename) {
43 std::string contents;
44 RETURN_IF_ERROR(file::GetContents(filename, &contents, file::Defaults()));
45 // Try decompressing it.
46 {
47 std::string uncompressed;
48 if (GunzipString(contents, &uncompressed)) contents.swap(uncompressed);
49 }
50 return contents;
51}
52
53absl::Status ReadFileToProto(absl::string_view filename,
54 google::protobuf::Message* proto,
55 bool allow_partial) {
56 std::string data;
58 return util::StatusBuilder(StringToProto(data, proto, allow_partial))
59 << " in file '" << filename << "'";
60}
61
62absl::Status StringToProto(absl::string_view data,
63 google::protobuf::Message* proto,
64 bool allow_partial) {
65 // Try decompressing it.
66 std::string uncompressed;
67 if (GunzipString(data, &uncompressed)) {
68 VLOG(1) << "ReadFileToProto(): input is gzipped";
69 data = uncompressed;
70 }
71 // Try binary format first, then text format, then JSON, then proto3 JSON,
72 // then give up.
73 // For some of those, like binary format and proto3 JSON, we perform
74 // additional checks to verify that we have the right proto: it can happen
75 // to try to read a proto of type Foo as a proto of type Bar, by mistake, and
76 // we'd rather have this function fail rather than silently accept it, because
77 // the proto parser is too lenient with unknown fields.
78 // We don't require ByteSizeLong(parsed) == input.size(), because it may be
79 // the case that the proto version changed and some fields are dropped.
80 // We just fail when the difference is too large.
81 constexpr double kMaxBinaryProtoParseShrinkFactor = 2;
82 std::string binary_format_error;
83 if (proto->ParsePartialFromString(data) &&
84 (allow_partial || proto->IsInitialized())) {
85 // NOTE(user): When using ParseFromString() from a generic
86 // google::protobuf::Message, like we do here, all fields are stored, even
87 // if they are unknown in the underlying proto type. Unless we explicitly
88 // discard those 'unknown fields' here, our call to ByteSizeLong() will
89 // still count the unknown payload.
90 proto->DiscardUnknownFields();
91 if (proto->ByteSizeLong() <
92 data.size() / kMaxBinaryProtoParseShrinkFactor) {
93 binary_format_error =
94 "The input may be a binary protobuf payload, but it"
95 " probably is from a different proto class";
96 } else {
97 VLOG(1) << "StringToProto(): input seems to be a binary proto";
98 return absl::OkStatus();
99 }
100 }
101
102 struct : public google::protobuf::io::ErrorCollector {
103 void RecordError(int line, google::protobuf::io::ColumnNumber column,
104 absl::string_view error_message) override {
105 if (!message.empty()) message += ", ";
106 absl::StrAppendFormat(&message, "%d:%d: %s",
107 // We convert the 0-indexed line to 1-indexed.
108 line + 1, column, error_message);
109 }
110 std::string message;
111 } text_format_error;
112 google::protobuf::TextFormat::Parser text_parser;
113 text_parser.RecordErrorsTo(&text_format_error);
114 text_parser.AllowPartialMessage(allow_partial);
115 if (text_parser.ParseFromString(data, proto)) {
116 VLOG(1) << "StringToProto(): input is a text proto";
117 return absl::OkStatus();
118 }
119 std::string json_error;
120 absl::Status json_status =
121 JsonStringToMessage(data, proto, JsonParseOptions());
122 if (json_status.ok()) {
123 // NOTE(user): We protect against the JSON proto3 parser being very lenient
124 // and easily accepting any JSON as a valid JSON for our proto: if the
125 // parsed proto's size is too small compared to the JSON, we probably parsed
126 // a JSON that wasn't representing a valid proto.
127 constexpr int kMaxJsonToBinaryShrinkFactor = 30;
128 if (proto->ByteSizeLong() < data.size() / kMaxJsonToBinaryShrinkFactor) {
129 json_status = absl::InvalidArgumentError(
130 "The input looks like valid JSON, but probably not"
131 " of the right proto class");
132 } else {
133 VLOG(1) << "StringToProto(): input is a proto JSON";
134 return absl::OkStatus();
135 }
136 }
137 return absl::InvalidArgumentError(absl::StrFormat(
138 "binary format error: '%s', text format error: '%s', json error: '%s'",
139 binary_format_error, text_format_error.message, json_status.message()));
140}
141
142absl::Status WriteProtoToFile(absl::string_view filename,
143 const google::protobuf::Message& proto,
144 ProtoWriteFormat proto_write_format, bool gzipped,
145 bool append_extension_to_file_name) {
146 std::string file_type_suffix;
147 std::string output_string;
148 google::protobuf::io::StringOutputStream stream(&output_string);
149 auto make_error = [filename](absl::string_view error_message) {
150 return absl::InternalError(absl::StrFormat(
151 "WriteProtoToFile('%s') failed: %s", filename, error_message));
152 };
153 switch (proto_write_format) {
155 if (!proto.SerializeToZeroCopyStream(&stream)) {
156 return make_error("SerializeToZeroCopyStream()");
157 }
158 file_type_suffix = ".bin";
159 break;
161 if (!google::protobuf::TextFormat::PrintToString(proto, &output_string)) {
162 return make_error("TextFormat::PrintToString()");
163 }
164 break;
166 google::protobuf::util::JsonPrintOptions options;
167 options.add_whitespace = true;
168 options.always_print_fields_with_no_presence = true;
169 options.preserve_proto_field_names = true;
170 if (!google::protobuf::util::MessageToJsonString(proto, &output_string,
171 options)
172 .ok()) {
173 LOG(WARNING) << "Printing to stream failed.";
174 return make_error("google::protobuf::util::MessageToJsonString()");
175 }
176 file_type_suffix = ".json";
177 break;
178 }
180 google::protobuf::util::JsonPrintOptions options;
181 options.add_whitespace = true;
182 if (!google::protobuf::util::MessageToJsonString(proto, &output_string,
183 options)
184 .ok()) {
185 LOG(WARNING) << "Printing to stream failed.";
186 return make_error("google::protobuf::util::MessageToJsonString()");
187 }
188 file_type_suffix = ".json";
189 break;
190 }
191 if (gzipped) {
192 std::string gzip_string;
193 GzipString(output_string, &gzip_string);
194 output_string.swap(gzip_string);
195 file_type_suffix += ".gz";
196 }
197 std::string output_filename(filename);
198 if (append_extension_to_file_name) output_filename += file_type_suffix;
199 VLOG(1) << "Writing " << output_string.size() << " bytes to '"
200 << output_filename << "'";
201 return file::SetContents(output_filename, output_string, file::Defaults());
202}
203
204} // namespace operations_research
#define RETURN_IF_ERROR(expr)
CpModelProto proto
The output proto.
bool GunzipString(absl::string_view str, std::string *out)
Definition gzipstring.h:22
bool GzipString(absl::string_view uncompressed, std::string *compressed)
Definition gzipstring.h:63
absl::StatusOr< std::string > GetContents(absl::string_view path, Options options)
Definition file.cc:191
absl::Status SetContents(absl::string_view filename, absl::string_view contents, Options options)
Definition file.cc:242
Options Defaults()
Definition file.h:109
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:42
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:62
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:142
absl::Status ReadFileToProto(absl::string_view filename, google::protobuf::Message *proto, bool allow_partial)
Definition file_util.cc:53
int line
int column
std::string message
Definition trace.cc:397