Skip to content

Commit 4ff7163

Browse files
authored
Merge pull request #33 from TyKonKet/copilot/add-barcode-validation-api
Add standalone validation API for barcodes without encoding with opt-in type suggestions
2 parents f89fff0 + abda7fa commit 4ff7163

6 files changed

Lines changed: 847 additions & 2 deletions

File tree

README.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,29 @@ barcode.Export("output/{barcode}_{quality}.{format}", SKEncodedImageFormat.Png,
141141
// Creates: output/1234567890128_95.png
142142
```
143143

144-
> **💡 Need more help?** Check out our [Getting Started Guide](docs/getting-started.md) for step-by-step tutorials and examples.
144+
### **Validate Before Encoding**
145+
```csharp
146+
// NEW: Validate barcode data without encoding
147+
var result = BarcodeValidator.Validate("123456789012", BarcodeTypes.Ean13);
148+
if (result.IsValid)
149+
{
150+
Console.WriteLine($"✓ Valid: {result.ValidatedBarcode}");
151+
// Output: ✓ Valid: 1234567890128 (with check digit)
152+
}
153+
else
154+
{
155+
Console.WriteLine($"✗ Errors: {string.Join(", ", result.Errors)}");
156+
// Get suggestions for compatible barcode types (opt-in)
157+
var resultWithSuggestions = BarcodeValidator.Validate("ABC123", BarcodeTypes.Ean13, includeSuggestions: true);
158+
if (resultWithSuggestions.SuggestedTypes.Count > 0)
159+
{
160+
Console.WriteLine($"💡 Try: {string.Join(", ", resultWithSuggestions.SuggestedTypes)}");
161+
}
162+
}
163+
```
164+
165+
> **💡 Need more help?** Check out our [Getting Started Guide](docs/getting-started.md) for step-by-step tutorials and examples.
166+
> **📚 Validation API:** See the complete [Validation API Documentation](docs/api/barcode-validation.md) for detailed usage.
145167
146168
---
147169

@@ -211,10 +233,10 @@ BarcodeGenerator supports multiple .NET framework versions for maximum compatibi
211233

212234
### **Recently Completed**
213235
- [x] **CODE-39** encoder - Automotive and defense industry standard
236+
- [x] **Validation API** - Standalone barcode validation without generation ✨ NEW!
214237

215238
### 🚧 **Coming Soon**
216239
- [ ] **QR Code** support - 2D barcode generation
217-
- [ ] **Validation API** - Standalone barcode validation without generation
218240
- [ ] **Batch processing** - Generate multiple barcodes efficiently
219241
- [ ] **SVG export** - Vector format support
220242

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
using System;
2+
using Xunit;
3+
4+
namespace TyKonKet.BarcodeGenerator.Tests
5+
{
6+
public class BarcodeValidatorTests
7+
{
8+
[Theory]
9+
[InlineData("123456789012", BarcodeTypes.Ean13, "1234567890128")]
10+
[InlineData("1234567890128", BarcodeTypes.Ean13, "1234567890128")]
11+
[InlineData("978014300723", BarcodeTypes.Isbn13, "9780143007234")]
12+
[InlineData("9780143007234", BarcodeTypes.Isbn13, "9780143007234")]
13+
[InlineData("1234567", BarcodeTypes.Ean8, "12345670")]
14+
[InlineData("12345670", BarcodeTypes.Ean8, "12345670")]
15+
[InlineData("12345678905", BarcodeTypes.Upca, "012345678905")]
16+
[InlineData("012345678905", BarcodeTypes.Upca, "012345678905")]
17+
[InlineData("01234565", BarcodeTypes.Upce, "01234565")]
18+
[InlineData("0123456", BarcodeTypes.Upce, "01234565")]
19+
public void Validate_ShouldReturnValidResult_ForValidBarcodes(string input, BarcodeTypes type, string expected)
20+
{
21+
var result = BarcodeValidator.Validate(input, type);
22+
23+
Assert.True(result.IsValid);
24+
Assert.Equal(expected, result.ValidatedBarcode);
25+
Assert.Empty(result.Errors);
26+
Assert.Equal(type, result.Type);
27+
}
28+
29+
[Theory]
30+
[InlineData("ABC-1234-ABC", BarcodeTypes.Code93, "ABC-1234-ABCDX")]
31+
[InlineData("92736182345", BarcodeTypes.Code93, "92736182345%/")]
32+
[InlineData("ANDJEOW918273", BarcodeTypes.Code93, "ANDJEOW918273W+")]
33+
public void Validate_ShouldReturnValidResult_ForCode93Barcodes(string input, BarcodeTypes type, string expected)
34+
{
35+
var result = BarcodeValidator.Validate(input, type);
36+
37+
Assert.True(result.IsValid);
38+
Assert.Equal(expected, result.ValidatedBarcode);
39+
Assert.Empty(result.Errors);
40+
Assert.Equal(type, result.Type);
41+
}
42+
43+
[Theory]
44+
[InlineData("ABC-123", BarcodeTypes.Code39, "ABC-123")]
45+
[InlineData("HELLO WORLD", BarcodeTypes.Code39, "HELLO WORLD")]
46+
[InlineData("abc-123", BarcodeTypes.Code39, "ABC-123")]
47+
public void Validate_ShouldReturnValidResult_ForCode39Barcodes(string input, BarcodeTypes type, string expected)
48+
{
49+
var result = BarcodeValidator.Validate(input, type);
50+
51+
Assert.True(result.IsValid);
52+
Assert.Equal(expected, result.ValidatedBarcode);
53+
Assert.Empty(result.Errors);
54+
Assert.Equal(type, result.Type);
55+
}
56+
57+
[Theory]
58+
[InlineData("ABC123", BarcodeTypes.Code128, "ABC123")]
59+
[InlineData("Hello World!", BarcodeTypes.Code128, "Hello World!")]
60+
public void Validate_ShouldReturnValidResult_ForCode128Barcodes(string input, BarcodeTypes type, string expected)
61+
{
62+
var result = BarcodeValidator.Validate(input, type);
63+
64+
Assert.True(result.IsValid);
65+
Assert.Equal(expected, result.ValidatedBarcode);
66+
Assert.Empty(result.Errors);
67+
Assert.Equal(type, result.Type);
68+
}
69+
70+
[Theory]
71+
[InlineData("A123456A", BarcodeTypes.Codabar, "A123456A")]
72+
[InlineData("a123456a", BarcodeTypes.Codabar, "A123456A")]
73+
[InlineData("B1234B", BarcodeTypes.Codabar, "B1234B")]
74+
public void Validate_ShouldReturnValidResult_ForCodabarBarcodes(string input, BarcodeTypes type, string expected)
75+
{
76+
var result = BarcodeValidator.Validate(input, type);
77+
78+
Assert.True(result.IsValid);
79+
Assert.Equal(expected, result.ValidatedBarcode);
80+
Assert.Empty(result.Errors);
81+
Assert.Equal(type, result.Type);
82+
}
83+
84+
[Theory]
85+
[InlineData("ABC123", BarcodeTypes.Ean13)]
86+
[InlineData("12345ABCDE", BarcodeTypes.Ean8)]
87+
[InlineData("NOTANUMBER", BarcodeTypes.Upca)]
88+
[InlineData("INVALID", BarcodeTypes.Upce)]
89+
public void Validate_ShouldReturnInvalidResult_ForInvalidCharset(string input, BarcodeTypes type)
90+
{
91+
var result = BarcodeValidator.Validate(input, type);
92+
93+
Assert.False(result.IsValid);
94+
Assert.Null(result.ValidatedBarcode);
95+
Assert.NotEmpty(result.Errors);
96+
Assert.Equal(type, result.Type);
97+
}
98+
99+
[Theory]
100+
[InlineData("ABC-1234-aBC", BarcodeTypes.Code93)]
101+
[InlineData("$VARNAME PHP?", BarcodeTypes.Code39)]
102+
public void Validate_ShouldReturnInvalidResult_ForInvalidCode39And93Charset(string input, BarcodeTypes type)
103+
{
104+
var result = BarcodeValidator.Validate(input, type);
105+
106+
Assert.False(result.IsValid);
107+
Assert.Null(result.ValidatedBarcode);
108+
Assert.NotEmpty(result.Errors);
109+
Assert.Equal(type, result.Type);
110+
}
111+
112+
[Theory]
113+
[InlineData("879014300723", BarcodeTypes.Isbn13)]
114+
[InlineData("8780143007234", BarcodeTypes.Isbn13)]
115+
[InlineData("123014300723", BarcodeTypes.Isbn13)]
116+
public void Validate_ShouldReturnInvalidResult_ForInvalidIsbn13Prefix(string input, BarcodeTypes type)
117+
{
118+
var result = BarcodeValidator.Validate(input, type);
119+
120+
Assert.False(result.IsValid);
121+
Assert.Null(result.ValidatedBarcode);
122+
Assert.NotEmpty(result.Errors);
123+
Assert.Equal(type, result.Type);
124+
Assert.Contains("prefix", result.Errors[0], StringComparison.OrdinalIgnoreCase);
125+
}
126+
127+
[Fact]
128+
public void Validate_ShouldThrowArgumentNullException_ForNullBarcode()
129+
{
130+
Assert.Throws<ArgumentNullException>(() => BarcodeValidator.Validate(null!, BarcodeTypes.Ean13));
131+
}
132+
133+
[Theory]
134+
[InlineData("")]
135+
[InlineData(" ")]
136+
public void Validate_ShouldHandleEmptyOrWhitespaceInput(string input)
137+
{
138+
var result = BarcodeValidator.Validate(input, BarcodeTypes.Ean13);
139+
140+
Assert.False(result.IsValid);
141+
Assert.Null(result.ValidatedBarcode);
142+
Assert.NotEmpty(result.Errors);
143+
}
144+
145+
[Theory]
146+
[InlineData("ABC123", BarcodeTypes.Ean13)] // Should suggest Code39, Code93, Code128
147+
[InlineData("123456", BarcodeTypes.Code39)] // Should suggest numeric types like Ean8, Upca, etc.
148+
public void Validate_ShouldProvideSuggestedTypes_WhenValidationFails(string input, BarcodeTypes type)
149+
{
150+
var result = BarcodeValidator.Validate(input, type, includeSuggestions: true);
151+
152+
Assert.False(result.IsValid);
153+
Assert.NotNull(result.SuggestedTypes);
154+
// Should have at least one suggestion
155+
Assert.NotEmpty(result.SuggestedTypes);
156+
// Should not suggest the failed type
157+
Assert.DoesNotContain(type, result.SuggestedTypes);
158+
}
159+
160+
[Fact]
161+
public void Validate_ShouldSuggestCode128_ForAlphanumericInput()
162+
{
163+
var result = BarcodeValidator.Validate("ABC123", BarcodeTypes.Ean13, includeSuggestions: true);
164+
165+
Assert.False(result.IsValid);
166+
Assert.Contains(BarcodeTypes.Code128, result.SuggestedTypes);
167+
}
168+
169+
[Fact]
170+
public void Validate_ShouldNotHaveSuggestedTypes_WhenValidationSucceeds()
171+
{
172+
var result = BarcodeValidator.Validate("123456789012", BarcodeTypes.Ean13, includeSuggestions: true);
173+
174+
Assert.True(result.IsValid);
175+
Assert.Empty(result.SuggestedTypes);
176+
}
177+
178+
[Fact]
179+
public void Validate_ShouldSuggestNumericTypes_ForNumericInput()
180+
{
181+
// Input that's numeric but fails for Code39
182+
var result = BarcodeValidator.Validate("123456", BarcodeTypes.Code39, includeSuggestions: true);
183+
184+
Assert.True(result.IsValid); // Code39 accepts digits
185+
// But let's test with a pure numeric that might fail length checks
186+
result = BarcodeValidator.Validate("12", BarcodeTypes.Ean13, includeSuggestions: true);
187+
188+
Assert.False(result.IsValid);
189+
// Should suggest other numeric types
190+
var hasNumericSuggestion = result.SuggestedTypes.Count > 0;
191+
Assert.True(hasNumericSuggestion);
192+
}
193+
194+
[Fact]
195+
public void Validate_ShouldNotProvideSuggestions_WhenNotRequested()
196+
{
197+
var result = BarcodeValidator.Validate("ABC123", BarcodeTypes.Ean13);
198+
199+
Assert.False(result.IsValid);
200+
Assert.Empty(result.SuggestedTypes);
201+
}
202+
203+
[Fact]
204+
public void Validate_ShouldDefaultToNoSuggestions_ForPerformance()
205+
{
206+
// Default behavior should not compute suggestions
207+
var result = BarcodeValidator.Validate("INVALID", BarcodeTypes.Upca);
208+
209+
Assert.False(result.IsValid);
210+
Assert.Empty(result.SuggestedTypes);
211+
}
212+
}
213+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using Xunit;
2+
3+
namespace TyKonKet.BarcodeGenerator.Tests.Integration
4+
{
5+
/// <summary>
6+
/// Integration tests that verify the BarcodeValidator works correctly with the Barcode encoder.
7+
/// These tests ensure that validation and encoding produce consistent results.
8+
/// </summary>
9+
public class BarcodeValidatorIntegrationTests
10+
{
11+
[Theory]
12+
[InlineData("123456789012", BarcodeTypes.Ean13)]
13+
[InlineData("1234567", BarcodeTypes.Ean8)]
14+
[InlineData("12345678905", BarcodeTypes.Upca)]
15+
[InlineData("0123456", BarcodeTypes.Upce)]
16+
[InlineData("978014300723", BarcodeTypes.Isbn13)]
17+
public void ValidatedBarcode_ShouldMatchEncodedBarcode_ForNumericTypes(string input, BarcodeTypes type)
18+
{
19+
// Validate the barcode
20+
var validationResult = BarcodeValidator.Validate(input, type);
21+
22+
// Encode the barcode
23+
using var barcode = new Barcode(opt => opt.Type = type);
24+
var encodedResult = barcode.Encode(input);
25+
26+
// The validated barcode should match the encoded barcode
27+
Assert.True(validationResult.IsValid);
28+
Assert.Equal(encodedResult, validationResult.ValidatedBarcode);
29+
}
30+
31+
[Theory]
32+
[InlineData("ABC-1234-ABC", BarcodeTypes.Code93)]
33+
[InlineData("HELLO-WORLD", BarcodeTypes.Code39)]
34+
[InlineData("A123456A", BarcodeTypes.Codabar)]
35+
public void ValidatedBarcode_ShouldMatchEncodedBarcode_ForAlphanumericTypes(string input, BarcodeTypes type)
36+
{
37+
// Validate the barcode
38+
var validationResult = BarcodeValidator.Validate(input, type);
39+
40+
// Encode the barcode
41+
using var barcode = new Barcode(opt => opt.Type = type);
42+
var encodedResult = barcode.Encode(input);
43+
44+
// The validated barcode should match the encoded barcode
45+
Assert.True(validationResult.IsValid);
46+
Assert.Equal(encodedResult, validationResult.ValidatedBarcode);
47+
}
48+
49+
[Theory]
50+
[InlineData("ABC123", BarcodeTypes.Ean13)]
51+
[InlineData("INVALID", BarcodeTypes.Ean8)]
52+
[InlineData("NOT-A-NUMBER", BarcodeTypes.Upca)]
53+
public void Validation_ShouldFail_WhenEncodingWouldFail(string input, BarcodeTypes type)
54+
{
55+
// Validate the barcode
56+
var validationResult = BarcodeValidator.Validate(input, type);
57+
58+
// Validation should fail
59+
Assert.False(validationResult.IsValid);
60+
61+
// Encoding should also fail
62+
using var barcode = new Barcode(opt => opt.Type = type);
63+
Assert.Throws<System.FormatException>(() => barcode.Encode(input));
64+
}
65+
66+
[Fact]
67+
public void Validation_ShouldBeMorePerformant_ThanEncoding()
68+
{
69+
const int iterations = 1000;
70+
var testBarcode = "123456789012";
71+
72+
// Measure validation time
73+
var validationWatch = System.Diagnostics.Stopwatch.StartNew();
74+
for (int i = 0; i < iterations; i++)
75+
{
76+
var result = BarcodeValidator.Validate(testBarcode, BarcodeTypes.Ean13);
77+
Assert.True(result.IsValid);
78+
}
79+
validationWatch.Stop();
80+
81+
// Measure encoding time
82+
var encodingWatch = System.Diagnostics.Stopwatch.StartNew();
83+
for (int i = 0; i < iterations; i++)
84+
{
85+
using var barcode = new Barcode(opt => opt.Type = BarcodeTypes.Ean13);
86+
barcode.Encode(testBarcode);
87+
}
88+
encodingWatch.Stop();
89+
90+
// Validation should be significantly faster than encoding
91+
// We expect validation to be at least 50% faster (encoding includes rendering)
92+
Assert.True(validationWatch.ElapsedMilliseconds < encodingWatch.ElapsedMilliseconds / 2,
93+
$"Validation ({validationWatch.ElapsedMilliseconds}ms) should be faster than encoding ({encodingWatch.ElapsedMilliseconds}ms)");
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)