Skip to content

Commit cc815e0

Browse files
committed
docs: tighten class reference docs and complete error reference
1 parent 2c698c2 commit cc815e0

8 files changed

Lines changed: 185 additions & 18 deletions

File tree

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ Use mutable classes with fields that can be set reflectively. Field visibility c
4242
- Fields should be non-primitive boxed/object types (`Integer`, not `int`).
4343
- Value objects can validate with a single-arg constructor.
4444
- `Optional<T>`, `List<T>`, `Set<T>`, and `Map<K,V>` are supported.
45+
- `Class<T>` is supported: provide a class name string and it resolves if assignable to `T`.
4546
- Nested generic combinations are supported (for example `GenericBox<List<GenericItem<String>>>`, `Map<StringConstructedGenericKey<Integer>, List<String>>`).
4647
- Enums are parsed from string names.
4748
- Expose values however you prefer (public fields or getters on private fields).
@@ -144,6 +145,25 @@ GenericConfig cfg = new JsonDeserializer().deserialize(
144145
);
145146
```
146147

148+
## Class reference example
149+
150+
```java
151+
interface Service {}
152+
class ServiceImpl implements Service {}
153+
class ServiceCfg {
154+
public Class<Service> impl;
155+
}
156+
157+
ServiceCfg cfg = new JsonDeserializer().deserialize(
158+
"{\"impl\":\"" + ServiceImpl.class.getName() + "\"}",
159+
ServiceCfg.class
160+
);
161+
162+
assertEquals(ServiceImpl.class, cfg.impl);
163+
```
164+
165+
Any class assignable to `T` is accepted for `Class<T>`.
166+
147167
## Validation and errors
148168

149169
Object mapping and validation are done in one pass. Field errors are collected, then one `ConfigDeserializationException` is thrown with all errors.

errors.md

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class ShowcaseCfg {
4141
public java.util.Map<NonEmptyString, PositiveInteger> limits;
4242
public NoNoArgNested bad;
4343
public Feature feature;
44+
public Class<Worker> workerImpl;
4445
public PositiveDouble ratio;
4546

4647
static class Db {
@@ -67,6 +68,10 @@ class ShowcaseCfg {
6768
public NonEmptyString name;
6869
}
6970

71+
interface Worker {}
72+
73+
static class WorkerImpl implements Worker {}
74+
7075
static class NonEmptyString {
7176
public final String value;
7277
public NonEmptyString(String v) {
@@ -123,6 +128,7 @@ Failing config (JSON):
123128
"x": "ok"
124129
},
125130
"feature": {},
131+
"workerImpl": "java.lang.String",
126132
"ratio": 1
127133
}
128134
```
@@ -151,6 +157,7 @@ Expected path segments and error kinds include:
151157
- `["limits", "[bad]"]` -> `CtorRejected`
152158
- `["bad"]` -> `NoNoArgCtor`
153159
- `["feature", "name"]` -> `MissingRequiredField`
160+
- `["workerImpl"]` -> `ClassRefNotAssignable`
154161
- `["ratio"]` -> `NoOneArgCtor`
155162

156163
`ex.getMessage()` output:
@@ -170,6 +177,7 @@ $
170177
├─ bad -> No no-arg constructor for nested object type: ShowcaseCfg$NoNoArgNested
171178
├─ feature
172179
| └─ name -> Missing required field (no default value).
180+
├─ workerImpl -> Class java.lang.String is not assignable to ShowcaseCfg$Worker
173181
└─ ratio -> No 1-arg constructor on ShowcaseCfg$PositiveDouble accepting java.lang.Integer
174182
```
175183

@@ -185,7 +193,27 @@ A field type resolves to a `Type` that is neither `Class<?>` nor `ParameterizedT
185193
How to fix:
186194
Use supported field declarations (`Class<?>` and parameterized types with concrete raw classes, including nested generics).
187195

188-
### 2) `UnsupportedParameterizedRaw`
196+
### 2) `UnresolvedTypeVariable`
197+
Message:
198+
`Unresolved generic type variable: <TypeVariable>`
199+
200+
Real trigger:
201+
A field resolves to a generic type variable that cannot be resolved to a concrete type in the target object graph.
202+
203+
How to fix:
204+
Use concrete generic types in deserialization targets.
205+
206+
### 3) `WildcardTypeNotSupported`
207+
Message:
208+
`Wildcard generic types are not supported here: <WildcardType>`
209+
210+
Real trigger:
211+
A wildcard type appears in a place where mapping requires a concrete resolved type.
212+
213+
How to fix:
214+
Use concrete generic arguments for deserialization target fields.
215+
216+
### 4) `UnsupportedParameterizedRaw`
189217
Message:
190218
`Unsupported parameterized raw type: <raw>`
191219

@@ -195,7 +223,7 @@ Parameterized raw type is not a `Class<?>`.
195223
How to fix:
196224
Use normal class-based generic declarations.
197225

198-
### 3) `PrimitiveNotSupported`
226+
### 5) `PrimitiveNotSupported`
199227
Message:
200228
`Primitive field types are not supported: <primitive>`
201229

