Include the required headers
You use std::string extensively, but you don't include <string>. This is allowed to work (any standard header is allowed to act as if it includes any or all others), but it's not required to work (and didn't, for me).
Use the proper defaults
Right now, your code defaults to https on port 440, but the normal default for https is port 443. I'd tend to include the port in the result if and only if it's explicitly specified. Otherwise, it's probably better to let the browser sort out the port, since most know quite a few more than your code does.
Fluent Interface
I'm not particularly excited about fluent interfaces in general, but if you're going to use one, I'd prefer to get rid of the set on the beginnings of most of the setters. If you have something like:
auto uri = builder().scheme(HTTPS).domain("www.google.com").build();
...you don't really need a set on the beginning of each to make it apparent what you intend.
Consider unique types for the parts of a URL/URI
I tend to wonder whether we wouldn't be better served by a set of tiny classes, one for each type of element in a URL, with each able to (for example) insert itself to a stream, with the url class basically just a set of those. At least for some of the obvious cases, I'd also at least consider using overloaded operators instead of named functions. For example, a fairly minimalist version might look something like this:
struct protocol {
enum prot { http, https } p_;
protocol(prot p) : p_(p) {}
friend std::ostream &operator<<(std::ostream &os, protocol const &p) {
switch (p.p_) {
case http: return os << "http://";
case https: return os << "https://";
}
}
};
struct query {
std::string k;
std::string v;
};
class queries {
std::vector<query> queries_;
public:
void add(query const &q) { queries_.push_back(q); }
friend std::ostream &operator<<(std::ostream &os, queries const &q) {
std::string sep = "?";
for (auto const & f : q.queries_) {
os << sep << f.k << "=" << f.v;
sep = "&";
}
return os;
}
};
class path {
std::vector<std::string> components;
public:
void add(std::string const &path) {
components.push_back(path);
}
friend std::ostream &operator << (std::ostream &os, path const &p) {
for (auto const & s : p.components)
os << s << "/";
return os;
}
};
class url {
protocol prot_;
path path_;
queries q_;
public:
url(protocol::prot p) : prot_(p) {}
url &operator/(std::string const &p) { path_.add(p); return *this; }
url &operator&(query const &q) { q_.add(q); return *this; }
friend std::ostream &operator<<(std::ostream &os, url const &u) {
return os << u.prot_ << u.path_ << u.q_;
}
};
int main() {
url u = url(protocol::http) / "www.youtube.com" / "watch" & query{"v", "tpnrd0xGRsw"};
std::cout << u;
}
Note how url's operator<< (roughly equivalent to your build) is now almost trivially simple--and if we add in types for a fragment, port, user name, and so on, it'll remain almost equally trivial, because most of the complexity will be delegated to each of those classes instead of being crammed together into the one giant "build" that knows everything about a URL and how all the pieces need to be delimited and such. Instead, it needs to know the order of the components in a URL, but that's about it. All the details of how to write each piece are delegated to the type for that piece (most of which are individually pretty trivial too).