$darkmode
path_impl.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2023 Robert Bosch GmbH
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  * SPDX-License-Identifier: Apache-2.0
17  */
23 #pragma once
24 
25 #include <fable/schema/path.hpp>
26 
27 #include <limits> // for numeric_limits
28 #include <optional> // for optional<>
29 #include <regex> // for regex, regex_match
30 #include <string> // for string
31 #include <vector> // for vector<>
32 
33 #include <fable/environment.hpp> // for interpolate_vars
34 #include <fable/utility/string.hpp> // for split_string
35 
36 namespace fable::schema {
37 
38 namespace detail {
39 
40 // Defined in path.cpp
41 const char* path_state_cstr(PathState e);
42 
43 // The templates below are defined separately for
44 // boost::filesystem::path AND std::filesystem::path
45 template <typename TPath>
46 bool exists(const TPath& path);
47 
48 template <typename TPath>
49 bool is_regular_file(const TPath& path);
50 
51 template <typename TPath>
52 bool is_directory(const TPath& path);
53 
54 template <typename TPath>
55 bool is_other(const TPath& path);
56 
57 template <typename TPath>
58 TPath canonical(const TPath& path);
59 
60 template <typename TPath>
61 std::optional<TPath> search_path(const TPath& executable);
62 
63 } // namespace detail
64 
65 template <typename T>
67  Json j{
68  {"type", "string"},
69  };
70  if (!pattern_.empty()) {
71  j["pattern"] = pattern_;
72  }
73  if (min_length_ != 0) {
74  j["minLength"] = min_length_;
75  }
76  if (max_length_ != std::numeric_limits<size_t>::max()) {
77  j["maxLength"] = max_length_;
78  }
79 
80  if (req_state_ != State::Any) {
81  j["comment"] = detail::path_state_cstr(req_state_);
82  }
83 
84  this->augment_schema(j);
85  return j;
86 }
87 
88 template <typename T>
89 bool Path<T>::validate(const Conf& c, std::optional<SchemaError>& err) const {
90  if (!this->validate_type(c, err)) {
91  return false;
92  }
93 
94  auto src = c.get<std::string>();
95  if (interpolate_) {
96  try {
97  src = interpolate_vars(src, env_);
98  } catch (std::invalid_argument& e) {
99  // This error is thrown when $ is used incorrectly. It's a user-error so pass it on.
100  return this->set_error(err, c, "error interpolating variable in path: {}", e.what());
101  }
102  }
103  if (src.size() < min_length_) {
104  return this->set_error(err, c, "expect minimum path length of {}, got {}", min_length_,
105  src.size());
106  }
107  if (src.size() > max_length_) {
108  return this->set_error(err, c, "expect maximum path length of {}, got {}", max_length_,
109  src.size());
110  }
111  if (!pattern_.empty() && !std::regex_match(src, std::regex(pattern_))) {
112  return this->set_error(err, c, "expect path to match regex '{}': {}", pattern_, src);
113  }
114 
115  Type p{src};
116  if (req_abs_ && !p.is_absolute()) {
117  return this->set_error(err, c, "expect path to be absolute: {}", src);
118  }
119  if (resolve_) {
120  try {
121  p = resolve_path(c, p);
122  } catch (SchemaError& e) {
123  err.emplace(std::move(e));
124  return false;
125  }
126  }
127 
128  switch (req_state_) {
129  case State::Absent:
130  if (detail::exists(p)) {
131  return this->set_error(err, c, detail::path_state_cstr(req_state_));
132  }
133  break;
134  case State::Exists:
135  if (!detail::exists(p)) {
136  return this->set_error(err, c, detail::path_state_cstr(req_state_));
137  }
138  break;
139  case State::Executable:
140  // fallthrough
141  case State::FileExists:
142  if (!detail::is_regular_file(p)) {
143  return this->set_error(err, c, detail::path_state_cstr(req_state_));
144  }
145  break;
146  case State::DirExists:
147  if (!detail::is_directory(p)) {
148  return this->set_error(err, c, detail::path_state_cstr(req_state_));
149  }
150  break;
151  case State::NotFile:
152  if (detail::is_regular_file(p) || detail::is_other(p)) {
153  return this->set_error(err, c, detail::path_state_cstr(req_state_));
154  }
155  break;
156  case State::NotDir:
157  if (detail::is_directory(p)) {
158  return this->set_error(err, c, detail::path_state_cstr(req_state_));
159  }
160  break;
161  default:
162  break;
163  }
164 
165  return true;
166 }
167 
168 template <typename T>
169 typename Path<T>::Type Path<T>::deserialize(const Conf& c) const {
170  auto s = c.get<std::string>();
171  if (interpolate_) {
172  s = interpolate_vars(s, env_);
173  }
174  Type p{s};
175  if (resolve_) {
176  p = resolve_path(c, p);
177  }
178  if (normalize_) {
179  p = detail::canonical(p);
180  }
181  return p;
182 }
183 
184 template <typename T>
185 typename Path<T>::Type Path<T>::resolve_path(const Conf& c, const Path<T>::Type& filepath) const {
186  std::string filepath_str = filepath.generic_string();
187 
188  // Only resolve executables if the path is not a basename, otherwise we
189  // let the search_path do the resolving.
190  if (req_state_ == State::Executable && filepath_str.find('/') == std::string::npos) {
191  auto result = detail::search_path(filepath);
192  if (!result) {
193  throw this->error(c, "expect executable to exist: {}", filepath.native());
194  }
195  return *result;
196  } else {
197  return c.resolve_file(filepath_str);
198  }
199 }
200 
201 } // namespace fable::schema
Definition: conf.hpp:82
T get() const
Definition: conf.hpp:298
Definition: error.hpp:149
Definition: path.hpp:89
bool validate(const Conf &c, std::optional< SchemaError > &err) const override
Definition: path_impl.hpp:89
Json json_schema() const override
Definition: path_impl.hpp:66
nlohmann::json Json
Definition: fable_fwd.hpp:35