Schema Parser & Code Generator
A language agnostic model generator for a subset of JSON schemas. (Rust only for now, next planned: python + pydantic)
How to Use
Check out the codegen-test crate to see how it can be used to generate your code at compile time.
Schema constraints
While writing the parser and deserializer I put some constraints of possible schemas, this only allows a subset of possible asyncapi schema definitions.
- Every top-level item in the
components -> schemaspart of the document needs to be an actual schema, this would not be allowed:
components:
schemas:
MyEntity:
$ref: '#/components/schemas/AnotherEntity'
- Every top-level schema can only be one of the following:
[AllOf, OneOf, AnyOf, type: object], top-levelarraytypes don't currently work, in your asyncapi schema I'd recommend creating an anonymous schema in themessagespart of the specification and creating the specificitemtype for thearrayitems in thecomponents/schemassection such that your code will have the type for the items and you can easily deserialize payloads by wrapping it in language specific collections. - Currently enums only work with String values, even if they're supported at deserialization/parsing at generation time numerical enums will throw errors as I haven't created a specific type to distinguish them from Literal enums.
- Every time a
constvalue is specified there must be atypewith it. - Currently only integers are supported and any
formatdirective is simply ignored - Due to how the current implementation of
AllOfworks duplicate properties will cause errors in Rust, the current codegenerator just takes the combined schemas, creates anAnonymousEntityfor each (or a named one iftitleis set) and then combines them with#[serde(flatten)]in a struct, this will cause the deserialization to fail if the combined schemas define overlapping properties. (Fixing this is on my roadmap but not a priority, in OOP languages my codegenerator will simply extend allAllOfschema classes and duplicate properties will be handled by the inheritance of the programming language)
Sidenote for Rust users
- For
OneOfschemas with a specificdiscriminatorset it currently only works if the discriminator matches the name of the entity (For anonymous entity set thetitleproperty for a deterministic name), otherwise if you use special values for the discriminator inside ofconstfields you need to omit thediscriminatorfor now and just use the#[serde(untagged)]enum that is generated,constfields will be respected through the use of themonostatecrate. AllOfschemas currently don't merge properties, out of lazyness they create struct for inner schemas and then put them in a single struct through#[serde(flatten)]. (Out of simplicity I may use a solution like this in other languages, having a named empty class inherit from anonymous/named structs for its fields)
Planned
- A CLI tool for code generation
- Python
pydanticmodel generator - A protobuf generator
Issues
The deserializer defines untagged enums with monostate::MustBe for the deserialization of a schema, this leads to quite unhelpful error messages when you schema does not match, most of the errors are Did not match any variant in SchemaDef
Sample
Asyncapi schema definitions:
RequestBase:
type: object
additionalProperties:
type: array
properties:
id:
type: string
description: "correlation id to match request and response"
kind:
type: string
const: request
tupleProp:
type: array
items: false
prefixItems:
- type: string
- type: object
required:
- id
GetUser:
description: TODO
allOf:
- $ref: '#/components/schemas/RequestBase'
- type: object
title: GetUserInner
properties:
data:
title: GetUserData
type: object
properties:
userId:
type: string
name:
type: string
required:
- userId
required:
- data
- event
DeleteUser:
description: TODO
allOf:
- $ref: '#/components/schemas/RequestBase'
- type: object
title: DeleteUserInner
properties:
data:
title: DeleteUserData
type: object
properties:
userId:
type: string
required:
- userId
required:
- data
- event
SampleRequestPayload:
description: "SampleRequestPayload"
discriminator: event
oneOf:
- $ref: '#/components/schemas/GetUser'
- $ref: '#/components/schemas/DeleteUser'
Generated rust code: