// Copyright 2014-2023 Bloomberg Finance L.P.
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// bmqt_uri.h                                                         -*-C++-*-
#ifndef INCLUDED_BMQT_URI
#define INCLUDED_BMQT_URI

//@PURPOSE: Provide value-semantic type and utilities for a BlazingMQ queue
//URI.
//
//@CLASSES:
//  bmqt::Uri        : value-semantic type representing a URI
//  bmqt::UriParser  : utility to parse a string into a URI
//  bmqt::UriBuilder : builder mechanism to create a URI
//
//@DESCRIPTION: This component provides a value-semantic type, 'bmqt::Uri'
// representing a URI for a BlazingMQ queue.  A 'bmqt:Uri' can be built by
// parsing from a string representation, using the 'bmqt::UriParser', or built
// using the 'bmqt::UriBuilder'.
//
//@SEE_ALSO:
//----------
// https://tools.ietf.org/html/rfc3986
//
///URI format
///----------
// In a nutshell, a URI representing an application queue managed by a
// BlazingMQ broker on a given machine looks like one of the following:
//..
//  bmq://ts.trades.myapp/my.queue
//  bmq://ts.trades.myapp.~bt/my.queue
//  bmq://ts.trades.myapp/my.queue?id=foo
//..
// where:
//
//: o The URI scheme is always "bmq".
//
//: o The URI authority is the name of BlazingMQ domain (such as
//    "ts.trades.myapp")
//:   as registered with the BlazingMQ infrastructure.  The domain name may
//:   contain alphanumeric characters, dots and dashes (it has to match the
//:   following regular expression: '[-a-zA-Z0-9\\._]+').  The domain may be
//:   followed by an optional tier, introduced by the ".~" sequence and
//:   consisting of alphanumeric characters and dashes.  The ".~" sequence is
//:   not part of the tier.
//
//: o The URI path is the name of the queue ("my.queue" above) and may contain
//:   alphanumeric characters, dashes, underscores and tild (it has to match
//:   the following regular expression: '[-a-zA-Z0-9_~\\.]+').  The queue name
//:   must be less than 'bmqt::Uri::k_QUEUENAME_MAX_LENGTH' characters long.
//
//: o The name of the queue ("my.queue" above) may contain alphanumeric
//:   characters and dots.
//
//: o The URI may contain an optional query with a key-value pair.  Currently
//:   supported keys are:
//:   o !id!: the corresponding value represents a name that will be used by
//:     BlazingMQ broker to uniquely identify the client.
//
//: o The URI fragment part is currently unused.
//
///Usage Example 1
///---------------
// First, call the 'initialize' method of the 'UriParser'.  This call is only
// needed one time; you can call it when your task starts.
//
// Note that the 'bmq' library takes care of that, so users of 'bmq' don't
// have to explicitly do it themselves.
//..
//  bmqt::UriParser::initialize();
//..
// Then, parse a URI string created on the stack to populate a 'Uri' object.
// The parse function takes an optional error string which is populated with a
// short error message if the URI is not formatted correctly.
//..
//   bsl::string input = "bmq://my.domain/queue";
//   bmqt::Uri   uri(allocator);
//   bsl::string errorDescription;
//
//   int         rc = bmqt::UriParser::parse(&uri, &errorDescription, input);
//   if (rc != 0) {
//      BALL_LOG_ERROR << "Invalid URI [error: " << errorDescription << "]";
//   }
//   assert(rc == 0);
//   assert(error == "");
//   assert(uri.scheme()    == "bmq");
//   assert(uri.domain()    == "my.domain");
//   assert(uri.queue()     == "queue");
//..
//
/// Usage Example 2
///----------------
// Instantiate a 'Uri' object with a string representation of the URI and an
// allocator.
//..
//  bmqt::Uri uri("bmq://my.domain/queue", allocator);
//  assert(uri.scheme() == "bmq");
//  assert(uri.domain() == "my.domain");
//  assert(uri.queue()  == "queue");
//..

///Thread Safety
///-------------
//: o 'bmqt::UriBuilder' is NOT thread safe
//: o 'bmqt::UriParser' should be thread safe: the component depends on
//:   'bdepcre_regex' that is a wrapper around "pcre.h".  See
//:    http://www.pcre.org/pcre.txt.


// BMQ
#include <bmqscm_version.h>

