Skip to content

Commit f2d5943

Browse files
committed
docs: clarify casing behavior for modbus entity validation
Signed-off-by: Pranav-0440 <pranavghorpade61@gmail.com>
1 parent ab6b700 commit f2d5943

5 files changed

Lines changed: 52 additions & 94 deletions

File tree

doc/examples/csv/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ A valid CSV file with correct data types and Modbus entities. This file should i
2323

2424
### `invalid.csv`
2525

26-
A CSV file with intentional validation errors to demonstrate the warning system. When imported, this file will trigger the following warnings:
26+
A CSV file with intentional validation issues to demonstrate the warning system. When imported, this file will trigger the following warnings:
2727

2828
1. **Row 2, type**: `number123` is invalid (should be `number`, `string`, or `boolean`)
29-
2. **Row 2, modbus:entity**: `holdingregister` is invalid (case-sensitive, should be `HoldingRegister`)
29+
2. **Row 2, modbus:entity**: `holdingregister` has incorrect casing (expected `HoldingRegister`)
3030
3. **Row 3, modbus:entity**: `InvalidRegister` is not a recognized Modbus entity
3131
4. **Row 4, type**: `invalid_type` is not a valid type
32-
5. **Row 5, modbus:entity**: `coil` is invalid (case-sensitive, should be `Coil`)
32+
5. **Row 5, modbus:entity**: `coil` has incorrect casing (expected `Coil`)
3333

3434
## Usage
3535

@@ -57,4 +57,4 @@ Required columns:
5757
- `modbus:mostSignificantWord`: Boolean (true/false)
5858
- `href`: Property endpoint path
5959

60-
Note: The validation is case-insensitive for Modbus entities, so `coil`, `Coil`, and `COIL` are all treated as equivalent.
60+
Note: Modbus entity validation is case-insensitive for recognition purposes, but incorrect casing will produce a warning. For example, `coil` and `Coil` are treated as the same entity, but `coil` will generate a casing warning.

