consistent string handling across parsers (#6866)

* consistent string handling across parsers

* fix
This commit is contained in:
naman-bruno
2026-01-27 14:10:40 +05:30
committed by GitHub
parent 01b87ee71c
commit 7661af34c8
14 changed files with 63 additions and 51 deletions

View File

@@ -1,6 +1,6 @@
import type { Action, ActionSetVariable, ActionVariableScope } from '@opencollection/types/common/actions';
import type { Variable as BrunoVariable, Variables as BrunoVariables } from '@usebruno/schema-types/common/variables';
import { uuid } from '../../../utils';
import { uuid, ensureString } from '../../../utils';
/**
* Convert Bruno post-response variables to OpenCollection actions.
@@ -58,8 +58,8 @@ export const toBrunoPostResponseVariables = (actions: Action[] | null | undefine
const variable: BrunoVariable = {
uid: uuid(),
name: setVarAction.variable?.name || '',
value: setVarAction.selector?.expression || '',
name: ensureString(setVarAction.variable?.name),
value: ensureString(setVarAction.selector?.expression),
enabled: setVarAction.disabled !== true,
local: false
};

View File

@@ -1,6 +1,6 @@
import type { KeyValue as BrunoKeyValue } from '@usebruno/schema-types/common/key-value';
import type { Assertion } from '@opencollection/types/common/assertions';
import { uuid } from '../../../utils';
import { uuid, ensureString } from '../../../utils';
const OPERATORS = [
'eq',
@@ -119,14 +119,14 @@ export const toBrunoAssertions = (assertions: Assertion[] | null | undefined): B
const brunoAssertions: BrunoKeyValue[] = assertions.map((assertion: Assertion): BrunoKeyValue => {
// Reconstruct the "operator value" format that Bruno uses
let valueString = assertion.operator;
let valueString = ensureString(assertion.operator);
if (assertion.value !== undefined && assertion.value !== null) {
valueString = `${assertion.operator} ${assertion.value}`;
valueString = `${assertion.operator} ${ensureString(assertion.value)}`;
}
const brunoAssertion: BrunoKeyValue = {
uid: uuid(),
name: assertion.expression || '',
name: ensureString(assertion.expression),
value: valueString,
enabled: assertion.disabled !== true
};

View File

@@ -10,7 +10,7 @@ import type {
FileBodyEntry
} from '@opencollection/types/requests/http';
import type { KeyValue as BrunoKeyValue } from '@usebruno/schema-types/common/key-value';
import { uuid } from '../../../utils';
import { uuid, ensureString } from '../../../utils';
export const toOpenCollectionBody = (body: BrunoHttpRequestBody | null | undefined): HttpRequestBody | undefined => {
if (!body) {
@@ -179,8 +179,8 @@ export const toBrunoBody = (body: HttpRequestBody | null | undefined): BrunoHttp
brunoBody.formUrlEncoded = body.data?.map((entry): BrunoKeyValue => {
const formEntry: BrunoKeyValue = {
uid: uuid(),
name: entry.name || '',
value: entry.value || '',
name: ensureString(entry.name),
value: ensureString(entry.value),
enabled: entry.disabled !== true
};
@@ -202,8 +202,8 @@ export const toBrunoBody = (body: HttpRequestBody | null | undefined): BrunoHttp
const multipartEntry: any = {
uid: uuid(),
type: entry.type,
name: entry.name || '',
value: entry.value || (entry.type === 'file' ? [] : ''),
name: ensureString(entry.name),
value: entry.type === 'file' ? (entry.value || []) : ensureString(entry.value),
contentType: entry.contentType || null,
enabled: entry.disabled !== true
};

View File

@@ -1,7 +1,7 @@
import type { FolderRequest as BrunoFolderRequest } from '@usebruno/schema-types/collection/folder';
import type { KeyValue as BrunoKeyValue } from '@usebruno/schema-types/common/key-value';
import type { HttpRequestHeader, HttpResponseHeader } from '@opencollection/types/requests/http';
import { uuid } from '../../../utils';
import { uuid, ensureString } from '../../../utils';
export const toOpenCollectionHttpHeaders = (headers: BrunoFolderRequest['headers']): HttpRequestHeader[] | undefined => {
if (!headers?.length) {
@@ -46,8 +46,8 @@ export const toBrunoHttpHeaders = (headers: HttpRequestHeader[] | HttpResponseHe
const brunoHeaders = headers.map((header): BrunoKeyValue => {
const brunoHeader: BrunoKeyValue = {
uid: uuid(),
name: header.name || '',
value: header.value || '',
name: ensureString(header.name),
value: ensureString(header.value),
enabled: ('disabled' in header) ? header.disabled !== true : true
};

View File

@@ -1,6 +1,6 @@
import type { HttpRequestParam as BrunoHttpRequestParam } from '@usebruno/schema-types/requests/http';
import type { HttpRequestParam } from '@opencollection/types/requests/http';
import { uuid } from '../../../utils';
import { uuid, ensureString } from '../../../utils';
export const toOpenCollectionParams = (params: BrunoHttpRequestParam[] | null | undefined): HttpRequestParam[] | undefined => {
if (!params?.length) {
@@ -36,8 +36,8 @@ export const toBrunoParams = (params: HttpRequestParam[] | null | undefined): Br
const brunoParams = params.map((param: HttpRequestParam): BrunoHttpRequestParam => {
const brunoParam: BrunoHttpRequestParam = {
uid: uuid(),
name: param.name || '',
value: param.value || '',
name: ensureString(param.name),
value: ensureString(param.value),
type: param.type,
enabled: param.disabled !== true
};

View File

@@ -1,7 +1,7 @@
import { Variable } from '@opencollection/types/common/variables';
import { FolderRequest as BrunoFolderRequest } from '@usebruno/schema-types/collection/folder';
import { Variable as BrunoVariable, Variables as BrunoVariables } from '@usebruno/schema-types/common/variables';
import { uuid } from '../../../utils';
import { uuid, ensureString } from '../../../utils';
/**
* Convert Bruno pre-request variables to OpenCollection variables format.
@@ -51,8 +51,8 @@ export const toBrunoVariables = (variables: Variable[] | null | undefined): { re
variables.forEach((v: Variable) => {
const variable: BrunoVariable = {
uid: uuid(),
name: v.name || '',
value: v.value as string || '',
name: ensureString(v.name),
value: ensureString(v.value),
enabled: v.disabled !== true,
local: false
};

View File

@@ -8,7 +8,7 @@ import { toBrunoVariables } from '../common/variables';
import { toBrunoPostResponseVariables } from '../common/actions';
import { toBrunoScripts } from '../common/scripts';
import { toBrunoAssertions } from '../common/assertions';
import { uuid } from '../../../utils';
import { uuid, ensureString } from '../../../utils';
const parseGraphQLRequest = (ocRequest: GraphQLRequest): BrunoItem => {
const info = ocRequest.info;
@@ -16,8 +16,8 @@ const parseGraphQLRequest = (ocRequest: GraphQLRequest): BrunoItem => {
const runtime = ocRequest.runtime;
const brunoRequest: BrunoHttpRequest = {
url: graphql?.url || '',
method: graphql?.method || 'POST',
url: ensureString(graphql?.url),
method: ensureString(graphql?.method, 'POST'),
headers: toBrunoHttpHeaders(graphql?.headers) || [],
params: toBrunoParams(graphql?.params) || [],
auth: toBrunoAuth(graphql?.auth),
@@ -86,7 +86,7 @@ const parseGraphQLRequest = (ocRequest: GraphQLRequest): BrunoItem => {
uid: uuid(),
type: 'graphql-request',
seq: info?.seq || 1,
name: info?.name || 'Untitled Request',
name: ensureString(info?.name, 'Untitled Request'),
tags: info?.tags || [],
request: brunoRequest,
settings: null,

View File

@@ -6,7 +6,7 @@ import { toBrunoAuth } from '../common/auth';
import { toBrunoVariables } from '../common/variables';
import { toBrunoScripts } from '../common/scripts';
import { toBrunoAssertions } from '../common/assertions';
import { isNonEmptyString, uuid } from '../../../utils';
import { isNonEmptyString, uuid, ensureString } from '../../../utils';
const toBrunoGrpcMetadata = (metadata: GrpcMetadata[] | null | undefined): BrunoKeyValue[] | undefined => {
if (!metadata?.length) {
@@ -16,8 +16,8 @@ const toBrunoGrpcMetadata = (metadata: GrpcMetadata[] | null | undefined): Bruno
const brunoMetadata = metadata.map((meta: GrpcMetadata): BrunoKeyValue => {
const brunoMeta: BrunoKeyValue = {
uid: uuid(),
name: meta.name || '',
value: meta.value || '',
name: ensureString(meta.name),
value: ensureString(meta.value),
enabled: meta.disabled !== true
};
@@ -33,8 +33,8 @@ const parseGrpcRequest = (ocRequest: GrpcRequest): BrunoItem => {
const runtime = ocRequest.runtime;
const brunoRequest: BrunoGrpcRequest = {
url: grpc?.url || '',
method: grpc?.method || '',
url: ensureString(grpc?.url),
method: ensureString(grpc?.method),
methodType: grpc?.methodType || '',
protoPath: grpc?.protoFilePath || null,
headers: toBrunoGrpcMetadata(grpc?.metadata) || [],
@@ -98,7 +98,7 @@ const parseGrpcRequest = (ocRequest: GrpcRequest): BrunoItem => {
uid: uuid(),
type: 'grpc-request',
seq: info?.seq || 1,
name: info?.name || 'Untitled Request',
name: ensureString(info?.name, 'Untitled Request'),
tags: info?.tags || [],
request: brunoRequest,
settings: {},

View File

@@ -9,7 +9,7 @@ import { toBrunoVariables } from '../common/variables';
import { toBrunoPostResponseVariables } from '../common/actions';
import { toBrunoScripts } from '../common/scripts';
import { toBrunoAssertions } from '../common/assertions';
import { uuid } from '../../../utils';
import { uuid, ensureString } from '../../../utils';
const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
const info = ocRequest.info;
@@ -17,8 +17,8 @@ const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
const runtime = ocRequest.runtime;
const brunoRequest: BrunoHttpRequest = {
url: http?.url || '',
method: http?.method || 'GET',
url: ensureString(http?.url),
method: ensureString(http?.method, 'GET'),
headers: toBrunoHttpHeaders(http?.headers) || [],
params: toBrunoParams(http?.params) || [],
auth: toBrunoAuth(http?.auth),
@@ -84,7 +84,7 @@ const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
uid: uuid(),
type: 'http-request',
seq: info?.seq || 1,
name: info?.name || 'Untitled Request',
name: ensureString(info?.name, 'Untitled Request'),
tags: info?.tags || [],
request: brunoRequest,
settings: null,
@@ -135,7 +135,7 @@ const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
const brunoExample: any = {
uid: uuid(),
itemUid: uuid(),
name: example.name || 'Untitled Example',
name: ensureString(example.name, 'Untitled Example'),
type: 'http-request',
request: null,
response: null
@@ -151,8 +151,8 @@ const parseHttpRequest = (ocRequest: HttpRequest): BrunoItem => {
if (example.request) {
brunoExample.request = {
url: example.request.url || '',
method: example.request.method || 'GET',
url: ensureString(example.request.url),
method: ensureString(example.request.method, 'GET'),
headers: toBrunoHttpHeaders(example.request.headers) || [],
params: toBrunoParams(example.request.params) || [],
body: toBrunoBody(example.request.body) || {

View File

@@ -5,7 +5,7 @@ import { toBrunoAuth } from '../common/auth';
import { toBrunoHttpHeaders } from '../common/headers';
import { toBrunoVariables } from '../common/variables';
import { toBrunoScripts } from '../common/scripts';
import { uuid } from '../../../utils';
import { uuid, ensureString } from '../../../utils';
const parseWebsocketRequest = (ocRequest: WebSocketRequest): BrunoItem => {
const info = ocRequest.info;
@@ -13,7 +13,7 @@ const parseWebsocketRequest = (ocRequest: WebSocketRequest): BrunoItem => {
const runtime = ocRequest.runtime;
const brunoRequest: BrunoWebSocketRequest = {
url: websocket?.url || '',
url: ensureString(websocket?.url),
headers: toBrunoHttpHeaders(websocket?.headers) || [],
auth: toBrunoAuth(websocket?.auth),
body: {
@@ -36,11 +36,12 @@ const parseWebsocketRequest = (ocRequest: WebSocketRequest): BrunoItem => {
// message
if (websocket?.message) {
const message = websocket.message as WebSocketMessage;
if (message.data?.trim().length) {
const messageData = ensureString(message.data);
if (messageData.trim().length) {
brunoRequest.body.ws = [{
name: '',
type: message.type || 'text',
content: message.data
content: messageData
}];
}
}
@@ -88,7 +89,7 @@ const parseWebsocketRequest = (ocRequest: WebSocketRequest): BrunoItem => {
uid: uuid(),
type: 'ws-request',
seq: info?.seq || 1,
name: info?.name || 'Untitled Request',
name: ensureString(info?.name, 'Untitled Request'),
tags: info?.tags || [],
request: brunoRequest,
settings: wsSettings as any,

View File

@@ -5,6 +5,7 @@ import { toBrunoAuth } from './common/auth';
import { toBrunoHttpHeaders } from './common/headers';
import { toBrunoVariables } from './common/variables';
import { toBrunoScripts } from './common/scripts';
import { ensureString } from '../../utils';
interface ParsedCollection {
collectionRoot: FolderRoot;
@@ -18,7 +19,7 @@ const parseCollection = (ymlString: string): ParsedCollection => {
// bruno config
const brunoConfig: Record<string, any> = {
opencollection: oc.opencollection || '1.0.0',
name: oc.info?.name || 'Untitled Collection',
name: ensureString(oc.info?.name, 'Untitled Collection'),
type: 'collection',
ignore: []
};

View File

@@ -2,7 +2,7 @@ import type { Environment as BrunoEnvironment, EnvironmentVariable as BrunoEnvir
import type { Environment } from '@opencollection/types/config/environments';
import type { Variable, SecretVariable } from '@opencollection/types/common/variables';
import { parseYml } from './utils';
import { uuid } from '../../utils';
import { uuid, ensureString } from '../../utils';
const isSecretVariable = (v: Variable | SecretVariable): v is SecretVariable => {
return 'secret' in v && v.secret === true;
@@ -17,7 +17,7 @@ const toBrunoEnvironmentVariables = (variables: (Variable | SecretVariable)[] |
if (isSecretVariable(v)) {
return {
uid: uuid(),
name: v.name || '',
name: ensureString(v.name),
value: '',
type: 'text',
enabled: v.disabled !== true,
@@ -26,8 +26,8 @@ const toBrunoEnvironmentVariables = (variables: (Variable | SecretVariable)[] |
}
const variable: BrunoEnvironmentVariable = {
uid: uuid(),
name: v.name || '',
value: (typeof v.value === 'string' ? v.value : '') || '',
name: ensureString(v.name),
value: ensureString(v.value),
type: 'text',
enabled: v.disabled !== true,
secret: false
@@ -42,7 +42,7 @@ const parseEnvironment = (ymlString: string): BrunoEnvironment => {
const brunoEnvironment: BrunoEnvironment = {
uid: uuid(),
name: ocEnvironment.name || 'Untitled Environment',
name: ensureString(ocEnvironment.name, 'Untitled Environment'),
variables: toBrunoEnvironmentVariables(ocEnvironment.variables)
};

View File

@@ -5,7 +5,7 @@ import { toBrunoAuth } from './common/auth';
import { toBrunoHttpHeaders } from './common/headers';
import { toBrunoVariables } from './common/variables';
import { toBrunoScripts } from './common/scripts';
import { isNonEmptyString } from '../../utils';
import { ensureString } from '../../utils';
const parseFolder = (ymlString: string): FolderRoot => {
try {
@@ -15,7 +15,7 @@ const parseFolder = (ymlString: string): FolderRoot => {
const folderRoot: FolderRoot = {
meta: {
name: info?.name || 'Untitled Folder',
name: ensureString(info?.name, 'Untitled Folder'),
seq: info?.seq || 1
},
request: null,

View File

@@ -6,6 +6,16 @@ export const isNumber = (value: unknown): value is number => typeof value === 'n
export const isNonEmptyString = (value: unknown): value is string => isString(value) && value.trim().length > 0;
export const ensureString = (value: unknown, fallback: string = ''): string => {
if (value === null || value === undefined) {
return fallback;
}
if (typeof value === 'string') {
return value;
}
return String(value);
};
export const uuid = () => {
// https://github.com/ai/nanoid/blob/main/url-alphabet/index.js
const urlAlphabet = 'useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict';