From 751fca68cffb9ed598d6222f388e7ccd9c62e70f Mon Sep 17 00:00:00 2001 From: Bryan Lee <38807139+liby@users.noreply.github.com> Date: Tue, 15 Apr 2025 20:44:33 +0800 Subject: [PATCH] Add RawPathSegment class to preserve slashes in API paths --- packages/core/src/infrastructure/Utils.ts | 40 +++++++++++++++---- .../core/src/resources/CommitDiscussions.ts | 15 +++---- .../core/src/templates/ResourceDiscussions.ts | 9 ++++- 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/packages/core/src/infrastructure/Utils.ts b/packages/core/src/infrastructure/Utils.ts index c9a19a71..d03fc3c3 100644 --- a/packages/core/src/infrastructure/Utils.ts +++ b/packages/core/src/infrastructure/Utils.ts @@ -41,15 +41,39 @@ export function appendFormFromObject(object: Record): F } /** - * Normalize GitLab API endpoint by encoding route parameters. - * @param strings - * @param values + * A special class to mark path segments that should not be URL-encoded. + * Used for paths that already contain special characters like '/' that should be preserved. + * For example, in API paths like 'repository/commits', the slash should not be encoded to '%2F'. */ -export function endpoint(strings: TemplateStringsArray, ...values: (string | number)[]): string { - return values.reduce( - (string, value, index) => string + encodeURIComponent(value) + strings[index + 1], - strings[0], - ); +export class RawPathSegment { + public readonly value: string; + + constructor(value: string) { + this.value = value; + } + + toString(): string { + return this.value; + } +} + +/** + * Normalize GitLab API endpoint by encoding route parameters. + * Handles special cases where certain path segments (wrapped in RawPathSegment) + * should not be URL-encoded. + * @param strings Template string parts + * @param values Values to be encoded and inserted between string parts + */ +export function endpoint( + strings: TemplateStringsArray, + ...values: (string | number | RawPathSegment)[] +): string { + return values.reduce((result, value, index) => { + const encodedValue = + value instanceof RawPathSegment ? value.value : encodeURIComponent(String(value)); + + return result + encodedValue + strings[index + 1]; + }, strings[0]); } /** diff --git a/packages/core/src/resources/CommitDiscussions.ts b/packages/core/src/resources/CommitDiscussions.ts index f135cd18..276ec5c3 100644 --- a/packages/core/src/resources/CommitDiscussions.ts +++ b/packages/core/src/resources/CommitDiscussions.ts @@ -1,11 +1,12 @@ import type { BaseResourceOptions } from '@gitbeaker/requester-utils'; import { ResourceDiscussions } from '../templates'; -import type { - GitlabAPIResponse, - PaginationRequestOptions, - PaginationTypes, - ShowExpanded, - Sudo, +import { + type GitlabAPIResponse, + type PaginationRequestOptions, + type PaginationTypes, + RawPathSegment, + type ShowExpanded, + type Sudo, } from '../infrastructure'; import type { DiscussionNotePositionOptions, @@ -68,6 +69,6 @@ export interface CommitDiscussions extends ResourceDi export class CommitDiscussions extends ResourceDiscussions { constructor(options: BaseResourceOptions) { /* istanbul ignore next */ - super('projects', 'repository/commits', options); + super('projects', new RawPathSegment('repository/commits'), options); } } diff --git a/packages/core/src/templates/ResourceDiscussions.ts b/packages/core/src/templates/ResourceDiscussions.ts index 41a7f11a..cfcdfba1 100644 --- a/packages/core/src/templates/ResourceDiscussions.ts +++ b/packages/core/src/templates/ResourceDiscussions.ts @@ -7,6 +7,7 @@ import type { MappedOmit, PaginationRequestOptions, PaginationTypes, + RawPathSegment, ShowExpanded, Sudo, } from '../infrastructure'; @@ -76,9 +77,13 @@ export interface DiscussionSchema extends Record { export type DiscussionNotePositionOptions = Camelize; export class ResourceDiscussions extends BaseResource { - protected resource2Type: string; + protected resource2Type: string | RawPathSegment; - constructor(resourceType: string, resource2Type: string, options: BaseResourceOptions) { + constructor( + resourceType: string, + resource2Type: string | RawPathSegment, + options: BaseResourceOptions, + ) { super({ prefixUrl: resourceType, ...options }); this.resource2Type = resource2Type;