src/components/App/CreateTd.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ const CreateTd: React.FC<CreateTdProps> = ({
9898
if (warnings.length > 0) {
9999
const warningMessage = warnings
100100
.map((w) => `Row ${w.row}, column "${w.column}": ${w.message}`)
101-
.join("\n");
102-
setError({ open: true, message: `Warnings:\n${warningMessage}` });
101+
.join("; ");
102+
setError({ open: true, message: `Warnings: ${warningMessage}` });
103103
} else {
104104
setError({ open: false, message: "" });
105105
}

src/components/Dialogs/ConvertTmDialog.tsx

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import {
1818
isVersionValid,
1919
} from "../../services/operations";
2020
import TextField from "../base/TextField";
21-
import { CsvWarning } from "../../utils/parser";
2221

2322
export interface ConvertTmDialogRef {
2423
openModal: () => void;
@@ -29,7 +28,6 @@ const ConvertTmDialog = forwardRef<ConvertTmDialogRef>((props, ref) => {
2928
const context = useContext(ediTDorContext);
3029

3130
const [display, setDisplay] = useState(false);
32-
const [warnings, setWarnings] = useState<CsvWarning[]>([]);
3331
const [validVersion, setValidVersion] = useState(false);
3432
const [versionInput, setVersionInput] = useState("");
3533

@@ -67,22 +65,9 @@ const ConvertTmDialog = forwardRef<ConvertTmDialogRef>((props, ref) => {
6765
onHandleEventRightButton={handleGenerateTd}
6866
rightButton={"Generate TD"}
6967
title={"Generate TD From TM"}
70-
description={"CSV conversion completed"}
68+
description={"Please provide values to switch the placeholders with."}
7169
>
7270
<>
73-
{warnings.length > 0 && (
74-
<div className="mb-4 rounded bg-yellow-900 p-3 text-yellow-200">
75-
<h3 className="font-bold">CSV Import Warnings</h3>
76-
<ul className="list-disc pl-5">
77-
{warnings.map((w, i) => (
78-
<li key={i}>
79-
Row {w.row}, {w.column}: {w.message}
80-
</li>
81-
))}
82-
</ul>
83-
</div>
84-
)}
85-
8671
{!validVersion && (
8772
<TextField
8873
label="TD instance version"

src/utils/parser.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ const VALID_MODBUS_ENTITIES = [
2121
"DiscreteInput",
2222
];
2323

24+
const VALID_MODBUS_ENTITIES_LOWER = VALID_MODBUS_ENTITIES.map((e) =>
25+
e.toLowerCase()
26+
);
2427
/** ====================================================== */
2528

2629
export type CsvData = {
@@ -91,10 +94,12 @@ export const parseCsv = (
9194
const warnings: CsvWarning[] = [];
9295

9396
const res = Papa.parse<CsvData>(csvContent, {
94-
header: true,
97+
header: hasHeaders,
98+
quoteChar: '"',
9599
skipEmptyLines: true,
100+
dynamicTyping: false,
96101
transformHeader: (h) => h.trim(),
97-
transform: (v) => (typeof v === "string" ? v.trim() : v),
102+
transform: (value) => (typeof value === "string" ? value.trim() : value),
98103
});
99104

100105
if (res.errors.length) {
@@ -115,16 +120,27 @@ export const parseCsv = (
115120
}
116121

117122
if (row["modbus:entity"]) {
118-
const entityLower = row["modbus:entity"].toLowerCase();
119-
const validEntityLower = VALID_MODBUS_ENTITIES.map((e) =>
120-
e.toLowerCase()
121-
);
122-
if (!validEntityLower.includes(entityLower)) {
123+
const entityValue = row["modbus:entity"];
124+
const entityLower = entityValue.toLowerCase();
125+
126+
const matchedIndex = VALID_MODBUS_ENTITIES_LOWER.indexOf(entityLower);
127+
128+
if (matchedIndex === -1) {
123129
warnings.push({
124130
row: rowNum,
125131
column: "modbus:entity",
126-
message: `Invalid modbus entity "${row["modbus:entity"]}"`,
132+
message: `Invalid modbus entity "${entityValue}"`,
127133
});
134+
} else {
135+
const canonical = VALID_MODBUS_ENTITIES[matchedIndex];
136+
137+
if (entityValue !== canonical) {
138+
warnings.push({
139+
row: rowNum,
140+
column: "modbus:entity",
141+
message: `Modbus entity "${entityValue}" has incorrect casing; expected "${canonical}"`,
142+
});
143+
}
128144
}
129145
}
130146
});

test/csv-examples.test.ts

Lines changed: 21 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,26 @@
1-
import { parseCsv, mapCsvToProperties } from "../src/utils/parser";
2-
import * as fs from "fs";
3-
import * as path from "path";
4-
import { fileURLToPath } from "url";
5-
6-
const __filename = fileURLToPath(import.meta.url);
7-
const __dirname = path.dirname(__filename);
8-
9-
console.log("=== Testing CSV Examples ===\n");
10-
11-
// Test valid.csv
12-
console.log("1. Testing valid.csv:");
13-
const validCsv = fs.readFileSync(
14-
path.join(__dirname, "../doc/examples/csv/valid.csv"),
15-
"utf-8"
16-
);
17-
const validResult = parseCsv(validCsv, true);
18-
console.log(` - Rows parsed: ${validResult.data.length}`);
19-
console.log(` - Warnings: ${validResult.warnings.length}`);
20-
if (validResult.warnings.length > 0) {
21-
console.log(" FAIL: Expected no warnings but got:");
22-
validResult.warnings.forEach((w) =>
23-
console.log(` Row ${w.row}, ${w.column}: ${w.message}`)
1+
import { describe, it, expect } from "vitest";
2+
import { parseCsv } from "../src/utils/parser";
3+
import fs from "fs";
4+
import path from "path";
5+
6+
describe("CSV example files", () => {
7+
const validCsv = fs.readFileSync(
8+
path.join(__dirname, "../doc/examples/csv/valid.csv"),
9+
"utf-8"
2410
);
25-
} else {
26-
console.log(" PASS: No warnings as expected");
27-
}
28-
29-
// Try to map properties
30-
try {
31-
const properties = mapCsvToProperties(validResult.data);
32-
console.log(` - Properties created: ${Object.keys(properties).length}`);
33-
console.log(" PASS: Properties mapped successfully");
34-
} catch (err) {
35-
console.log(` FAIL: ${(err as Error).message}`);
36-
}
37-
38-
console.log("\n2. Testing invalid.csv:");
39-
const invalidCsv = fs.readFileSync(
40-
path.join(__dirname, "../doc/examples/csv/invalid.csv"),
41-
"utf-8"
42-
);
43-
const invalidResult = parseCsv(invalidCsv, true);
44-
console.log(` - Rows parsed: ${invalidResult.data.length}`);
45-
console.log(` - Warnings: ${invalidResult.warnings.length}`);
4611

47-
if (invalidResult.warnings.length === 0) {
48-
console.log(" FAIL: Expected warnings but got none");
49-
} else {
50-
console.log(" PASS: Warnings detected as expected:");
51-
invalidResult.warnings.forEach((w) =>
52-
console.log(` Row ${w.row}, ${w.column}: ${w.message}`)
12+
const invalidCsv = fs.readFileSync(
13+
path.join(__dirname, "../doc/examples/csv/invalid.csv"),
14+
"utf-8"
5315
);
54-
}
5516

56-
// Try to map properties - should still work despite warnings
57-
try {
58-
const properties = mapCsvToProperties(invalidResult.data);
59-
console.log(` - Properties created: ${Object.keys(properties).length}`);
60-
console.log(" PASS: Properties mapped successfully despite warnings");
61-
} catch (err) {
62-
console.log(` FAIL: ${(err as Error).message}`);
63-
}
17+
it("valid.csv should produce no warnings", () => {
18+
const result = parseCsv(validCsv, true);
19+
expect(result.warnings.length).toBe(0);
20+
});
6421

65-
console.log("\n=== Summary ===");
66-
console.log("All CSV example files tested successfully!");
67-
console.log(
68-
"The validation system correctly identifies issues while still allowing data import."
69-
);
22+
it("invalid.csv should produce warnings", () => {
23+
const result = parseCsv(invalidCsv, true);
24+
expect(result.warnings.length).toBeGreaterThan(0);
25+
});
26+
});

0 commit comments

Comments
 (0)