@@ -205,7 +233,7 @@ A target field is primitive (`int`, `boolean`, etc.).
205233
How to fix:
206234
Use boxed types (`Integer`, `Boolean`, etc.).
207235

208-
### 4) `EnumExpectedString`
236+
### 6) `EnumExpectedString`
209237
Message:
210238
`Enum expects string name, got: <type>`
211239

@@ -215,7 +243,7 @@ Enum field receives non-string scalar or non-scalar value.
215243
How to fix:
216244
Provide enum as a string value.
217245

218-
### 5) `EnumUnknown`
246+
### 7) `EnumUnknown`
219247
Message:
220248
`Unknown enum value '<value>' for <EnumClass>. Valid values: [A, B, ...]`
221249

@@ -225,7 +253,7 @@ String value does not match any enum constant name.
225253
How to fix:
226254
Use a valid enum constant name exactly.
227255

228-
### 6) `ExpectedScalar`
256+
### 8) `ExpectedScalar`
229257
Message:
230258
`Expected primitive (string/number/bool), got: <type>`
231259

@@ -235,7 +263,7 @@ Leaf/value-object target receives table/array/object instead of scalar.
235263
How to fix:
236264
Provide scalar input or change Java field type to object/collection.
237265

238-
### 7) `MapExpected`
266+
### 9) `MapExpected`
239267
Message:
240268
`Expected table for Map, got: <type>`
241269

@@ -245,7 +273,7 @@ Real trigger:
245273
How to fix:
246274
Provide object/table-like input.
247275

248-
### 8) `CollectionExpected`
276+
### 10) `CollectionExpected`
249277
Message:
250278
`Expected table/array for <CollectionType>, got: <type>`
251279

@@ -255,7 +283,7 @@ Real trigger:
255283
How to fix:
256284
Provide list/array/table-like input.
257285

258-
### 9) `MissingRequiredField`
286+
### 11) `MissingRequiredField`
259287
Message:
260288
`Missing required field (no default value).`
261289

@@ -265,7 +293,7 @@ Key is missing and field has no default and no optional/missing adapter fallback
265293
How to fix:
266294
Provide key, set a default, or change field to `Optional<T>`.
267295

268-
### 10) `NoOneArgCtor`
296+
### 12) `NoOneArgCtor`
269297
Message:
270298
`No 1-arg constructor on <Type> accepting <ScalarType>`
271299

@@ -275,7 +303,7 @@ Leaf value mapping needs value-object construction, but constructor signature do
275303
How to fix:
276304
Add matching one-arg constructor or change input scalar type.
277305

278-
### 11) `CtorRejected`
306+
### 13) `CtorRejected`
279307
Message:
280308
`Value [<value>] rejected by <Type>: <reason>`
281309

@@ -288,7 +316,7 @@ Fix input value or constructor validation logic.
288316
Example:
289317
`Value [-1] rejected by PositiveInteger: must be > 0`
290318

291-
### 12) `CtorCallFailed`
319+
### 14) `CtorCallFailed`
292320
Message:
293321
`Failed calling constructor for <Type>: <reason>`
294322

@@ -298,7 +326,7 @@ One-arg constructor invocation fails reflectively for non-validation reasons.
298326
How to fix:
299327
Check constructor accessibility/reflective constraints and type assumptions.
300328

301-
### 13) `NoNoArgCtor`
329+
### 15) `NoNoArgCtor`
302330
Message:
303331
`No no-arg constructor for nested object type: <Type>`
304332

@@ -308,7 +336,7 @@ Object mapping requires a no-arg constructor and none exists.
308336
How to fix:
309337
Add a no-arg constructor or change model shape.
310338

311-
### 14) `CtorFailed`
339+
### 16) `CtorFailed`
312340
Message:
313341
`Constructor failed for <Type>: <reason>`
314342

@@ -318,7 +346,7 @@ No-arg constructor exists but throws while instantiating nested object.
318346
How to fix:
319347
Avoid throwing from construction path used by object mapping.
320348

321-
### 15) `InstantiateFailed`
349+
### 17) `InstantiateFailed`
322350
Message:
323351
`Failed to instantiate <Type>: <reason>`
324352

