Skip to content

Commit 962d7e8

Browse files
authoredOct 9, 2024··
Merge pull request #86 from google/dev
Added Curl Importing
2 parents ebe3530 + c2e592a commit 962d7e8

File tree

1 file changed

+234
-4
lines changed

1 file changed

+234
-4
lines changed
 

‎api/ui/src/components/EditTemplate.vue

+234-4
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,34 @@ limitations under the License.
126126

127127
<!-- Request Payload Tab -->
128128
<n-tab-pane name="Request Payload" tab="Request Payload">
129-
<!-- Test Request Button -->
129+
<!-- Test Request and Import Buttons -->
130130
<n-space justify="end" size="large">
131131
<v-btn variant="flat" color="secondary" class="mt-2" @click="testRequest"> Test Request </v-btn>
132+
<v-btn variant="flat" color="secondary" class="mt-2" @click="openImportModal"> Import </v-btn>
132133
</n-space>
134+
135+
<!-- Import Modal -->
136+
<n-modal v-model:show="showImportModal">
137+
<n-card style="width: 600px" title="Import Curl Command" :bordered="false" size="huge" role="dialog" aria-modal="true">
138+
<n-input
139+
v-model:value="curlCommandInput"
140+
type="textarea"
141+
placeholder="Paste your curl command here"
142+
:autosize="{
143+
minRows: 5,
144+
maxRows: 10
145+
}"
146+
/>
147+
<n-divider></n-divider>
148+
<div class="modal-footer">
149+
<n-space>
150+
<v-btn variant="flat" color="primary" class="mt-2" @click="handleImport">Import</v-btn>
151+
<v-btn @click="showImportModal = false" class="mt-2">Cancel</v-btn>
152+
</n-space>
153+
</div>
154+
</n-card>
155+
</n-modal>
156+
133157
<n-divider />
134158
<!-- JSON Editor for Request Payload -->
135159
<json-editor-vue v-model="templateData.test_request" mode="text"></json-editor-vue>
@@ -245,7 +269,8 @@ import {
245269
useMessage,
246270
NSelect,
247271
NInputNumber,
248-
NCheckbox
272+
NCheckbox,
273+
NModal
249274
} from 'naive-ui';
250275
import type { TabsInst } from 'naive-ui';
251276
import type { UploadFileInfo } from 'naive-ui';
@@ -395,6 +420,36 @@ const rules = {
395420
// ... add validation rules for other fields
396421
};
397422

