$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  // XXX: Fix throw here
97  src = interpolate_vars(src, env_);
98  }
99  if (src.size() < min_length_) {
100  return this->set_error(err, c, "expect minimum path length of {}, got {}", min_length_,
101  src.size());
102  }
103  if (src.size() > max_length_) {
104  return this->set_error(err, c, "expect maximum path length of {}, got {}", max_length_,
105  src.size());
106  }
107  if (!pattern_.empty() && !std::regex_match(src, std::regex(pattern_))) {
108  return this->set_error(err, c, "expect path to match regex '{}': {}", pattern_, src);
109  }
110 
111  Type p{src};
112  if (req_abs_ && !p.is_absolute()) {
113  return this->set_error(err, c, "expect path to be absolute: {}", src);
114  }
115  if (resolve_) {
116  try {
117  p = resolve_path(c, p);
118  } catch (SchemaError& e) {
119  err.emplace(std::move(e));
120  return false;
121  }
122  }
123 
124  switch (req_state_) {
125  case State::Absent:
126  if (detail::exists(p)) {
127  return this->set_error(err, c, detail::path_state_cstr(req_state_));
128  }
129  break;
130  case State::Exists:
131  if (!detail::exists(p)) {
132  return this->set_error(err, c, detail::path_state_cstr(req_state_));
133  }
134  break;
135  case State::Executable:
136  // fallthrough
137  case State::FileExists:
138  if (!detail::is_regular_file(p)) {
139  return this->set_error(err, c, detail::path_state_cstr(req_state_));
140  }
141  break;
142  case State::DirExists:
143  if (!detail::is_directory(p)) {
144  return this->set_error(err, c, detail::path_state_cstr(req_state_));
145  }
146  break;
147  case State::NotFile:
148  if (detail::is_regular_file(p) || detail::is_other(p)) {
149  return this->set_error(err, c, detail::path_state_cstr(req_state_));
150  }
151  break;
152  case State::NotDir:
153  if (detail::is_directory(p)) {
154  return this->set_error(err, c, detail::path_state_cstr(req_state_));
155  }
156  break;
157  default:
158  break;
159  }
160 
161  return true;
162 }
163 
164 template <typename T>
165 typename Path<T>::Type Path<T>::deserialize(const Conf& c) const {
166  auto s = c.get<std::string>();
167  if (interpolate_) {
168  s = interpolate_vars(s, env_);
169  }
170  Type p{s};
171  if (resolve_) {
172  p = resolve_path(c, p);
173  }
174  if (normalize_) {
175  p = detail::canonical(p);
176  }
177  return p;
178 }
179 
180 template <typename T>
181 typename Path<T>::Type Path<T>::resolve_path(const Conf& c, const Path<T>::Type& filepath) const {
182  std::string filepath_str = filepath.generic_string();
183 
184  // Only resolve executables if the path is not a basename, otherwise we
185  // let the search_path do the resolving.
186  if (req_state_ == State::Executable && filepath_str.find('/') == std::string::npos) {
187  auto result = detail::search_path(filepath);
188  if (!result) {
189  throw this->error(c, "expect executable to exist: {}", filepath.native());
190  }
191  return *result;
192  } else {
193  return c.resolve_file(filepath_str);
194  }
195 }
196 
197 } // namespace fable::schema
Definition: conf.hpp:76
T get() const
Definition: conf.hpp:166
Definition: error.hpp:130
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