luthor_generator 1.0.0
luthor_generator: ^1.0.0 copied to clipboard
Generate luthor schemas
luthor_generator #
luthor_generator is a package allowing you to generate luthor schemas with
code generation. It is a companion to
the luthor package, and works with
json_serializable, freezed, dart_mappable and hand-written fromJson
factories.
Features #
- Schema Generation - Automatically generate validation schemas from annotated classes
- Type-safe SchemaKeys - Generated constants for defining schemas with compile-time safety
- Type-safe ErrorKeys - Generated constants for accessing validation errors with dot-notation support
- Cross-field Validation - Support for
@WithSchemaCustomValidatorto validate fields against other fields
Generated Code #
For each class annotated with @luthor, luthor_generator creates:
$ClassNameSchema- The validation schema, afinal SchemaValidatortop-level variable named withwithName('ClassName')ClassNameSchemaKeys- Type-safe keys for schema field accessClassNameErrorKeys- Type-safe keys for error handling. A nested model field is a record whose$keyis the field's own key, for exampleUserErrorKeys.address.$keyandUserErrorKeys.address.street$ClassNameValidate()- Validates any input, such as decoded JSON, and converts it withfromJson(orClassNameMapper.fromMapfordart_mappable). It returns aValidationResult<ClassName>validateSelf()- An extension method that validates an instance. It is generated only when the class has atoJsonmethod (including the onefreezedgenerates) or is adart_mappableclass
A model needs a fromJson factory, constructor or static method, or a
@MappableClass annotation. The schema follows the constructor the
serializer uses: the one named in @JsonSerializable(constructor: ...), the
@MappableConstructor, the unnamed constructor, or else the first public
constructor with parameters.
Classes used by a model's fields but not annotated with @luthor get a
private schema (_$ClassNameSchema) in the library that uses them. Nested
schemas are always referenced lazily, so recursive and mutually recursive
models work without @luthorForwardRef, which is deprecated, still accepted,
and has no effect.
No build.yaml is needed. The builder applies source_gen's combining
builder itself, so you only need a part 'file.g.dart'; directive, even when
you don't use json_serializable.
Field keys and annotations #
Validation annotations can sit on the constructor parameter or on the field,
including fields reached through super. parameters. Schema keys follow the
serializer:
@JsonKey(name: ...)and@JsonSerializable(fieldRename: ...), on the class or, forfreezed, on the factory constructor@MappableField(key: ...)and@MappableClass(caseStyle: ...)
Fields excluded with @JsonKey(includeFromJson: false) are left out of the
schema. Fields with a constructor default, a freezed @Default or a
@JsonKey(defaultValue: ...) are optional.
For a @JsonSerializable class, public fields with a setter that are not
constructor parameters are schema fields too, since json_serializable
assigns them after calling the constructor. They are required unless they are
nullable or have a @JsonKey(defaultValue: ...).
Unknown keys #
A generated schema strips keys it doesn't declare before fromJson runs, so
every key the serializer reads must be a schema field. When the generator
cannot name every key the serializer may read, the schema keeps unknown keys
with .passthrough() instead:
- A field with
@JsonKey(readValue: ...), whose function receives the whole map. The field itself is validated as an optionall.any(). - A private field with
@JsonKey(includeFromJson: true), since a record field inSchemaKeyscannot be private. - A
@MappableClass(hook: ...), whose hook may read the whole map.
A hand-written fromJson must read only the keys of its constructor
parameters, or the settable fields above.
Global options in another builder's build.yaml, such as
json_serializable's field_rename, are not read. Put them on the class.
An annotation on a field type it doesn't apply to, such as @IsEmail on an
int or @HasLength on a List, fails generation. Validation annotations
are applied in the order they are written.
Supported field types #
| Dart type | Validator |
|---|---|
String, int, double, num, bool |
l.string(), l.int(), l.double(), l.num(), l.bool() |
DateTime |
l.string().dateTime() |
Uri |
l.string() |
| Enums | l.oneOf([...]) with the serialized values, honouring @JsonValue, @JsonEnum(fieldRename:, valueField:), @MappableEnum and @MappableValue |
List, Set, Iterable |
l.list(element) |
Map |
l.map() with key and value validators |
dynamic, Object, Object? |
l.any() |
File (dart:io), XFile (cross_file) |
l.file() |
A class with @luthor, a fromJson or @MappableClass |
Its schema |
JSON object keys are always strings, so Map keys of type int, double,
num and BigInt are validated as strings that parse to that type, with
l.string().custom((key) => int.tryParse(key) != null). DateTime, Uri,
enum (l.oneOf() of the serialized values as strings) and String keys are
also supported. A key that fails is reported as an invalid key of the map.
A json_serializable enum field with @JsonKey(unknownEnumValue: ...)
generates l.string() instead of l.oneOf([...]), because
json_serializable maps a value it does not know to the fallback instead of
throwing. On a List, Set or Iterable of an enum, this applies to each
element. An enum whose serialized values are integers generates l.int().
Non-nullable fields still get .required().
A double field generates l.double(), which rejects JSON integers. If your
backend may send 1 instead of 1.0, type the field as num.
A field with a JsonConverter (on the field, on the class, or in
@JsonSerializable(converters: ...)) or a @JsonKey(fromJson: ...) function
is validated as l.any(), because luthor cannot know the shape of the raw
JSON value. Only @WithCustomValidator and @WithSchemaCustomValidator
apply to such fields.
Not supported yet #
These fail generation with an error that names the class or field:
freezedunions (classes with more than one redirecting factory)- Generic models, and fields whose type is a generic class or a type parameter
- Records, function types and types without a luthor validator, such as
DurationorBigIntvalues (give these aJsonConverter) - Validators for list elements (#71)
See the documentation for more information.