Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-github-url-parsing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@asyncapi/cli": patch
---

fix: support slash-based branch names and multi-dot file extensions in GitHub URL parsing
24 changes: 11 additions & 13 deletions src/apps/api/middlewares/validation.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,17 @@

const requestBody = method.requestBody;
if (!requestBody) {
return;
return; // No request body defined - validation not needed
}

let schema = requestBody.content['application/json'].schema;
if (!schema) {
return;
// Check if application/json content type exists
const jsonContent = requestBody.content?.['application/json'];
if (!jsonContent || !jsonContent.schema) {

Check warning on line 62 in src/apps/api/middlewares/validation.middleware.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer using an optional chain expression instead, as it's more concise and easier to read.

See more on https://sonarcloud.io/project/issues?id=asyncapi_cli&issues=AZ2xDSwwWySueEBaoV3X&open=AZ2xDSwwWySueEBaoV3X&pullRequest=2128
return; // No JSON schema defined - validation not needed
}

let schema = jsonContent.schema;

schema = { ...schema };
schema['$schema'] = 'http://json-schema.org/draft-07/schema';

Expand Down Expand Up @@ -173,16 +176,11 @@
}

try {
if (!validate) {
throw new ProblemException({
type: 'invalid-request-body',
title: 'Invalid Request Body',
status: 422,
detail: `Request body validation is not supported for "${options.path}" path with "${options.method}" method.`,
});
// If no requestBody schema is defined, skip validation
// This is valid - not all endpoints require request bodies
if (validate) {
await validateRequestBody(validate, req.body);
}

await validateRequestBody(validate, req.body);
} catch (error: unknown) {
if (error instanceof ProblemException) {
return next(error);
Expand Down
5 changes: 3 additions & 2 deletions src/domains/models/SpecificationFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,12 @@ export async function fileExists(name: string): Promise<boolean> {
return true;
}

const extension = name.split('.')[1];
// Get file extension properly (handle multiple dots like my.asyncapi.yaml)
const extension = name.split('.').pop()?.toLowerCase();

const allowedExtenstion = ['yml', 'yaml', 'json'];

if (!allowedExtenstion.includes(extension)) {
if (!extension || !allowedExtenstion.includes(extension)) {
throw new ErrorLoadingSpec('invalid file', name);
}

Expand Down
38 changes: 31 additions & 7 deletions src/domains/services/validation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,37 @@ const convertGitHubWebUrl = (url: string): string => {
const urlWithoutFragment = url.split('#')[0];

// Handle GitHub web URLs like: https://github.com/owner/repo/blob/branch/path
// eslint-disable-next-line no-useless-escape
const githubWebPattern = /^https:\/\/github\.com\/([^\/]+)\/([^\/]+)\/blob\/([^\/]+)\/(.+)$/;
const match = urlWithoutFragment.match(githubWebPattern);

if (match) {
const [, owner, repo, branch, filePath] = match;
return `https://api.github.com/repos/${owner}/${repo}/contents/${filePath}?ref=${branch}`;
// Support branch names with slashes (e.g., feature/new-validation)
const githubUrlPrefix = 'https://github.com/';
if (urlWithoutFragment.startsWith(githubUrlPrefix)) {
const afterPrefix = urlWithoutFragment.slice(githubUrlPrefix.length);
const parts = afterPrefix.split('/');

if (parts.length >= 4 && parts[2] === 'blob') {
const owner = parts[0];
const repo = parts[1];
// Everything after 'blob/' is branch + file path
const branchAndPath = parts.slice(3).join('/');

// Find the file path by looking for the last segment with an extension
// or assume the last segment is the file
const segments = branchAndPath.split('/');
let filePathIndex = segments.length - 1;

// Work backwards to find where file path likely starts
// (look for segments with file extensions)
for (let i = segments.length - 1; i >= 0; i--) {
if (segments[i].includes('.') || i === segments.length - 1) {
filePathIndex = i;
break;
}
}

const branch = segments.slice(0, filePathIndex).join('/');
const filePath = segments.slice(filePathIndex).join('/');

return `https://api.github.com/repos/${owner}/${repo}/contents/${filePath}?ref=${branch}`;
}
}

return url;
Expand Down
Loading