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
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"Apis.Create.AsyncAPI.Steps.ProvideAsyncAPI.solace.api.label": "Identified as Solace Event Portal API",
"Apis.Create.AsyncAPI.Steps.ProvideAsyncAPI.solace.label": "Solace Event API",
"Apis.Create.AsyncAPI.Steps.ProvideAsyncAPI.url.label": "AsyncAPI URL",
"Apis.Create.AsyncAPI.Steps.ProvideAsyncAPI.url.validation.error": "Failed to validate the AsyncAPI URL. Please try again.",
"Apis.Create.Components.DefaultAPIForm.api.actual.context.helper": "API will be exposed in {actualContext} context at the gateway",
"Apis.Create.Components.DefaultAPIForm.api.channel": "Channel",
"Apis.Create.Components.DefaultAPIForm.api.context": "Context",
Expand All @@ -91,6 +92,7 @@
"Apis.Create.Components.DefaultAPIForm.display.name.helper.text.api": "Display name for the API",
"Apis.Create.Components.DefaultAPIForm.display.name.helper.text.mcp": "Display name for the MCP Server",
"Apis.Create.Components.DefaultAPIForm.display.name.helper.text.product": "Display name for the API Product",
"Apis.Create.Components.DefaultAPIForm.endpoint.validation.error": "Error while validating endpoint URL",
"Apis.Create.Components.DefaultAPIForm.mandatory.fields": "Mandatory fields",
"Apis.Create.Components.DefaultAPIForm.name": "Name",
"Apis.Create.Components.DefaultAPIForm.select.gateway.type": "Gateway type",
Expand Down Expand Up @@ -170,13 +172,15 @@
"Apis.Create.OpenAPI.Steps.ProvideOpenAPI.Input.file.dropzone": "Drag & Drop Open API File/Archive here {break} or {break} Browse files",
"Apis.Create.OpenAPI.Steps.ProvideOpenAPI.Input.file.upload": "Browse File to Upload",
"Apis.Create.OpenAPI.Steps.ProvideOpenAPI.Input.type": "Input Type",
"Apis.Create.OpenAPI.Steps.ProvideOpenAPI.url.validation.error": "Failed to validate the OpenAPI URL. Please try again.",
"Apis.Create.OpenAPI.Steps.ValidationResults.linter.results": "Linter Results",
"Apis.Create.OpenAPI.Steps.ValidationResults.linting": "Generating Linter Results",
"Apis.Create.OpenAPI.Steps.ValidationResults.validating": "Validating API definition",
"Apis.Create.OpenAPI.Steps.ValidationResults.validation.errros": "Validation Errors",
"Apis.Create.OpenAPI.create.api.form.file.label": "OpenAPI File/Archive",
"Apis.Create.OpenAPI.create.api.form.url.label": "OpenAPI URL",
"Apis.Create.OpenAPI.create.api.openapi.content.validation.failed": "OpenAPI content validation failed!",
"Apis.Create.OpenAPI.create.api.openapi.validation.error": "Error while validating OpenAPI definition",
"Apis.Create.OpenAPI.create.api.url.helper.text": "Click away to validate the URL",
"Apis.Create.OpenAPI.create.api.url.label": "OpenAPI URL",
"Apis.Create.OpenAPI.create.api.url.placeholder": "Enter OpenAPI URL",
Expand Down Expand Up @@ -210,6 +214,7 @@
"Apis.Create.WSDL.url.label": "WSDL URL",
"Apis.Create.WSDL.url.placeholder": "Enter WSDL URL",
"Apis.Create.WSDL.validation.error.response": "Error occurred during validation",
"Apis.Create.WSDL.validation.error.title": "Error while validating the WSDL definition",
"Apis.Create.asyncAPI.Components.SelectPolicies.business.plans": "Protocol",
"Apis.Create.streaming.Components.SelectPolicies.business.plans": "Protocol",
"Apis.Create.streaming.Components.create.and.publish.btn": "Create & Publish",
Expand Down Expand Up @@ -1008,6 +1013,7 @@
"Apis.Details.Endpoints.GenericEndpoint.check.endpoint": "Check endpoint status",
"Apis.Details.Endpoints.GenericEndpoint.config.endpoint": "Endpoint configurations",
"Apis.Details.Endpoints.GenericEndpoint.config.service.placeholder": "Select a service from the service list",
"Apis.Details.Endpoints.GenericEndpoint.endpoint.validation.error": "Error while validating endpoint URL",
"Apis.Details.Endpoints.GenericEndpoint.no.ep.error": "Endpoint URL should not be empty",
"Apis.Details.Endpoints.GenericEndpoint.security.endpoint": "Endpoint security",
"Apis.Details.Endpoints.GenericEndpoint.service.url.input": "Service URL",
Expand Down Expand Up @@ -2261,6 +2267,7 @@
"MCPServers.Create.MCPServerCreateProxy.url.required": "MCP Server URL is required",
"MCPServers.Create.MCPServerCreateProxy.validate": "Next",
"MCPServers.Create.MCPServerCreateProxy.validation.error": "Error while validating MCP Server",
"MCPServers.Create.MCPServerCreateProxy.validation.error.detail": "Failed to validate MCP Server URL.",
"MCPServers.Create.MCPServerCreateProxy.validation.failed": "MCP Server validation failed. Please check the URL and security credentials (if any) and try again.",
"MCPServers.Create.MCPServerCreateProxy.value.label": "Value",
"MCPServers.Create.MCPServerCreateProxy.wizard.one": "Provide MCP Server URL",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,14 @@ export default function ProvideAsyncAPI(props) {
onValidate(isValidURL);
setIsValidating(false);
}).catch((error) => {
setValidity({ url: { message: error.message } });
setValidity({ ...isValid, url: { message: error.response?.body?.description
|| error.response?.body?.message
|| error.message
|| intl.formatMessage({
id: 'Apis.Create.AsyncAPI.Steps.ProvideAsyncAPI.url.validation.error',
defaultMessage: 'Failed to validate the AsyncAPI URL. Please try again.',
}),
} });
onValidate(false);
setIsValidating(false);
console.error(error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { InputAdornment, IconButton, Icon, Select, MenuItem, InputLabel, Box } f
import CircularProgress from '@mui/material/CircularProgress';
import Chip from '@mui/material/Chip';
import Typography from '@mui/material/Typography';
import { FormattedMessage } from 'react-intl';
import { FormattedMessage, useIntl } from 'react-intl';
import APIValidation from 'AppData/APIValidation';
import FormControl from '@mui/material/FormControl';
import FormHelperText from '@mui/material/FormHelperText';
Expand Down Expand Up @@ -142,6 +142,7 @@ export default function DefaultAPIForm(props) {
const [isUpdating, setUpdating] = useState(false);
const [isErrorCode, setIsErrorCode] = useState(false);
const iff = (condition, then, otherwise) => (condition ? then : otherwise);
const intl = useIntl();

/**
* Generate gradient background based on gateway name
Expand Down Expand Up @@ -408,7 +409,20 @@ export default function DefaultAPIForm(props) {
setIsEndpointValid(false);
}
}
}).finally(() => {
})
.catch((error) => {
const message = error.response?.body?.description
|| error.response?.body?.message
|| error.message
|| intl.formatMessage({
id: 'Apis.Create.Components.DefaultAPIForm.endpoint.validation.error',
defaultMessage: 'Error while validating endpoint URL',
});
setStatusCode(message);
setIsErrorCode(true);
setIsEndpointValid(false);
})
.finally(() => {
setUpdating(false);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import { ExpandMore } from '@mui/icons-material';
import { FormattedMessage } from "react-intl";
import LinterUI from "AppComponents/Apis/Details/APIDefinition/LinterUI/LinterUI";
import APILintingSummary from "AppComponents/Apis/Details/APIDefinition/Linting/APILintingSummary";
import {
spectralSeverityMap as severityMap } from "../../../Details/APIDefinition/Linting/Linting"
import {
spectralSeverityMap as severityMap } from "AppComponents/Apis/Details/APIDefinition/Linting/Linting"

type APILintingProps = {
inputValue: any,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved.
*
* WSO2 LLC. licenses this file to you 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.
*/

import getValidationErrorsFromError from './validationErrorUtils';

const TITLE = 'Error while validating OpenAPI definition';

describe('getValidationErrorsFromError', () => {
it('surfaces the backend description from a rejected validation (e.g. an access-control block, HTTP 400 "not trusted")', () => {
const notTrusted = 'The provided URL is not trusted. Please contact the system administrator.';
const error = {
response: { body: { code: 400, message: 'Bad Request', description: notTrusted } },
};

expect(getValidationErrorsFromError(error, TITLE)).toEqual([
{ message: TITLE, description: notTrusted },
]);
});

it('returns [] without a backend description (network/other errors keep the generic fallback)', () => {
const noDescriptionCases = [
new Error('Network Error'),
{ response: { body: {} } },
{ response: {} },
undefined,
];
noDescriptionCases.forEach((error) => {
expect(getValidationErrorsFromError(error, TITLE)).toEqual([]);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved.
*
* WSO2 LLC. licenses this file to you 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.
*/

/**
* Build "Validation Errors" card entries from a rejected OpenAPI validation request.
*
* When validate-openapi returns 200 with `isValid:false`, its `errors` array is shown in the
* card directly. But when it *rejects* with an HTTP error (e.g. a network access-control block —
* 400 UNTRUSTED_URL, "The provided URL is not trusted."), the reason lives in
* `error.response.body.description` and was previously dropped to the console, leaving the
* card empty and the user with only a generic "validation failed" message.
*
* This surfaces that backend description as a card entry so a rejected validation is shown as
* prominently as the `isValid:false` path. Errors without a backend description (network
* failures, etc.) return `[]`, so callers keep their existing generic-message fallback.
*
* @param {any} error caught error from a validateOpenAPIByFile / validateOpenAPIByUrl call
* @param {string} title localized bold title shown above the backend description
* @returns {Array<{message: string, description: string}>} card entries (empty when there is
* no backend-provided description)
*/
export default function getValidationErrorsFromError(
error: any,
title: string,
): Array<{ message: string; description: string }> {
const description = error?.response?.body?.description;
if (!description) {
return [];
}
return [{ message: title, description }];
}
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,14 @@ export default function ProvideGraphQL(props) {
inputsDispatcher({ action: 'graphQLInfo', value: graphQLInfo });
setValidity({ isValidURL, file: null });
} else {
let errorMessage;
if (inputType === ProvideGraphQL.INPUT_TYPES.ENDPOINT) {
errorMessage = 'Error occurred while generating GraphQL schema from endpoint';
} else if (inputType === ProvideGraphQL.INPUT_TYPES.URL) {
errorMessage = 'Error occurred while retrieving GraphQL schema from url';
let errorMessage = response.body?.description
|| response.body?.message || response.body?.errorMessage;
if (!errorMessage) {
if (inputType === ProvideGraphQL.INPUT_TYPES.ENDPOINT) {
errorMessage = 'Error occurred while generating GraphQL schema from endpoint';
} else if (inputType === ProvideGraphQL.INPUT_TYPES.URL) {
errorMessage = 'Error occurred while retrieving GraphQL schema from url';
}
}
setValidity({
isValidURL,
Expand All @@ -172,7 +175,10 @@ export default function ProvideGraphQL(props) {
};

const handleError = (error) => {
setValidity({ url: { message: error.message } });
setValidity({ url: { message: error.response?.body?.description
|| error.response?.body?.message
|| error.message,
}});
onValidate(false);
setIsValidating(false);
console.error(error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ import API from 'AppData/api';
import MCPServer from 'AppData/MCPServer';
import DropZoneLocal, { humanFileSize } from 'AppComponents/Shared/DropZoneLocal';
import Utils from 'AppData/Utils';
import {
import getValidationErrorsFromError from 'AppComponents/Apis/Create/Components/validationErrorUtils';
import ValidationResults from 'AppComponents/Apis/Create/Components/ValidationResults';
import {
getLinterResultsFromContent } from "../../../Details/APIDefinition/Linting/Linting";
import ValidationResults from './ValidationResults';

const PREFIX = 'ProvideOpenAPI';

Expand Down Expand Up @@ -90,6 +91,11 @@ export default function ProvideOpenAPI(props) {

const intl = useIntl();

const openApiValidationErrorTitle = intl.formatMessage({
id: 'Apis.Create.OpenAPI.create.api.openapi.validation.error',
defaultMessage: 'Error while validating OpenAPI definition',
});

function lint(content) {
// Validate and linting
setIsLinting(true);
Expand Down Expand Up @@ -124,6 +130,21 @@ export default function ProvideOpenAPI(props) {

const validateURLDebounced = useCallback(
debounce((newURL) => { // Example: https://codesandbox.io/s/debounce-example-l7fq3?file=/src/App.js
const handleValidationError = (error) => {
const errorMessage = error.response?.body?.description
|| error.response?.body?.message
|| error.message
|| intl.formatMessage({
id: 'Apis.Create.OpenAPI.Steps.ProvideOpenAPI.url.validation.error',
defaultMessage: 'Failed to validate the OpenAPI URL. Please try again.',
});
setValidity({ ...isValid, url: { message: errorMessage } });
setValidationErrors(getValidationErrorsFromError(error, openApiValidationErrorTitle));
onValidate(false);
setIsValidating(false);
console.error(error);
};

if (isMCPServer) {
MCPServer.validateOpenAPIByUrl(newURL, { returnContent: true }).then((response) => {
const {
Expand Down Expand Up @@ -162,12 +183,7 @@ export default function ProvideOpenAPI(props) {
}
onValidate(isValidURL);
setIsValidating(false);
}).catch((error) => {
setValidity({ url: { message: error.message } });
onValidate(false);
setIsValidating(false);
console.error(error);
});
}).catch(handleValidationError);
} else {
API.validateOpenAPIByUrl(newURL, { returnContent: true }).then((response) => {
const {
Expand Down Expand Up @@ -197,13 +213,7 @@ export default function ProvideOpenAPI(props) {
}
onValidate(isValidURL);
setIsValidating(false);
}).catch((error) => {
setValidity({ url: { message: error.message } });
onValidate(false);
setIsValidating(false);
console.error(error);

});
}).catch(handleValidationError);
}
}, 750),
[],
Expand Down Expand Up @@ -231,6 +241,7 @@ export default function ProvideOpenAPI(props) {
validFile = file;
inputsDispatcher({ action: 'preSetAPI', value: info });
setValidity({ ...isValid, file: null });
setValidationErrors([]);
} else {
setValidity({
...isValid, file: {
Expand All @@ -252,6 +263,7 @@ export default function ProvideOpenAPI(props) {
})
}
});
setValidationErrors(getValidationErrorsFromError(error, openApiValidationErrorTitle));
console.error(error);
})
.finally(() => {
Expand All @@ -270,6 +282,7 @@ export default function ProvideOpenAPI(props) {
validFile = file;
inputsDispatcher({ action: 'preSetAPI', value: info });
setValidity({ ...isValid, file: null });
setValidationErrors([]);
} else {
setValidity({
...isValid, file: {
Expand All @@ -291,6 +304,7 @@ export default function ProvideOpenAPI(props) {
})
}
});
setValidationErrors(getValidationErrorsFromError(error, openApiValidationErrorTitle));
console.error(error);
})
.finally(() => {
Expand Down
Loading
Loading