// BDE
#include <ball_log.h>
#include <bsl_cstddef.h>
#include <bsl_iosfwd.h>
#include <bsl_string.h>
#include <bslh_hash.h>
#include <bslma_usesbslmaallocator.h>
#include <bslmf_nestedtraitdeclaration.h>


namespace BloombergLP {

// FORWARD DECLARATION
namespace bmqt { class UriBuilder; }
namespace bmqt { struct UriParser; }

namespace bmqt {


                                 // =========
                                 // class Uri
                                 // =========

class Uri {
    // Value semantic type representing a URI

  public:
    // PUBLIC CONSTANTS
    static const int k_QUEUENAME_MAX_LENGTH = 64;
        // The maximum authorized length for the queue name part of the URI.

  private:
    // CLASS-SCOPE CATEGORY
    BALL_LOG_SET_CLASS_CATEGORY("BMQT.URI");

  private:
    // FRIENDS
    friend struct UriParser;
    friend class UriBuilder;
    template <class HASH_ALGORITHM>
    friend void hashAppend(HASH_ALGORITHM& hashAlgo, const Uri& uri);

  private:
    // DATA
    bsl::string       d_uri;            // The full URI

    bslstl::StringRef d_scheme;         // URI scheme (must be "bmq").

    bslstl::StringRef d_authority;      // URI authority (domain + optional
                                        // tier)

    bslstl::StringRef d_domain;         // URI domain

    bslstl::StringRef d_tier;           // URI tier

    bslstl::StringRef d_path;           // URI path (i.e queue name).

    bslstl::StringRef d_query_id;       // Optional application id, part of the
                                        // URI query if present.

    bool              d_wasParserInitialized;
                                        // Flag indicating whether the URI
                                        // parser was initialized (and whether
                                        // shutdown should be called on it at
                                        // destruction)

  private:
    // PRIVATE MANIPULATORS
    void reset();
        // Reset this object to the default value.

    void copyImpl(const Uri& src);
        // Implementation of the copy of the specified 'src' URI into this
        // object.

  public:
    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(Uri, bslma::UsesBslmaAllocator)

    // CREATORS
    explicit Uri(bslma::Allocator *allocator = 0);
        // Constructor of an invalid Uri with all fields empty, using the
        // optionally specified 'allocator'.

    Uri(const Uri&        original,
        bslma::Allocator *allocator = 0); // IMPLICIT
        // Copy constructor, create a new Uri having the same values as the
        // specified 'original', and using the optionally specified
        // 'allocator'.

    Uri(const bsl::string&  uri,
        bslma::Allocator   *allocator = 0);                         // IMPLICIT
    Uri(const bslstl::StringRef&  uri,
        bslma::Allocator         *allocator = 0);                   // IMPLICIT
    Uri(const char       *uri,
        bslma::Allocator *allocator = 0);                           // IMPLICIT
        // Implicit constructor of this object from the specified 'uri' string
        // using the optionally specified 'allocator'.  If the 'uri' input
        // string doesn't not represent a valid URI, this object is left in an
        // invalid state (isValid() will return false).

    ~Uri();
        // Destructor.

    // MANIPULATORS
    Uri& operator=(const Uri& rhs);
        // Set the value of this object to the specified 'rhs'.

    // ACCESSORS
    const bsl::string& asString() const;
        // Return the string representation of this URI.

    bool isValid() const;
        // Return true if this object represents a valid URI.

    bool isCanonical() const;
        // Return true if this object represents a canonical URI.

    const bslstl::StringRef& scheme() const;
    const bslstl::StringRef& authority() const;
    const bslstl::StringRef& path() const;
        // Return the corresponding (raw) part of the URI, matching to the URI
        // RFC terminology.

    const bslstl::StringRef& qualifiedDomain() const;
    const bslstl::StringRef& domain() const;
    const bslstl::StringRef& tier() const;
    const bslstl::StringRef& queue() const;
    const bslstl::StringRef& id() const;
        // Return the corresponding (extracted) part of the URI, provided as
        // convenient accessors using the BlazingMQ terminology.

    bslstl::StringRef canonical() const;
        // Return the canonical form of the URI. Note that canonical form
        // includes everything except the query part of the URI.

