diff --git a/include/boost/url/impl/params_encoded_base.hpp b/include/boost/url/impl/params_encoded_base.hpp index 5ff1e0dd1..c220bfe7d 100644 --- a/include/boost/url/impl/params_encoded_base.hpp +++ b/include/boost/url/impl/params_encoded_base.hpp @@ -130,6 +130,27 @@ contains( begin().it_, key, ic) != end(); } +inline +pct_string_view +params_encoded_base:: +get_or( + pct_string_view key, + pct_string_view value, + ignore_case_param ic) const noexcept +{ + auto it = find_impl( + begin().it_, key, ic); + detail::params_iter_impl end_(ref_, 0); + if(it.equal(end_)) + return value; + + param_pct_view const p = it.dereference(); + if(! p.has_value) + return pct_string_view(); + + return p.value; +} + inline auto params_encoded_base:: diff --git a/include/boost/url/params_base.hpp b/include/boost/url/params_base.hpp index e972d4273..6553da5d5 100644 --- a/include/boost/url/params_base.hpp +++ b/include/boost/url/params_base.hpp @@ -29,11 +29,12 @@ namespace urls { # pragma warning(disable: 4251) #endif -/** Common functionality for query parameter containers +/** Decoded query parameter helper base - The library uses this base class - to provide common member functions for - containers of query parameters. + This base centralizes the read-only, + percent-decoded query parameter algorithms + (iteration, lookup, counting) that are + shared by @ref params_view and @ref params_ref. This class should not be instantiated directly; Instead, use one of the @@ -319,6 +320,45 @@ class BOOST_URL_DECL params_base core::string_view key, ignore_case_param ic = {}) const noexcept; + /** Return the value for a key or a fallback + + This convenience function searches for the + first parameter matching `key` and returns + its decoded value. If no parameter with the + specified key exists, the provided fallback + `value` is returned instead. When the key is + found but the parameter has no value, an + empty string is returned. + + @par Example + @code + url_view u( "/path?first=John&last=Doe" ); + assert( u.params().get_or( "first", "n/a" ) == "John" ); + assert( u.params().get_or( "missing", "n/a" ) == "n/a" ); + @endcode + + @par Complexity + Linear in `this->buffer().size()`. + + @par Exception Safety + Calls to allocate may throw. + + @param key The key to match. + @param value The fallback string returned + when no matching key exists. If this + parameter is omitted, an empty string is + used. + @param ic Optional case-insensitive compare + indicator. + + @return The decoded value or the fallback. + */ + std::string + get_or( + core::string_view key, + core::string_view value = {}, + ignore_case_param ic = {}) const; + /** Find a matching key This function examines the parameters diff --git a/include/boost/url/params_encoded_base.hpp b/include/boost/url/params_encoded_base.hpp index 3e26cb4ec..98cd67a17 100644 --- a/include/boost/url/params_encoded_base.hpp +++ b/include/boost/url/params_encoded_base.hpp @@ -21,13 +21,15 @@ namespace boost { namespace urls { -/** Common functionality for containers +/** Percent-encoded query helper base - This base class is used by the library - to provide common member functions for - containers. This cannot be instantiated - directly; Instead, use one of the - containers or functions: + This base implements the shared + percent-encoded query parameter view + functionality used by @ref params_encoded_view + and @ref params_encoded_ref. It cannot be + instantiated directly; instead, use one of + those containers or the corresponding ref + adaptors. @par Containers @li @ref params_ref @@ -319,6 +321,46 @@ class BOOST_URL_DECL params_encoded_base pct_string_view key, ignore_case_param ic = {}) const noexcept; + /** Return the value for a key or a fallback + + This convenience function searches for the + first parameter matching `key` and returns + its percent-encoded value. If no parameter + with the specified key exists, the provided + fallback `value` is returned instead. When + the key is found but the corresponding + parameter has no value, an empty string is + returned. + + @par Example + @code + url_view u( "/path?first=John&last=Doe" ); + assert( u.encoded_params().get_or( + "missing", "n%2Fa" ) == "n%2Fa" ); + @endcode + + @par Complexity + Linear in `this->buffer().size()`. + + @par Exception Safety + Throws nothing. + + @param key The key to match. + @param value The fallback string returned + when no matching key exists. If this + parameter is omitted, an empty string is + used. + @param ic Optional case-insensitive compare + indicator. + + @return The encoded value or the fallback. + */ + pct_string_view + get_or( + pct_string_view key, + pct_string_view value = {}, + ignore_case_param ic = {}) const noexcept; + /** Find a matching key This function examines the parameters diff --git a/include/boost/url/params_encoded_ref.hpp b/include/boost/url/params_encoded_ref.hpp index 67cb6a403..4ac8d230a 100644 --- a/include/boost/url/params_encoded_ref.hpp +++ b/include/boost/url/params_encoded_ref.hpp @@ -24,21 +24,14 @@ class url_base; class params_encoded_view; #endif -/** A view representing query parameters in a URL +/** Mutable encoded query parameter proxy - Objects of this type are used to interpret - the query parameters as a bidirectional view - of key value pairs. - - The view does not retain ownership of the - elements and instead references the original - url. The caller is responsible for ensuring - that the lifetime of the referenced url - extends until it is no longer referenced. - - The view is modifiable; calling non-const - members causes changes to the referenced - url. + This container exposes the percent-encoded + query parameters of a @ref url_base as a + bidirectional range while allowing mutation + of the underlying URL. It references the + URL’s buffer directly, so the url must stay + alive for the lifetime of the proxy. @par Example @code @@ -69,6 +62,17 @@ class params_encoded_view; @li @ref replace, @ref set : Modified params and all params after (including `end()`). + + @par Reads vs. writes + Even though this type can be used to mutate + the referenced URL, this is still a proxy + and every observer function inherited + from @ref params_encoded_base (for example + @ref contains, @ref find, and @ref get_or) + behaves like the corresponding function on + @ref params_encoded_view: it inspects the + current encoded query and does not perform + any modifications. */ class BOOST_URL_DECL params_encoded_ref : public params_encoded_base diff --git a/include/boost/url/params_encoded_view.hpp b/include/boost/url/params_encoded_view.hpp index 624dba84f..ec553e862 100644 --- a/include/boost/url/params_encoded_view.hpp +++ b/include/boost/url/params_encoded_view.hpp @@ -26,17 +26,13 @@ namespace implementation_defined { struct query_rule_t; } -/** A view representing query parameters in a URL +/** Non-owning encoded query parameter view - Objects of this type are used to interpret - the query parameters as a bidirectional view - of key/value pairs. - - The view does not retain ownership of the - elements and instead references the original - character buffer. The caller is responsible - for ensuring that the lifetime of the buffer - extends until it is no longer referenced. + This read-only range exposes the raw + percent-encoded key/value pairs stored in a + query string. It does not copy the underlying + buffer; callers must ensure the referenced + character storage outlives the view. @par Example @code @@ -45,11 +41,9 @@ namespace implementation_defined { params_encoded_view p = u.encoded_params(); @endcode - Strings produced when elements are returned - have type @ref param_pct_view and represent - encoded strings. Strings passed to member - functions may contain percent escapes, and - throw exceptions on invalid inputs. + Iteration yields @ref param_pct_view values, + so encoded strings and escape validation are + preserved for callers that want exact bytes. @par Iterator Invalidation Changes to the underlying character buffer diff --git a/include/boost/url/params_ref.hpp b/include/boost/url/params_ref.hpp index de2e53361..5ce4d5bc1 100644 --- a/include/boost/url/params_ref.hpp +++ b/include/boost/url/params_ref.hpp @@ -25,19 +25,15 @@ class url_base; class params_view; #endif -/** A view representing query parameters in a URL - - Objects of this type are used to interpret - the query parameters as a bidirectional view - of key/value pairs. - The view does not retain ownership of the - elements and instead references the original - url. The caller is responsible for ensuring - that the lifetime of the referenced url - extends until it is no longer referenced. - The view is modifiable; calling non-const - members causes changes to the referenced - url. +/** Mutable decoded query parameter proxy + + This container presents the decoded query + parameters of a @ref url_base as a bidirectional + range whose modifying operations update the + underlying URL in-place. It references (but + does not own) the character buffer, so the + referenced URL must stay alive while the proxy + is used.
@@ -76,6 +72,16 @@ class params_view; @li @ref replace, @ref set : Modified elements and all elements after (including `end()`). + + @par Reads vs. writes + Although this is a mutable proxy, all + observer helpers inherited from + @ref params_base (such as @ref contains, + @ref find, and @ref get_or) operate in the + same way as they do on @ref params_view: + they perform their lookup against the + current contents of the referenced URL + without modifying it. */ class BOOST_URL_DECL params_ref : public params_base diff --git a/include/boost/url/params_view.hpp b/include/boost/url/params_view.hpp index fb3d2b782..4ff9f63b8 100644 --- a/include/boost/url/params_view.hpp +++ b/include/boost/url/params_view.hpp @@ -17,17 +17,14 @@ namespace boost { namespace urls { -/** A view representing query parameters in a URL +/** Non-owning decoded query parameter view - Objects of this type are used to interpret - the query parameters as a bidirectional view - of key/value pairs. - - The view does not retain ownership of the - elements and instead references the original - character buffer. The caller is responsible - for ensuring that the lifetime of the buffer - extends until it is no longer referenced. + This read-only range interprets the query + string of a URL as bidirectional key/value + pairs with percent-decoding applied on + access. It merely references the original + character buffer; callers must keep that + buffer alive while the view is used. @par Example @code @@ -36,9 +33,8 @@ namespace urls { params_view p = u.params(); @endcode - Percent escapes in strings returned when - dereferencing iterators are automatically - decoded. + Strings retrieved from the iterators are + automatically percent-decoded. @par Iterator Invalidation Changes to the underlying character buffer diff --git a/include/boost/url/segments_base.hpp b/include/boost/url/segments_base.hpp index 3c50ba6ae..779513536 100644 --- a/include/boost/url/segments_base.hpp +++ b/include/boost/url/segments_base.hpp @@ -20,13 +20,14 @@ namespace boost { namespace urls { -/** Common functionality for containers - - This base class is used by the library - to provide common member functions for - containers. This cannot be instantiated - directly; Instead, use one of the - containers or functions: +/** Decoded path segment helper base + + Provides the shared decoded path-segment + algorithms (iteration, lookup, comparison) + used by @ref segments_view and + @ref segments_ref. This base cannot be + instantiated directly; instead, use one of + the concrete containers below. @par Containers @li @ref segments_ref diff --git a/include/boost/url/segments_encoded_base.hpp b/include/boost/url/segments_encoded_base.hpp index 7390c00cc..d181a121b 100644 --- a/include/boost/url/segments_encoded_base.hpp +++ b/include/boost/url/segments_encoded_base.hpp @@ -20,13 +20,13 @@ namespace boost { namespace urls { -/** Common functionality for containers +/** Percent-encoded path segment helper base - This base class is used by the library - to provide common member functions for - containers. This cannot be instantiated - directly; Instead, use one of the - containers or functions: + Implements the shared encoded-segment + algorithms reused by @ref segments_encoded_view + and @ref segments_encoded_ref. It is not + intended to be instantiated directly; use + one of those concrete containers instead. @par Containers @li @ref segments_ref diff --git a/include/boost/url/segments_encoded_ref.hpp b/include/boost/url/segments_encoded_ref.hpp index 7e35fb6af..30abc4633 100644 --- a/include/boost/url/segments_encoded_ref.hpp +++ b/include/boost/url/segments_encoded_ref.hpp @@ -24,22 +24,13 @@ class url_base; class segments_encoded_view; #endif -/** A view representing path segments in a URL - - Objects of this type are used to interpret - the path as a bidirectional view of segments, - where each segment is a string which may - contain percent-escapes. - - The view does not retain ownership of the - elements and instead references the original - character buffer. The caller is responsible - for ensuring that the lifetime of the buffer - extends until it is no longer referenced. - - The view is modifiable; calling non-const - members causes changes to the referenced - url. +/** Mutable encoded path segment proxy + + Exposes the percent-encoded segments of a + @ref url_base as a bidirectional range that + can modify the underlying URL. The proxy uses + the URL’s storage directly, so the url must + outlive any references. @par Example @code diff --git a/include/boost/url/segments_encoded_view.hpp b/include/boost/url/segments_encoded_view.hpp index 847fc553c..db1ebed0a 100644 --- a/include/boost/url/segments_encoded_view.hpp +++ b/include/boost/url/segments_encoded_view.hpp @@ -22,17 +22,13 @@ namespace boost { namespace urls { -/** A view representing path segments in a URL +/** Non-owning encoded path segment view - Objects of this type are used to interpret - the path as a bidirectional view of segment - strings. - - The view does not retain ownership of the - elements and instead references the original - character buffer. The caller is responsible - for ensuring that the lifetime of the buffer - extends until it is no longer referenced. + Exposes the raw percent-encoded segments of + a URL path as a read-only bidirectional range. + The view references the original buffer, so + callers must keep that storage alive while + iterating. @par Example @code @@ -43,11 +39,9 @@ namespace urls { assert( ps.buffer().data() == u.buffer().data() ); @endcode - Strings produced when elements are returned - have type @ref param_pct_view and represent - encoded strings. Strings passed to member - functions may contain percent escapes, and - throw exceptions on invalid inputs. + Elements are returned as encoded strings, + preserving escape sequences for callers that + need the exact byte representation. @par Iterator Invalidation Changes to the underlying character buffer diff --git a/include/boost/url/segments_ref.hpp b/include/boost/url/segments_ref.hpp index 8c26f185c..d9dbb3312 100644 --- a/include/boost/url/segments_ref.hpp +++ b/include/boost/url/segments_ref.hpp @@ -24,22 +24,14 @@ class url_base; class segments_view; #endif -/** A view representing path segments in a URL - - Objects of this type are used to interpret - the path as a bidirectional view of segments, - where each segment is a string with percent - escapes automatically decoded. - - The view does not retain ownership of the - elements and instead references the original - character buffer. The caller is responsible - for ensuring that the lifetime of the buffer - extends until it is no longer referenced. - - The view is modifiable; calling non-const - members causes changes to the referenced - url. +/** Mutable decoded path segment proxy + + Presents the decoded path segments of a + @ref url_base as a bidirectional range whose + modifiers update the underlying URL. The proxy + references the URL’s storage directly, so the + owning URL must remain alive while the proxy + is used. @par Example @code diff --git a/include/boost/url/segments_view.hpp b/include/boost/url/segments_view.hpp index c8560dca7..77fb2a80d 100644 --- a/include/boost/url/segments_view.hpp +++ b/include/boost/url/segments_view.hpp @@ -18,17 +18,13 @@ namespace boost { namespace urls { -/** A view representing path segments in a URL +/** Non-owning decoded path segment view - Objects of this type are used to interpret - the path as a bidirectional view of segment - strings. - - The view does not retain ownership of the - elements and instead references the original - character buffer. The caller is responsible - for ensuring that the lifetime of the buffer - extends until it is no longer referenced. + Presents the path of a URL as a read-only + bidirectional range of percent-decoded + segments. The range references the original + buffer, so callers must keep that storage + alive for as long as the view is accessed. @par Example @code @@ -39,9 +35,8 @@ namespace urls { assert( ps.buffer().data() == u.buffer().data() ); @endcode - Percent escapes in strings returned when - dereferencing iterators are automatically - decoded. + Any percent-escapes are decoded on demand + when iterators are dereferenced. @par Iterator Invalidation Changes to the underlying character buffer diff --git a/src/params_base.cpp b/src/params_base.cpp index ca1532ea5..a27d58be0 100644 --- a/src/params_base.cpp +++ b/src/params_base.cpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace boost { namespace urls { @@ -203,6 +204,27 @@ count( return n; } +std::string +params_base:: +get_or( + core::string_view key, + core::string_view value, + ignore_case_param ic) const +{ + auto it = find_impl( + begin().it_, key, ic); + detail::params_iter_impl end_(ref_, 0); + if(it.equal(end_)) + return std::string(value); + + param_pct_view const p = it.dereference(); + if(! p.has_value) + return std::string(); + + auto opt = opt_; + return p.value.decode(opt); +} + //------------------------------------------------ // // (implementation) @@ -282,4 +304,3 @@ operator<<( } // urls } // boost - diff --git a/test/unit/params_base.cpp b/test/unit/params_base.cpp index 82a0e2da6..97d320fa5 100644 --- a/test/unit/params_base.cpp +++ b/test/unit/params_base.cpp @@ -283,6 +283,32 @@ struct params_base_test BOOST_TEST(p.contains("F", ignore_case)); BOOST_TEST(! p.contains("G", ignore_case)); } + + // get_or() + { + url_view u( + "?first=John&empty=&novalue&encoded=John%20Doe"); + params_view p = u.params(); + + BOOST_TEST_EQ(p.get_or("first", "n/a"), "John"); + BOOST_TEST_EQ(p.get_or("encoded", "n/a"), "John Doe"); + BOOST_TEST_EQ(p.get_or("missing", "n/a"), "n/a"); + BOOST_TEST_EQ(p.get_or("novalue", "fallback"), ""); + BOOST_TEST_EQ(p.get_or("empty", "fallback"), ""); + BOOST_TEST_EQ( + p.get_or("FIRST", "n/a", ignore_case), "John"); + } + + // javadoc + { + url_view u("/path?first=John&last=Doe"); + BOOST_TEST_EQ( + u.params().get_or("first", "n/a"), + "John"); + BOOST_TEST_EQ( + u.params().get_or("missing", "n/a"), + "n/a"); + } } void diff --git a/test/unit/params_encoded_base.cpp b/test/unit/params_encoded_base.cpp index 6e354ac01..d1534d82a 100644 --- a/test/unit/params_encoded_base.cpp +++ b/test/unit/params_encoded_base.cpp @@ -283,6 +283,30 @@ struct params_encoded_base_test BOOST_TEST(p.contains("F", ignore_case)); BOOST_TEST(! p.contains("G", ignore_case)); } + + // get_or() + { + url_view u( + "?first=John&empty=&novalue&encoded=John%20Doe"); + params_encoded_view p = u.encoded_params(); + + BOOST_TEST_EQ(p.get_or("first", "n%2Fa"), "John"); + BOOST_TEST_EQ(p.get_or("encoded", "n%2Fa"), "John%20Doe"); + BOOST_TEST_EQ(p.get_or("missing", "n%2Fa"), "n%2Fa"); + BOOST_TEST_EQ(p.get_or("novalue", "fallback"), ""); + BOOST_TEST_EQ(p.get_or("empty", "fallback"), ""); + BOOST_TEST_EQ( + p.get_or("FIRST", "n%2Fa", ignore_case), "John"); + } + + // javadoc + { + url_view u("/path?first=John&last=Doe"); + BOOST_TEST_EQ( + u.encoded_params().get_or( + "missing", "n%2Fa"), + "n%2Fa"); + } } void