423+
// Ref for the modal visibility
424+
const showImportModal = ref(false);
425+
426+
// Ref for the curl command input
427+
const curlCommandInput = ref('');
428+
429+
/**
430+
* Opens the import modal.
431+
*/
432+
const openImportModal = () => {
433+
showImportModal.value = true;
434+
};
435+
436+
/**
437+
* Handles the import of a curl command.
438+
*/
439+
const handleImport = () => {
440+
try {
441+
const json = curlToJson(curlCommandInput.value); // Call curlToJson function
442+
templateData.value.test_request = JSON.stringify(json, null, 2); // Format as JSON string
443+
message.success('Curl command imported successfully!');
444+
} catch (error) {
445+
console.error('Error importing curl command:', error);
446+
message.error('Invalid curl command.');
447+
} finally {
448+
showImportModal.value = false; // Close the modal
449+
curlCommandInput.value = ''; // Reset the input
450+
}
451+
};
452+
398453
/**
399454
* Handles file uploads, validating for JSON format and structure.
400455
* @param data - Upload event data including file and fileList.
@@ -562,6 +617,11 @@ const addItem = () => {
562617
const testRequest = async () => {
563618
const reqString = templateData.value.test_request;
564619

620+
// Type guard function to check if an object is an Error
621+
function isError(error: unknown): error is Error {
622+
return typeof error === 'object' && error !== null && 'message' in error;
623+
}
624+
565625
if (typeof reqString === 'string') {
566626
try {
567627
const req = JSON.parse(reqString);
@@ -577,11 +637,31 @@ const testRequest = async () => {
577637
test_response = JSON.stringify(resptemp);
578638
showOutputUI.value = true;
579639
} else {
580-
message.error('Error: ' + response.status + ' ' + response.statusText);
640+
// Handle non-OK responses with more detail
641+
let errorMessage = `Error: ${response.status} ${response.statusText}`;
642+
643+
try {
644+
// Attempt to parse the error response as JSON for more context
645+
const errorJson = await response.json();
646+
if (errorJson && errorJson.message) {
647+
errorMessage += ` - ${errorJson.message}`;
648+
} else if (errorJson) {
649+
errorMessage += ` - ${JSON.stringify(errorJson)}`;
650+
}
651+
} catch (error) {
652+
// If parsing the error response fails, just use the status/statusText
653+
}
654+
655+
message.error(errorMessage);
581656
}
582657
} catch (parseError) {
658+
// Handle JSON parsing errors with the specific error message (using the type guard)
583659
console.error('Error parsing JSON:', parseError);
584-
message.error('Invalid JSON format in Request Payload');
660+
if (isError(parseError)) {
661+
message.error(`Invalid JSON format in Request Payload: ${parseError.message}`);
662+
} else {
663+
message.error(`Invalid JSON format in Request Payload (unknown error): ${parseError}`);
664+
}
585665
}
586666
} else {
587667
console.error('test_request is undefined');
@@ -796,4 +876,154 @@ watch(showDeepEvalOptions, (newValue) => {
796876
onUnmounted(() => {
797877
// ... Perform any cleanup if necessary
798878
});
879+
880+
interface RequestBody {
881+
[key: string]: any; // Allow any key-value pairs in the request body
882+
}
883+
884+
interface TransformedRequest {
885+
body?: RequestBody | string; // Allow string body for non-JSON data
886+
headers: { [key: string]: string };
887+
method: string;
888+
url: string;
889+
}
890+
891+
function curlToJson(curlCommand: string): TransformedRequest {
892+
const lines = curlCommand.split('\n');
893+
let requestBody: RequestBody | string | undefined;
894+
const headers: { [key: string]: string } = {};
895+
let method = 'GET'; // Default to GET if not specified
896+
let url = '';
897+
let inDataSection = false;
898+
let inHeredoc = false;
899+
let heredocContent = '';
900+
901+
// Extract values from the curl command
902+
lines.forEach((line) => {
903+
line = line.trim();
904+
905+
// Remove backslashes and leading/trailing single quotes from URL, data, and method (where appropriate)
906+
if (
907+
line.startsWith('curl') ||
908+
line.startsWith('-X') ||
909+
line.startsWith('-d') ||
910+
line.startsWith('--data') ||
911+
line.startsWith('--data-raw')
912+
) {
913+
line = line.replace(/\\\\/g, ''); // Only remove backslashes from these lines
914+
}
915+
if (line.startsWith('curl') || line.startsWith('-d') || line.startsWith('--data') || line.startsWith('--data-raw')) {
916+
line = line.replace(/^'/, '').replace(/'$/, ''); // Only remove single quotes from these lines
917+
}
918+
919+
if (line.startsWith('cat << EOF')) {
920+
inHeredoc = true;
921+
} else if (line === 'EOF') {
922+
inHeredoc = false;
923+
try {
924+
requestBody = JSON.parse(heredocContent);
925+
} catch (error) {
926+
// If not valid JSON, treat as plain text
927+
requestBody = heredocContent;
928+
}
929+
heredocContent = ''; // Reset for potential future heredocs
930+
} else if (inHeredoc) {
931+
heredocContent += line + '\n';
932+
} else if (line.startsWith('curl')) {
933+
// Extract URL if present on the same line (handle both single and double quotes)
934+
const urlMatch = line.match(/'(.*?)'|"(.*?)"/);
935+
if (urlMatch) {
936+
url = urlMatch[1] || urlMatch[2]; // Use the captured group that matched
937+
}
938+
} else if (line.startsWith('-X')) {
939+
// Capture the method (and remove any trailing backslashes and spaces)
940+
method = line.substring('-X '.length).trim().replace(/\\$/, '').toUpperCase().replace(/\s/g, '');
941+
} else if (line.startsWith('-H')) {
942+
// Capture headers (handle both single and double quotes correctly, and URLs)
943+
const headerMatch = line.match(/'(.*?)'|"(.*?)"/);
944+
if (headerMatch) {
945+
const headerLine = headerMatch[1] || headerMatch[2];
946+
const colonIndex = headerLine.indexOf(':');
947+
if (colonIndex > -1) {
948+
const key = headerLine.substring(0, colonIndex).trim();
949+
const value = headerLine.substring(colonIndex + 1).trim();
950+
headers[key] = value;
951+
}
952+
}
953+
} else if (line.startsWith('-d') || line.startsWith('--data') || line.startsWith('--data-raw')) {
954+
// Capture request body (handle file path or inline data)
955+
const singleQuoteDataStartIndex = line.indexOf("'") + 1; // Assuming data is enclosed in single quotes
956+
const doubleQuoteDataStartIndex = line.indexOf('"') + 1; // Assuming data is enclosed in double quotes
957+
let data = '';
958+
if (line.includes("'")) {
959+
data = line.substring(singleQuoteDataStartIndex);
960+
if (data.endsWith("'")) {
961+
data = data.substring(0, data.length - 1);
962+
}
963+
} else if (line.includes('"')) {
964+
data = line.substring(doubleQuoteDataStartIndex);
965+
if (data.endsWith('"')) {
966+
// Corrected: Removed extra double quote
967+
data = data.substring(0, data.length - 1);
968+
}
969+
}
970+
971+
if (data.startsWith('@')) {
972+
// If data starts with '@', it's a file path, so use the heredoc content as the body
973+
requestBody = requestBody; // Assuming requestBody was already populated from the heredoc
974+
} else {
975+
// Otherwise, it's inline data, try parsing as JSON or keep as string
976+
if (headers['Content-Type'] && headers['Content-Type'].includes('application/json')) {
977+
try {
978+
requestBody = JSON.parse(data);
979+
} catch (error) {
980+
// If it's supposed to be JSON but parsing fails, try to fix it or keep as string
981+
try {
982+
requestBody = fixInvalidJson(data);
983+
} catch (fixError) {
984+
requestBody = data;
985+
console.warn('Invalid JSON in request body (could not fix):', data);
986+
}
987+
}
988+
} else if (headers['Content-Type'] && headers['Content-Type'].includes('application/x-www-form-urlencoded')) {
989+
requestBody = parseUrlEncodedData(data);
990+
} else {
991+
requestBody = data;
992+
}
993+
}
994+
} else if (line.startsWith("'") && !inDataSection) {
995+
// If no other match and not in the data section, assume it's the URL
996+
url = line.substring(1, line.length - 1);
997+
} else if (line.startsWith('"') && !inDataSection) {
998+
// If no other match and not in the data section, assume it's the URL
999+
url = line.substring(1, line.length - 1);
1000+
}
1001+
});
1002+
1003+
// Construct the output object
1004+
const outputObject: TransformedRequest = {
1005+
body: requestBody,
1006+
headers,
1007+
method,
1008+
url
1009+
};
1010+
1011+
return outputObject;
1012+
}
1013+
1014+
function fixInvalidJson(jsonString: string): RequestBody {
1015+
// Basic attempt to fix common JSON errors (e.g., trailing commas)
1016+
const fixedJsonString = jsonString.replace(/,\s*([}\]])/g, '$1');
1017+
return JSON.parse(fixedJsonString);
1018+
}
1019+
1020+
function parseUrlEncodedData(data: string): RequestBody {
1021+
const result: RequestBody = {};
1022+
const pairs = data.split('&');
1023+
pairs.forEach((pair) => {
1024+
const [key, value] = pair.split('=');
1025+
result[decodeURIComponent(key)] = decodeURIComponent(value);
1026+
});
1027+
return result;
1028+
}
7991029
</script>

0 commit comments

Comments
 (0)
Please sign in to comment.