    bsl::ostream& print(bsl::ostream& stream,
                        int           level          = 0,
                        int           spacesPerLevel = 4) const;
        // Format this object to the specified output 'stream' at the (absolute
        // value of) the optionally specified indentation 'level' and return a
        // reference to 'stream'.  If 'level' is specified, optionally specify
        // 'spacesPerLevel', the number of spaces per indentation level for
        // this and all of its nested objects.  If 'level' is negative,
        // suppress indentation of the first line.  If 'spacesPerLevel' is
        // negative format the entire output on one line, suppressing all but
        // the initial indentation (as governed by 'level').  If 'stream' is
        // not valid on entry, this operation has no effect.
};

// FREE OPERATORS
bool operator==(const Uri& lhs,
                const Uri& rhs);
    // Return 'true' if the specified 'rhs' object contains the value of the
    // same type as contained in the specified 'lhs' object and the value
    // itself is the same in both objects, return false otherwise.

bool operator!=(const Uri& lhs,
                const Uri& rhs);
    // Return 'false' if the specified 'rhs' object contains the value of the
    // same type as contained in the specified 'lhs' object and the value
    // itself is the same in both objects, return 'true' otherwise.

bool operator<(const Uri& lhs,
               const Uri& rhs);
    // Return 'true' if the specified 'lhs' object compares less than the
    // specified 'rhs' object.


bsl::ostream& operator<<(bsl::ostream& stream,
                         const Uri&    rhs);
    // Format the specified 'rhs' to the specified output 'stream' and return
    // a reference to the modifiable 'stream'.


                              // ================
                              // struct UriParser
                              // ================

struct UriParser {
    // Utility namespace of methods for parsing URI strings into 'Uri' objects.

    // CLASS METHODS
    static void initialize(bslma::Allocator *allocator = 0);
        // Initialize the 'UriParser'.  Note that this will compile the regular
        // expression used by 'parseUri'.  This method only needs to be called
        // once before any other method, but can be called multiple times
        // provided that for each call to 'initialize' there is a corresponding
        // call to 'shutdown'.  Use the optionally specified 'allocator' for
        // any memory allocation, or the 'global' allocator if none is
        // provided.  Note that specifying the allocator is provided for test
        // drivers only, and therefore users should let it default to the
        // global allocator.

    static void shutdown();
        // Pendant operation of the 'initialize' one.  Note that behaviour
        // after calling the '.parse()' method of the 'UriParser' after
        // 'shutdown' has been called is undefined.  The number of calls to
        // 'shutdown' must equal the number of calls to 'initialize', without
        // corresponding 'shutdown' calls, to fully destroy the parser.  It is
        // safe to call 'initialize' after calling 'shutdown'.  Behaviour is
        // undefined if 'shutdown' is called without 'initialize' first being
        // called.

    static int parse(Uri                      *result,
                     bsl::string              *errorDescription,
                     const bslstl::StringRef&  uriString);
        // Parse the specified 'uriString' into the specified 'result' object
        // if 'uriString' is a valid URI, otherwise load the specified
        // 'errorDescription' with a description of the syntax error present in
        // 'uriString'.  Return 0 on success and non-zero if 'uriString' does
        // not have a valid syntax.  Note that 'errorDescription' may be null
        // if the caller does not care about getting error messages.  The
        // behavior is undefined unless 'initialize' has been called
        // previously.

  private:
    // CLASS-SCOPE CATEGORY
    BALL_LOG_SET_CLASS_CATEGORY("BMQT.URI");
};


                              // ================
                              // class UriBuilder
                              // ================

class UriBuilder {
    // This component implements a mechanism, 'bmqt::UriBuilder', that can be
    // used for creating queue URI for BMQ.

  private:
    // DATA
    Uri d_uri; // Placeholder object for URI object being built.

  private:
    // NOT IMPLEMENTED
    UriBuilder(const UriBuilder&);
    UriBuilder& operator=(const UriBuilder&);
        // Copy constructor and assignment operator not implemented.

  public:
    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(UriBuilder, bslma::UsesBslmaAllocator)

    // CREATORS
    explicit UriBuilder(bslma::Allocator *allocator = 0);
        // Create a new UriBuilder using the optionally specified 'allocator'.

    explicit UriBuilder(const bmqt::Uri&  uri,
                        bslma::Allocator *allocator = 0);
        // Create a new UriBuilder initialized with the specified 'uri' and
        // using the optionally specified 'allocator'.

    // MANIPULATORS
    UriBuilder& setDomain(const bslstl::StringRef& value);
    UriBuilder& setTier(const bslstl::StringRef& value);
    UriBuilder& setQualifiedDomain(const bslstl::StringRef& value);
    UriBuilder& setQueue(const bslstl::StringRef& value);
    UriBuilder& setId(const bslstl::StringRef& value);
        // Set the corresponding field of the URI to the specified 'value'.
        // The behavior is undefined unless 'value' remains valid until URI has
        // been built by invoking 'uri()'.  'setDomain()' and 'setTier()'
        // should be preferred over 'setQualifiedDomain()' whenever possible.

    void reset();
        // Reset all fields of this builder.

    // ACCESSORS
    int uri(Uri         *result,
            bsl::string *errorDescription = 0) const;
        // Build and populate the specified 'result' with the built URI,
        // returning 0 on success, or return non-zero on error, populating the
        // optionally specified 'errorDescription' if provided.
};


// ============================================================================
//                             INLINE DEFINITIONS
// ============================================================================


                                 // ---------
                                 // class Uri
                                 // ---------

inline
Uri&
Uri::operator=(const Uri& rhs)
{
    copyImpl(rhs);

    return *this;
}

inline
const bsl::string&
Uri::asString() const
{
    return d_uri;
}

inline
bool
Uri::isValid() const
{
    // URI can only be set using either 'UriBuilder' or 'UriParser', which are
    // resetting the d_uri on failure.
    return !d_uri.empty();
}

inline
bool
Uri::isCanonical() const
{
    return d_query_id.isEmpty();
}

inline
const bslstl::StringRef&
Uri::scheme() const
{
    return d_scheme;
}

inline
const bslstl::StringRef&
Uri::authority() const
{
    return d_authority;
}

inline
const bslstl::StringRef&
Uri::path() const
{
    return d_path;
}

inline
const bslstl::StringRef&
Uri::domain() const
{
    return d_domain;
}

inline
const bslstl::StringRef&
Uri::qualifiedDomain() const
{
    return d_authority;
}

inline
const bslstl::StringRef&
Uri::tier() const
{
    return d_tier;
}

inline
const bslstl::StringRef&
Uri::queue() const
{
    return d_path;
}

inline
const bslstl::StringRef&
Uri::id() const
{
    return d_query_id;
}

inline
bslstl::StringRef
Uri::canonical() const
{
    size_t queryBeginPos = d_uri.find_first_of('?');
    if (bsl::string::npos == queryBeginPos) {
        return d_uri;                                                 // RETURN
    }

    return bslstl::StringRef(d_uri.c_str(), queryBeginPos);
}

// FREE FUNCTIONS
template <class HASH_ALGORITHM>
void
hashAppend(HASH_ALGORITHM& hashAlgo,
           const Uri&      uri)
{
    using bslh::hashAppend;  // for ADL
    hashAppend(hashAlgo, uri.d_uri);  // hashes full uri string
}


                              // ----------------
                              // class UriBuilder
                              // ----------------

inline
UriBuilder&
UriBuilder::setDomain(const bslstl::StringRef& value)
{
    d_uri.d_domain = value;
    return *this;
}

inline
UriBuilder&
UriBuilder::setTier(const bslstl::StringRef& value)
{
    d_uri.d_tier = value;
    return *this;
}

inline
UriBuilder&
UriBuilder::setQueue(const bslstl::StringRef& value)
{
    d_uri.d_path = value;
    return *this;
}

inline
UriBuilder&
UriBuilder::setId(const bslstl::StringRef& value)
{
    d_uri.d_query_id = value;
    return *this;
}


}  // close package namespace

// FREE OPERATORS

inline
bool
bmqt::operator==(const bmqt::Uri& lhs,
                 const bmqt::Uri& rhs)
{
    return (lhs.asString() == rhs.asString());
}

inline
bool
bmqt::operator!=(const bmqt::Uri& lhs,
                 const bmqt::Uri& rhs)
{
    return (lhs.asString() != rhs.asString());
}

inline
bool
bmqt::operator<(const bmqt::Uri& lhs,
                const bmqt::Uri& rhs)
{
    return (lhs.asString() < rhs.asString());
}

inline
bsl::ostream&
bmqt::operator<<(bsl::ostream&    stream,
                 const bmqt::Uri& rhs)
{
    return rhs.print(stream, 0, -1);
}

}  // close enterprise namespace

#endif