Skip to content

Commit

Permalink
improve(cli): optimize file read/write parameter validation code
Browse files Browse the repository at this point in the history
  • Loading branch information
DM1-1 committed Apr 25, 2024
1 parent 189d35c commit 8dd42f3
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 58 deletions.
22 changes: 11 additions & 11 deletions cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'core-js'
import { Command } from 'commander'
import { Command, Option } from 'commander'
import { getClientId } from './utils/generator'
import { checkUpdate } from './utils/checkUpdate'
import {
Expand All @@ -10,6 +10,9 @@ import {
parseQoS,
parseVariadicOfBooleanType,
parsePubTopic,
parseFileRead,
parseFileSave,
parseFileWrite,
parseFormat,
parseOutputMode,
} from './utils/parse'
Expand Down Expand Up @@ -144,7 +147,7 @@ export class Commander {
.option('-p, --port <PORT>', 'the broker port', parseNumber)
.option(
'-f, --format <TYPE>',
'the format type of the input message, support base64, json, hex and cbor',
'the format type of the input message, support base64, json, hex, binary and cbor',
parseFormat,
)
.option('-i, --client-id <ID>', 'the client id', getClientId())
Expand Down Expand Up @@ -206,6 +209,7 @@ export class Commander {
.option(
'--file-read <PATH>',
'read the message body from the file',
parseFileRead
)
.option(
'-Pp, --protobuf-path <PATH>',
Expand Down Expand Up @@ -238,7 +242,7 @@ export class Commander {
'the user properties of MQTT 5.0 (e.g. -up "name: mqttx cli")',
parseUserProperties,
)
.option('-f, --format <TYPE>', 'format the message body, support base64, json, hex and cbor', parseFormat)
.option('-f, --format <TYPE>', 'format the message body, support base64, json, hex, binary and cbor', parseFormat)
.option('-v, --verbose', 'turn on verbose mode to display incoming MQTT packets')
.option(
'--output-mode <default/clean>',
Expand Down Expand Up @@ -306,14 +310,9 @@ export class Commander {
'--config [PATH]',
'load the parameters from the local configuration file, which supports json and yaml format, default path is ./mqttx-cli-config.json',
)
.option(
'--file-write <PATH>',
'append received messages to a specified file',
)
.option(
'--file-save <PATH>',
'save each received message to a new file',
)
// https://github.com/tj/commander.js/blob/master/examples/options-conflicts.js
.addOption(new Option('--file-write <PATH>', 'append received messages to a specified file').default(parseFileWrite).conflicts('fileSave'))
.addOption(new Option('--file-save <PATH>', 'save each received message to a new file').default(parseFileSave).conflicts('fileWrite'))
.option(
'-Pp, --protobuf-path <PATH>',
'the path to the .proto file that defines the message format for Protocol Buffers (protobuf)',
Expand Down Expand Up @@ -500,6 +499,7 @@ export class Commander {
.option(
'--file-read <PATH>',
'read the message body from the file',
parseFileRead
)
.allowUnknownOption(false)
.action(benchPub)
Expand Down
15 changes: 2 additions & 13 deletions cli/src/lib/pub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import { readFile, processPath } from '../utils/fileUtils'
import convertPayload from '../utils/convertPayload'
import * as Debug from 'debug'

const MQTT_SINGLE_MESSAGE_BYTE_LIMIT = 256 * 1024 * 1024;

const processPublishMessage = (
message: string | Buffer,
protobufPath?: string,
Expand Down Expand Up @@ -151,22 +149,13 @@ const multisend = (
}

const handleFileRead = (filePath: string) => {
if (!filePath) {
signale.error('File path is required when reading from file.')
process.exit(1)
}

try {
basicLog.fileReading()
const bufferData = readFile(filePath)
if (bufferData.length >= MQTT_SINGLE_MESSAGE_BYTE_LIMIT) {
signale.error('File size over 256MB not supported by MQTT.')
process.exit(1)
}
basicLog.fileReadSuccess()
return bufferData
} catch(error) {
signale.error('Failed to read file:', error)
} catch (err) {
signale.error('Failed to read file:', err)
process.exit(1)
}
}
Expand Down
44 changes: 20 additions & 24 deletions cli/src/lib/sub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { parseConnectOptions, parseSubscribeOptions, checkTopicExists } from '..
import delay from '../utils/delay'
import convertPayload from '../utils/convertPayload'
import { saveConfig, loadConfig } from '../utils/config'
import { createNextNumberedFileName, writeFile, appendFile, processPath, getPathExtname } from '../utils/fileUtils'
import { writeFile, appendFile, getPathExtname } from '../utils/fileUtils'
import { deserializeBufferToProtobuf } from '../utils/protobuf'
import isSupportedBinaryFormatForMQTT from '../utils/binaryFormats'
import * as Debug from 'debug'
Expand Down Expand Up @@ -38,6 +38,20 @@ const processReceivedMessage = (
return message
}

/**
* @param format
* @param filePath after option check, it must be a string not undefined
*/
const handleDefaultBinaryFile = (format: FormatType | undefined, filePath: string) => {
if ((!format || format !== 'binary') && isSupportedBinaryFormatForMQTT(getPathExtname(filePath))) {
signale.warn('Please use the --format binary option for handling binary files')
if(!format) {
return 'binary'
}
}
return format
}

const sub = (options: SubscribeOptions) => {
const { debug, save, config } = options

Expand All @@ -57,18 +71,13 @@ const sub = (options: SubscribeOptions) => {

const outputModeClean = outputMode === 'clean'

options.format = handleDefaultBinaryFile(options.format, options.fileSave! || options.fileWrite!)

let retryTimes = 0

!outputModeClean && basicLog.connecting(config, connOpts.hostname!, connOpts.port, options.topic.join(', '))

client.on('connect', () => {
const { fileWrite, fileSave } = options

if(fileWrite && fileSave) {
signale.error('Connected failed, Cannot use both fileSave and fileWrite options')
process.exit(1)
}

!outputModeClean && basicLog.connected()

retryTimes = 0
Expand Down Expand Up @@ -114,25 +123,12 @@ const sub = (options: SubscribeOptions) => {
if(fileOperate.SAVE || fileOperate.WRITE) {
let savePath = ''
if(fileSave) {
savePath = createNextNumberedFileName(processPath(fileSave))
savePath = fileSave
} else if(fileWrite) {
savePath = processPath(fileWrite)
}

if(!savePath) {
signale.error('A valid file path with extension is required when writing to a file')
process.exit(1)
savePath = fileWrite
}

let messageFormat = format
if ((!format || format !== 'binary') && isSupportedBinaryFormatForMQTT(getPathExtname(savePath))) {
signale.warn('Please use the --format binary option for handling binary files')
if(!format) {
messageFormat = 'binary'
}
}

const receivedMessage = processReceivedMessage(payload, protobufPath, protobufMessageName, messageFormat)
const receivedMessage = processReceivedMessage(payload, protobufPath, protobufMessageName, format)

fileOperate.SAVE && writeFile(savePath, receivedMessage)
fileOperate.WRITE && appendFile(savePath, receivedMessage)
Expand Down
33 changes: 23 additions & 10 deletions cli/src/utils/fileUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from 'path'
import YAML from 'js-yaml'
import signale from 'signale'

export const processPath = (savePath: boolean | string, defaultPath?: string) => {
const processPath = (savePath: boolean | string, defaultPath?: string) => {
let filePath = ''
if (savePath === true && defaultPath) {
filePath = defaultPath
Expand All @@ -16,24 +16,24 @@ export const processPath = (savePath: boolean | string, defaultPath?: string) =>
return filePath
}

export const getPathExtname = (filePath: string): string => path.extname(filePath)
const getPathExtname = (filePath: string): string => path.extname(filePath)

export const fileExists = (filePath: string): boolean => fs.existsSync(filePath)
const fileExists = (filePath: string): boolean => fs.existsSync(filePath)

export const isYaml = (filePath: string): boolean => {
const isYaml = (filePath: string): boolean => {
const fileExtension = getPathExtname(filePath)
return fileExtension === '.yaml' || fileExtension === '.yml'
}

export const parseYamlOrJson = (data: string, isYaml: boolean): Config => {
const parseYamlOrJson = (data: string, isYaml: boolean): Config => {
return isYaml ? YAML.load(data) : JSON.parse(data)
}

export const stringifyToYamlOrJson = (data: Config, isYaml: boolean): string => {
const stringifyToYamlOrJson = (data: Config, isYaml: boolean): string => {
return isYaml ? YAML.dump(data) : JSON.stringify(data, null, 2)
}

export const readFile = (filePath: string): Buffer => {
const readFile = (filePath: string): Buffer => {
try {
return fs.readFileSync(filePath)
} catch (error) {
Expand All @@ -42,7 +42,7 @@ export const readFile = (filePath: string): Buffer => {
}
}

export const writeFile = (filePath: string, data: string | Buffer): void => {
const writeFile = (filePath: string, data: string | Buffer): void => {
try {
fs.writeFileSync(filePath, data)
} catch (error) {
Expand All @@ -51,7 +51,7 @@ export const writeFile = (filePath: string, data: string | Buffer): void => {
}
}

export const appendFile = (filePath: string, data: string | Buffer): void => {
const appendFile = (filePath: string, data: string | Buffer): void => {
try {
fs.appendFileSync(filePath, `${data}\n`)
} catch (error) {
Expand All @@ -60,7 +60,7 @@ export const appendFile = (filePath: string, data: string | Buffer): void => {
}
}

export const createNextNumberedFileName = (filePath: string): string => {
const createNextNumberedFileName = (filePath: string): string => {
const escapeRegExp = (string: string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
Expand Down Expand Up @@ -90,4 +90,17 @@ export const createNextNumberedFileName = (filePath: string): string => {
signale.error(`Error: Unable to create a new numbered file name for path '${filePath}'.`)
process.exit(1)
}
}

export {
processPath,
getPathExtname,
fileExists,
isYaml,
parseYamlOrJson,
stringifyToYamlOrJson,
readFile,
writeFile,
appendFile,
createNextNumberedFileName
}
39 changes: 39 additions & 0 deletions cli/src/utils/parse.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import * as fs from 'fs'
import signale from '../utils/signale'
import { getSpecialTypesOption } from '../utils/generator'
import { createNextNumberedFileName, readFile, processPath, getPathExtname } from '../utils/fileUtils'

import { IClientOptions, IClientPublishOptions, IClientSubscribeOptions } from 'mqtt'
import { getLocalScenarioList, getScenarioFilePath } from './simulate'

const MQTT_SINGLE_MESSAGE_BYTE_LIMIT = 256 * 1024 * 1024

const parseNumber = (value: string) => {
const parsedValue = Number(value)
if (isNaN(parsedValue)) {
Expand Down Expand Up @@ -97,6 +100,39 @@ const parsePubTopic = (value: string) => {
return value
}

const parseFileRead = (value: string) => {
const filePath = processPath(value)
if(!filePath) {
signale.error('A valid file path is required when reading from file.')
process.exit(1)
}

const fileContent = readFile(filePath)
if(fileContent.length >= MQTT_SINGLE_MESSAGE_BYTE_LIMIT) {
signale.error('File size over 256MB not supported by MQTT.')
process.exit(1)
}
return value
}

const parseFileSave = (value: string) => {
const filePath = createNextNumberedFileName(processPath(value))
if(!filePath) {
signale.error('A valid file path is required when saving to file.')
process.exit(1)
}
return filePath
}

const parseFileWrite = (value: string) => {
const filePath = processPath(value)
if(!filePath) {
signale.error('A valid file path is required when writing to file.')
process.exit(1)
}
return filePath
}

const parseFormat = (value: string) => {
if (!['base64', 'json', 'hex', 'cbor', 'binary'].includes(value)) {
signale.error('Not a valid format type.')
Expand Down Expand Up @@ -380,6 +416,9 @@ export {
checkTopicExists,
checkScenarioExists,
parsePubTopic,
parseFileRead,
parseFileSave,
parseFileWrite,
parseFormat,
parseOutputMode,
parseConnectOptions,
Expand Down

0 comments on commit 8dd42f3

Please sign in to comment.