@@ -328,7 +356,7 @@ Reflective no-arg instantiation fails (for example abstract type or reflection f
328356
How to fix:
329357
Use concrete instantiable field types.
330358

331-
### 16) `FieldSetAccess`
359+
### 18) `FieldSetAccess`
332360
Message:
333361
`Failed to set field (access): <reason>`
334362

@@ -338,7 +366,7 @@ Reflection cannot assign field due to access/security restriction at runtime.
338366
How to fix:
339367
Allow reflective field access in runtime environment.
340368

341-
### 17) `FieldSetTypeMismatch`
369+
### 19) `FieldSetTypeMismatch`
342370
Message:
343371
`Failed to set field (type mismatch): <reason>`
344372

@@ -348,7 +376,7 @@ Bound value type is incompatible with field type.
348376
How to fix:
349377
Align field declaration with actual bound value type.
350378

351-
### 18) `FieldAccessSetup`
379+
### 20) `FieldAccessSetup`
352380
Message:
353381
`Failed to access field reflectively: <reason>`
354382

@@ -358,7 +386,37 @@ Runtime blocks reflective access setup (`setAccessible(true)`), for example modu
358386
How to fix:
359387
Open module/package for reflection, or use a runtime that allows reflective access for your config model types.
360388

361-
### 19) `FieldReadAccess`
389+
### 21) `ClassRefExpectedString`
390+
Message:
391+
`Class reference expects string class name, got: <type>`
392+
393+
Real trigger:
394+
A `Class<T>` field receives a non-string value.
395+
396+
How to fix:
397+
Provide the class as a fully qualified string name.
398+
399+
### 22) `ClassRefNotFound`
400+
Message:
401+
`Class not found: <className>`
402+
403+
Real trigger:
404+
A `Class<T>` field receives a string class name that cannot be loaded.
405+
406+
How to fix:
407+
Use a valid class name available on the application classpath.
408+
409+
### 23) `ClassRefNotAssignable`
410+
Message:
411+
`Class <ActualType> is not assignable to <ExpectedType>`
412+
413+
Real trigger:
414+
A `Class<T>` field resolves to a class that is not assignable to `T`.
415+
416+
How to fix:
417+
Use a class name that resolves to `T` or a subtype of `T`.
418+
419+
### 24) `FieldReadAccess`
362420
Message:
363421
`Failed to read field default value: <reason>`
364422

modules/deserializer/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Language modules convert native parse trees to `ConfigValue`/`ConfigTable`, then
99
- map config trees to Java objects
1010
- apply defaults and optional semantics
1111
- parse enums and generic containers (`Optional`, `List`, `Set`, `Map`)
12+
- resolve `Class<T>` references from class-name strings (assignable to `T`)
1213
- perform constructor-based leaf validation
1314
- aggregate path-based errors into one exception
1415

@@ -27,6 +28,7 @@ implementation "org.msuo:deserializer:<version>"
2728
- Missing required fields fail unless a default already exists.
2829
- `Optional<T>` maps missing/nil to `Optional.empty()`.
2930
- Nested generics are supported across object graphs and containers.
31+
- `Class<T>` fields are supported when config provides a string class name.
3032

3133
Example:
3234

@@ -43,6 +45,18 @@ class GenericConfig {
4345
}
4446
```
4547

48+
`Class<T>` example:
49+
50+
```java
51+
interface Service {}
52+
final class ServiceImpl implements Service {}
53+
class Cfg {
54+
public Class<Service> impl;
55+
}
56+
```
57+
58+
Input value for `impl` must be a string class name, and the resolved class must be assignable to `Service`.
59+
4660
## For language implementers
4761

4862
`Deserializer` defines the required API:

modules/groovy2java/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,21 @@ String groovy = "return [foo: [value: [[payload: 'a']]], values: [k1: ['x', 'y']
113113
GenericConfig cfg = new GroovyDeserializer().deserialize(groovy, GenericConfig.class);
114114
```
115115

116+
## Class references
117+
118+
```java
119+
interface Service {}
120+
final class ServiceImpl implements Service {}
121+
class Cfg {
122+
public Class<Service> impl;
123+
}
124+
125+
String groovy = "return [impl: '" + ServiceImpl.class.getName() + "']";
126+
Cfg cfg = new GroovyDeserializer().deserialize(groovy, Cfg.class);
127+
128+
assertEquals(ServiceImpl.class, cfg.impl);
129+
```
130+
116131
## Null semantics
117132

118133
```java

modules/json2java/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,21 @@ String json = "{\"foo\":{\"value\":[{\"payload\":\"a\"}]},\"values\":{\"k1\":[\"
110110
GenericConfig cfg = new JsonDeserializer().deserialize(json, GenericConfig.class);
111111
```
112112

113+
## Class references
114+
115+
```java
116+
interface Service {}
117+
final class ServiceImpl implements Service {}
118+
class Cfg {
119+
public Class<Service> impl;
120+
}
121+
122+
String json = "{\"impl\":\"" + ServiceImpl.class.getName() + "\"}";
123+
Cfg cfg = new JsonDeserializer().deserialize(json, Cfg.class);
124+
125+
assertEquals(ServiceImpl.class, cfg.impl);
126+
```
127+
113128
## Null semantics
114129

115130
```java

modules/lua2java/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,21 @@ String lua = "return { foo = { value = { { payload = 'a' } } }, values = { k1 =
113113
GenericConfig cfg = new LuaDeserializer().deserialize(lua, GenericConfig.class);
114114
```
115115

116+
## Class references
117+
118+
```java
119+
interface Service {}
120+
final class ServiceImpl implements Service {}
121+
class Cfg {
122+
public Class<Service> impl;
123+
}
124+
125+
String lua = "return { impl = '" + ServiceImpl.class.getName() + "' }";
126+
Cfg cfg = new LuaDeserializer().deserialize(lua, Cfg.class);
127+
128+
assertEquals(ServiceImpl.class, cfg.impl);
129+
```
130+
116131
## Nil semantics
117132

118133
```java

0 commit comments

Comments
 (0)