universal-common
Version:
Library that provides useful missing base class library functionality.
204 lines (178 loc) • 5.84 kB
JavaScript
import StringBuilder from "./StringBuilder.js";
/**
* A builder class for constructing relative URIs.
*
* Relative URIs can be one of three types:
* 1. Scheme-relative (starts with "//") - inherits protocol from current page
* 2. Root-relative (starts with "/") - relative to domain root
* 3. Current-path-relative (no prefix) - relative to current path
*
* This class follows the builder pattern to allow fluent method chaining.
*/
export default class RelativeUriBuilder {
/**
* Type constant for scheme-relative URIs (e.g., "//example.com/path")
* These URIs inherit the protocol (http/https) from the current page.
* @type {string}
*/
static get TYPE_SCHEME() { return "//"; }
/**
* Type constant for root-relative URIs (e.g., "/path/to/resource")
* These URIs are relative to the domain root.
* @type {string}
*/
static get TYPE_ROOT() { return "/"; }
/**
* Type constant for current-path-relative URIs (e.g., "resource" or "subdir/resource")
* These URIs are relative to the current path.
* @type {string}
*/
static get TYPE_CURRENT() { return ""; }
#type;
#pathSegments = [];
#queryParameters = new URLSearchParams();
/**
* Creates a new RelativeUriBuilder instance.
*
* @param {string} type - The type of relative URI to build.
* Use the static TYPE_* constants for valid values.
* @example
* // Create a root-relative URI builder
* const builder = new RelativeUriBuilder(RelativeUriBuilder.TYPE_ROOT);
*/
constructor(type) {
this.#type = type;
}
/**
* Gets the path part of the URI.
*
* @returns {string} The path as a string with trailing slash
*/
get path() {
let stringBuilder = new StringBuilder();
for (let segment of this.#pathSegments) {
stringBuilder.append(`${segment}/`);
}
return stringBuilder.toString();
}
/**
* Gets the query string part of the URI.
*
* @returns {string} The query string without the leading '?'
*/
get query() {
return this.#queryParameters.toString();
}
/**
* Gets a copy of the path segments array.
*
* @returns {Array<string>} Array of path segments
*/
get segments() {
return [...this.#pathSegments];
}
/**
* Gets the type of relative URI being built.
*
* @returns {string} The URI type (one of the TYPE_* constants)
*/
get type() {
return this.#type;
}
/**
* Sets the type of relative URI being built.
*
* @param {string} value - The URI type (should be one of the TYPE_* constants)
*/
set type(value) {
this.#type = value;
}
/**
* Adds a single path segment.
*
* @param {string} value - The path segment to add
* @returns {RelativeUriBuilder} This builder instance for method chaining
*/
addSegment(value) {
this.#pathSegments.push(value);
return this;
}
/**
* Adds multiple path segments.
*
* @param {...string} arguments - Path segments to add
* @returns {RelativeUriBuilder} This builder instance for method chaining
*
* DIFFERENCE FROM UriBuilder: This implementation doesn't handle array arguments,
* unlike the similar method in UriBuilder.
*/
addSegments() {
const segments = Array.isArray(arguments[0]) && arguments.length === 1
? arguments[0] // Use the array directly
: arguments; // Use the arguments object
for (let segment of segments) {
this.addSegment(segment);
}
return this;
}
/**
* Adds a query parameter.
*
* @param {string} name - The parameter name
* @param {string} value - The parameter value
* @returns {RelativeUriBuilder} This builder instance for method chaining
*/
addQuery(name, value) {
this.#queryParameters.append(name, value);
return this;
}
/**
* Adds multiple query parameters from an object.
*
* @param {Object|Map} queries - Object containing name-value pairs
* @returns {RelativeUriBuilder} This builder instance for method chaining
*/
addQueries(queries) {
if (queries instanceof Map) {
for (const [key, value] of queries) {
this.addQuery(key, value);
}
} else {
for (const key in queries) {
this.addQuery(key, queries[key]);
}
}
return this;
}
/**
* Builds and returns the complete relative URI string.
*
* @returns {string} The complete relative URI
*/
get uri() {
// Determine the prefix based on the URI type
let prefix;
switch (this.type) {
case RelativeUriBuilder.TYPE_SCHEME:
prefix = "//";
break;
case RelativeUriBuilder.TYPE_ROOT:
prefix = "/";
break;
default:
prefix = "";
break;
}
// Build the URI
let stringBuilder = new StringBuilder(prefix);
// Add path (removing the trailing slash)
if (this.path) {
stringBuilder.append(this.path.slice(0, -1));
}
// Add query string if present
if (this.query) {
stringBuilder.append(`?${this.query}`);
}
return stringBuilder.toString();
}
}