11[[nodiscard]] Result<Uri> uriFailure(std::string
message, std::string_view
path = {}) {
15[[nodiscard]] Result<ResourceUri> resourceUriFailure(std::string
message, std::string_view
path = {}) {
20[[nodiscard]]
bool isSchemeCharacter(
char value,
bool first)
noexcept {
22 const bool digit =
value >=
'0' &&
value <=
'9';
26[[nodiscard]]
bool isPathCharacter(
char value)
noexcept {
27 const auto byte =
static_cast<unsigned char>(
value);
28 if (byte < 0x21 || byte > 0x7e)
return false;
36 case '@':
return true;
37 default:
return std::isalnum(
byte) != 0;
41[[nodiscard]]
bool isQueryOrFragmentCharacter(
char value)
noexcept {
42 const auto byte =
static_cast<unsigned char>(
value);
43 if (byte < 0x21 || byte > 0x7e)
return false;
47[[nodiscard]] std::optional<UriScheme> schemeFromText(std::string_view scheme)
noexcept {
56[[nodiscard]]
bool isKnownScheme(
UriScheme scheme)
noexcept {
68 if (
path.empty() ||
path.front() ==
'/' ||
path.back() ==
'/')
return false;
70 std::size_t segmentStart = 0;
71 for (std::size_t i = 0; i <
path.size(); ++i) {
75 const auto segment =
path.substr(segmentStart, i - segmentStart);
76 if (segment ==
"." || segment ==
".." || segment.empty())
return false;
81 const auto lastSegment =
path.substr(segmentStart);
82 return lastSegment !=
"." && lastSegment !=
".." && !lastSegment.empty();
85[[nodiscard]]
bool validQueryOrFragment(std::string_view
value)
noexcept {
86 if (
value.empty())
return false;
87 return std::all_of(
value.begin(),
value.end(), isQueryOrFragmentCharacter);
90[[nodiscard]] Result<Uri> parseUri(std::string_view
text) {
91 const auto delimiter =
text.find(
"://");
92 if (delimiter == std::string_view::npos || delimiter == 0) {
93 return uriFailure(
"resource URI must use scheme://path and cannot be a bare path",
text);
95 for (std::size_t i = 0; i < delimiter; ++i) {
96 if (!isSchemeCharacter(
text[i], i == 0)) {
97 return uriFailure(
"resource URI contains an invalid scheme",
text);
101 std::string scheme(
text.substr(0, delimiter));
102 std::transform(scheme.begin(), scheme.end(), scheme.begin(),
103 [](
unsigned char value) { return static_cast<char>(std::tolower(value)); });
104 const auto schemeKind = schemeFromText(scheme);
105 if (!schemeKind)
return uriFailure(
"unsupported resource URI scheme",
text);
107 std::string_view remainder =
text.substr(delimiter + 3);
108 const auto fragmentDelimiter = remainder.find(
'#');
109 std::string_view fragment;
110 if (fragmentDelimiter != std::string_view::npos) {
111 fragment = remainder.substr(fragmentDelimiter + 1);
112 remainder = remainder.substr(0, fragmentDelimiter);
113 if (!validQueryOrFragment(fragment))
return uriFailure(
"URI fragment is empty or invalid",
text);
116 const auto queryDelimiter = remainder.find(
'?');
117 std::string_view
query;
118 if (queryDelimiter != std::string_view::npos) {
119 query = remainder.substr(queryDelimiter + 1);
120 remainder = remainder.substr(0, queryDelimiter);
121 if (!validQueryOrFragment(query))
return uriFailure(
"URI query is empty or invalid",
text);
123 if (!
validPath(remainder))
return uriFailure(
"URI path is empty, absolute, or invalid",
text);
141Uri::Uri(
UriScheme scheme, std::string
path, std::string query, std::string fragment)
142 : scheme_(scheme), path_(
std::move(
path)), query_(
std::move(query)), fragment_(
std::move(fragment)) {
143 formatted_.reserve(3 + path_.size() + query_.size() + fragment_.size() + 16);
147 if (!query_.empty()) {
149 formatted_ += query_;
151 if (!fragment_.empty()) {
153 formatted_ += fragment_;
160 if (!isKnownScheme(scheme))
return uriFailure(
"unknown resource URI scheme",
path);
161 if (!validPath(
path))
return uriFailure(
"URI path is empty, absolute, or invalid",
path);
162 if ((!query.empty() && !validQueryOrFragment(query)) || (!fragment.empty() && !validQueryOrFragment(fragment))) {
163 return uriFailure(
"URI query or fragment is invalid",
path);
169 auto parsed = Uri::parse(
text);
171 const auto* diagnostic = parsed.error();
172 return resourceUriFailure(diagnostic ? diagnostic->message() :
"invalid resource URI",
text);
178 std::string_view fragment) {
179 auto parsed = Uri::fromParts(scheme,
path, query, fragment);
181 const auto* diagnostic = parsed.error();
182 return resourceUriFailure(diagnostic ? diagnostic->message() :
"invalid resource URI",
path);
188 if (
path.find(
"://") != std::string_view::npos) {
189 return resourceUriFailure(
"legacy path facade does not reinterpret URI-looking input",
path);
191 std::string normalized(
path);
192 std::replace(normalized.begin(), normalized.end(),
'\\',
'/');
193 while (normalized.size() >= 2 && normalized[0] ==
'.' && normalized[1] ==
'/') {
194 normalized.erase(0, 2);
196 return fromParts(UriScheme::Project, normalized);
graphics::Canvas * previous
Strict, canonical resource URI values shared by engine modules.
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
A semantically named resource locator.
Move-only operation result carrying either a value or Status.
static Result failure(Status status)
Construct a failed result from a structured status.
An immutable, canonical URI value.
static Result< Uri > fromParts(UriScheme scheme, std::string_view path, std::string_view query={}, std::string_view fragment={})
Construct a URI from validated components.
bool validPath(std::string_view path, const AssetImportLimits &limits)
Valid path.
Build metadata (engine git commit, build time, third-party version).
UriScheme
URI schemes understood by the resource layer.
const char * uriSchemeName(UriScheme scheme) noexcept
Return the stable lower-case spelling of a resource URI scheme.