# `Chronicle.Schemas.JsonSchemaGenerator`
[🔗](https://github.com/Cratis/Chronicle.Elixir/blob/main/lib/chronicle/schemas/json_schema_generator.ex#L4)

Generates JSON schemas for struct-backed event types and read models.

This is the single source of truth for schema generation shared by event type
registration (`Chronicle.EventTypes`) and read model registration
(`Chronicle.Registration.Coordinator`). Generating both through one path keeps
property typing consistent and ensures compliance (PII) and security
(Encrypted) metadata are always embedded the same way.

PII and Encrypted are each resolved from two places, mirroring the C#
client's `PIIMetadataProvider`/`EncryptedMetadataProvider`:

  * **Property-level** — a field marked with `Chronicle.Compliance.pii/1,2`
    or `Chronicle.Confidentiality.encrypted/1,2,3` on the event type or read
    model module itself.
  * **Type-level** — a field whose value is a `Chronicle.Concept` module
    that declared `pii/0,1` or `encrypted/0,1` on itself (see that module's
    documentation). The concept's classification travels with it
    automatically to every event and read model that uses it, without
    repeating the annotation at every call site.

PII and Encrypted are deliberately separate mechanisms sharing only this
generator: a field can carry one or the other, never both — see
`Chronicle.Confidentiality.PiiAndEncryptedCombinedNotSupported`, raised
here the moment both resolve for the same leaf.

Nested structs (including concepts) are descended into recursively, and a
`Chronicle.Concept` field is described by the schema of its **wrapped
value**, not the wrapper struct — mirroring the concept's own
`Jason.Encoder` implementation, so the schema always matches what actually
goes on the wire.

Compliance and security metadata are always written on schema **leaves**,
never on a container (`"type" => "object"`) node: a marker left on a
container would make Chronicle hand the whole JSON object to the handler
and store one opaque ciphertext string where the schema still says
`object`, so a released value comes back as a string instead of an object
and the read model fails to materialize. An array is the one exception —
metadata declared on the array *field itself* stays on the array (the
collection is blob-encrypted as a whole); metadata resolved from the
array's *element type* (a list of PII or Encrypted concepts) is attached to
the `"items"` schema instead, which is already a leaf. A leaf reachable
through more than one path (for example a PII concept nested inside a value
object whose own field also carries a property-level `pii/1,2`) is only
ever marked once.

# `generate`

```elixir
@spec generate(module(), keyword()) :: String.t()
```

Generates a JSON schema string for a struct module.

## Options

  * `:key_transform` — how struct field names are rendered as schema property
    names. `:camel` renders `snake_case` as `camelCase` (used for event types,
    whose content is serialized as camelCase); `:identity` keeps the field
    name as-is (used for read models, whose stored properties are snake_case).
    Defaults to `:identity`.

PII fields are discovered through the module's `__chronicle_pii__/0` accessor
when present, and recursively through the `__chronicle_pii__/0` of any field's
value that is itself a struct (a `Chronicle.Concept`, or a plain nested value
object holding one).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
