Skip to content

Commit efdf0d8

Browse files
Version 2.1.0
Support YAML for Java and C#. Support private fields for Java and C#. Support Map (Dictionary) and Set for the 'length' condition type. Minify the size of the npm package.
1 parent 125f7e0 commit efdf0d8

File tree

32 files changed

+461
-244
lines changed

32 files changed

+461
-244
lines changed

C Sharp/Object Validator Test/Object Validator Test.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
1616
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
1717
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
18-
<PackageReference Include="Quicksilver0218.ObjectValidator" Version="2.0.1" />
18+
<PackageReference Include="Quicksilver0218.ObjectValidator" Version="2.1.0" />
1919
<None Include="rules.json">
2020
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2121
</None>

C Sharp/Object Validator Test/TestObject.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
using System.Globalization;
22

3-
class TestObject
3+
class TestObject(string? testString, int testInt, bool testBool, TestObject2?[]? testArray)
44
{
5-
public string? testString;
6-
public int testInt;
7-
public bool testBool;
8-
public TestObject2?[]? testArray;
5+
private readonly string? testString = testString;
6+
private readonly int testInt = testInt;
7+
private readonly bool testBool = testBool;
8+
private readonly TestObject2?[]? testArray = testArray;
9+
10+
public string? TestString => testString;
11+
public int TestInt => testInt;
12+
public bool TestBool => testBool;
13+
public TestObject2?[]? TestArray => testArray;
914
}
1015

11-
class TestObject2
16+
class TestObject2(DateTime testDateTime)
1217
{
13-
public DateTime testDateTime;
18+
private readonly DateTime testDateTime = testDateTime;
1419

1520
public override string ToString()
1621
{

C Sharp/Object Validator Test/UnitTest1.cs

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,17 @@ public class UnitTest1
1111

1212
public UnitTest1()
1313
{
14-
validator = new(File.ReadAllText("rules.json"));
15-
TestObject testObject = new() {
16-
testString = "test測試",
17-
testInt = 1,
18-
testBool = true,
19-
testArray = [
14+
validator = new(File.OpenText("rules.json"));
15+
TestObject testObject = new(
16+
"test測試",
17+
1,
18+
true,
19+
[
2020
null,
21-
new() {
22-
testDateTime = new DateTime(2023, 1, 1)
23-
},
24-
new() {
25-
testDateTime = new DateTime(2024, 1, 1)
26-
}
21+
new(new(2023, 1, 1)),
22+
new(new(2024, 1, 1))
2723
]
28-
};
24+
);
2925
Stopwatch stopwatch = Stopwatch.StartNew();
3026
result = validator.Validate(testObject);
3127
stopwatch.Stop();
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
2-
1+
<Project Sdk="Microsoft.NET.Sdk">
32
<PropertyGroup>
43
<TargetFramework>net8.0</TargetFramework>
54
<RootNamespace>Quicksilver.ObjectValidator</RootNamespace>
65
<ImplicitUsings>enable</ImplicitUsings>
76
<Nullable>enable</Nullable>
87
<PackageId>Quicksilver0218.ObjectValidator</PackageId>
9-
<PackageVersion>2.0.1</PackageVersion>
8+
<PackageVersion>2.1.0</PackageVersion>
109
<Authors>Quicksilver0218</Authors>
1110
<Copyright>Copyright (c) Quicksilver0218 2024</Copyright>
1211
<Description>
@@ -17,5 +16,7 @@
1716
<PackageTags>Validation;Object;JSON;Common Rules;Client;Server</PackageTags>
1817
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
1918
</PropertyGroup>
20-
21-
</Project>
19+
<ItemGroup>
20+
<PackageReference Include="YamlDotNet" Version="16.3.0"/>
21+
</ItemGroup>
22+
</Project>

C Sharp/Object Validator/Runtime/Condition.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections;
2+
using System.Reflection;
23

34
namespace Quicksilver.ObjectValidator.Runtime;
45
abstract class Condition(bool reversed, string? fieldExpression)
@@ -7,7 +8,7 @@ abstract class Condition(bool reversed, string? fieldExpression)
78
protected readonly string? fieldExpression = fieldExpression;
89

910
private static void HandleField(object obj, string field, List<object?> values) {
10-
values.Add(obj.GetType().GetField(field)!.GetValue(obj));
11+
values.Add(obj.GetType().GetField(field, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(obj));
1112
}
1213

1314
private static void HandleIndex(IList list, int index, List<object?> values) {

C Sharp/Object Validator/Runtime/Length.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ protected override bool IsFulfilledBy(object? value, string? fullFieldExpression
1313
return Utils.InRange(s.Length, range);
1414
if (value is ICollection c)
1515
return Utils.InRange(c.Count, range);
16+
if (value is IReadOnlyCollection<object> roc)
17+
return Utils.InRange(roc.Count, range);
1618
throw new Exception("Unsupported type for 'length': " + value.GetType());
1719
}
1820
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
using Quicksilver.ObjectValidator.Config;
2+
using YamlDotNet.Core;
3+
using YamlDotNet.Core.Events;
4+
using YamlDotNet.Serialization;
5+
6+
namespace Quicksilver.ObjectValidator;
7+
internal sealed class TypeConverter : IYamlTypeConverter
8+
{
9+
public bool Accepts(Type type)
10+
{
11+
return type == typeof(Rule[]);
12+
}
13+
14+
public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
15+
{
16+
int state = 0;
17+
List<Rule> rules = [];
18+
do {
19+
switch (state) {
20+
case 0:
21+
if (parser.Current is not SequenceStart)
22+
throw new YamlException("Expected SequenceStart");
23+
state = 1;
24+
break;
25+
case 1:
26+
if (parser.Current is SequenceEnd) {
27+
parser.MoveNext();
28+
if (parser.Current is not DocumentEnd)
29+
throw new YamlException("Expected DocumentEnd");
30+
return rules.ToArray();
31+
}
32+
if (parser.Current is MappingStart) {
33+
state = 2;
34+
break;
35+
}
36+
throw new YamlException("Expected MappingStart or SequenceEnd");
37+
case 2:
38+
Condition? condition = null;
39+
int? id = null;
40+
string? message = null;
41+
while (parser.Current is not MappingEnd) {
42+
string key = (parser.Current as Scalar)!.Value;
43+
parser.MoveNext();
44+
switch (key) {
45+
case "condition":
46+
condition = ReadCondition(parser);
47+
break;
48+
case "id":
49+
id = int.Parse((parser.Current as Scalar)!.Value);
50+
break;
51+
case "message":
52+
message = (parser.Current as Scalar)!.Value;
53+
break;
54+
}
55+
parser.MoveNext();
56+
}
57+
rules.Add(new Rule(condition!, id, message));
58+
state = 1;
59+
break;
60+
}
61+
} while (parser.MoveNext());
62+
throw new YamlException("Unexpected end of document");
63+
}
64+
65+
private static Condition ReadCondition(IParser parser)
66+
{
67+
int state = 0;
68+
do {
69+
switch (state) {
70+
case 0:
71+
if (parser.Current is not MappingStart)
72+
throw new YamlException("Expected MappingStart");
73+
state = 1;
74+
break;
75+
case 1:
76+
string? type = null, field = null, arg = null;
77+
string?[]? args = null;
78+
Condition[]? conditions = null;
79+
while (parser.Current is not MappingEnd) {
80+
string key = (parser.Current as Scalar)!.Value;
81+
parser.MoveNext();
82+
switch (key) {
83+
case "type":
84+
type = (parser.Current as Scalar)!.Value;
85+
break;
86+
case "field":
87+
field = (parser.Current as Scalar)!.Value;
88+
break;
89+
case "arg":
90+
arg = (parser.Current as Scalar)!.Value;
91+
break;
92+
case "args":
93+
if (parser.Current is not SequenceStart)
94+
throw new YamlException("Expected SequenceStart");
95+
parser.MoveNext();
96+
List<string> argList = [];
97+
while (parser.Current is not SequenceEnd) {
98+
argList.Add((parser.Current as Scalar)!.Value);
99+
parser.MoveNext();
100+
}
101+
args = [..argList];
102+
break;
103+
case "conditions":
104+
if (parser.Current is not SequenceStart)
105+
throw new YamlException("Expected SequenceStart");
106+
parser.MoveNext();
107+
List<Condition> conditionList = [];
108+
while (parser.Current is not SequenceEnd) {
109+
conditionList.Add(ReadCondition(parser));
110+
parser.MoveNext();
111+
}
112+
conditions = [..conditionList];
113+
break;
114+
}
115+
parser.MoveNext();
116+
}
117+
return new Condition(type!, field, arg, args, conditions);
118+
}
119+
} while (parser.MoveNext());
120+
throw new YamlException("Unexpected end of document");
121+
}
122+
123+
public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
124+
{
125+
throw new NotImplementedException();
126+
}
127+
}

C Sharp/Object Validator/Validator.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
using System.Text.Json;
21
using Quicksilver.ObjectValidator.Runtime;
2+
using YamlDotNet.Serialization;
33

44
namespace Quicksilver.ObjectValidator;
55
public class Validator(Config.Rule[] rules, bool fastFail = false)
66
{
7-
private static readonly JsonSerializerOptions options = new() { IncludeFields = true };
8-
private readonly Rule[] rules = rules.Select(r => new Rule(r)).ToArray();
7+
private static readonly IDeserializer deserializer = new DeserializerBuilder().WithTypeConverter(new TypeConverter()).Build();
8+
private readonly Rule[] rules = [..rules.Select(r => new Rule(r))];
99
public bool fastFail = fastFail;
1010

11-
public Validator(string rulesJson, bool fastFail = false) : this(JsonSerializer.Deserialize<Config.Rule[]>(rulesJson, options)!, fastFail) {}
11+
public Validator(string rulesYaml, bool fastFail = false) : this(deserializer.Deserialize<Config.Rule[]>(rulesYaml)!, fastFail) {}
12+
13+
public Validator(TextReader reader, bool fastFail = false) : this(deserializer.Deserialize<Config.Rule[]>(reader)!, fastFail) {}
1214

1315
public ValidationResult Validate(object? obj)
1416
{

Document.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Table of Contents
2-
- [JSON Rule List](#json-rule-list)
2+
- [JSON / YAML Rule List](#json--yaml-rule-list)
33
- [Field Expression](#field-expression)
44
- [General](#general)
55
- [Iterable](#iterable)
@@ -40,7 +40,7 @@
4040
- [Rule](#rule-2)
4141
- [Condition](#condition-2)
4242

43-
## JSON Rule List
43+
## JSON / YAML Rule List
4444
Structure of the rule list:
4545

4646
```ts
@@ -224,7 +224,7 @@ A NOT gate can be applied to a condition by adding `!` to the beginning. e.g. `!
224224
`bytes` checks whether the length of the value in UTF-8 encoding in bytes is within the given range. It only supports string values. The `arg` field is required as the range expression for this condition type. (Please see [Range Expression](#range-expression) for detail.) The condition is passed when the length of the value in UTF-8 encoding in bytes is within the given range.
225225
226226
#### Length
227-
`length` checks whether the length of the value is within the given range. It supports string-like and array-like values. The `arg` field is required as the range expression for this condition type. (Please see [Range Expression](#range-expression) for detail.) The condition is passed when the length of the value is within the given range.
227+
`length` checks whether the length of the value is within the given range. It supports string-like, array-like, `Map` (`Dictionary`) and `Set` values. The `arg` field is required as the range expression for this condition type. (Please see [Range Expression](#range-expression) for detail.) The condition is passed when the length of the value is within the given range.
228228
229229
#### Contains
230230
`contains` checks whether the value contains the given string or null value. It supports string and iterable values. The `arg` field is required as the given string or null value. For string values, the condition is passed when the value contains the given string. For iterable values, the condition is passed when any of the string representation of the elements equals to the given string or null value. The string representation is obtained by calling `toString()`/`ToString()`.
@@ -251,7 +251,8 @@ namespace Quicksilver.ObjectValidator;
251251
##### Constructors
252252
```cs
253253
public Validator(Rule[] rules, bool fastFail = false);
254-
public Validator(string rulesJson, bool fastFail = false);
254+
public Validator(string rulesYaml, bool fastFail = false);
255+
public Validator(TextReader reader, bool fastFail = false);
255256
```
256257
257258
##### Fields
@@ -332,7 +333,9 @@ package com.quicksilver.objectvalidator;
332333
##### Constructors
333334
```java
334335
public Validator(Rule[] rules, boolean fastFail = false);
335-
public Validator(String rulesJson, boolean fastFail = false) throws JsonMappingException, JsonProcessingException;
336+
public Validator(String rulesYaml, boolean fastFail = false) throws JsonMappingException, JsonProcessingException;
337+
public Validator(URL url, boolean fastFail = false) throws IOException;
338+
public Validator(InputStream stream, boolean fastFail = false) throws IOException;
336339
```
337340
338341
##### Fields

Java/Object Validator/lib/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies {
2929
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
3030
implementation libs.guava
3131
implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.1'
32+
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.2'
3233
}
3334

3435
// Apply a specific Java toolchain to ease working on different environments.
@@ -46,7 +47,7 @@ tasks.named('test') {
4647
}
4748

4849
archivesBaseName = 'com.quicksilver.objectvalidator'
49-
version = '2.0.1'
50+
version = '2.1.0'
5051
description = 'A library providing functions that let users validate the values in objects with JSON-formatted rules.'
5152
group = 'io.github.quicksilver0218'
5253

0 commit comments

Comments
 (0)