# `PB.Schema`
[🔗](https://github.com/hansihe/pb_ex/blob/v0.2.0/pb/lib/pb/schema.ex#L1)

Embeds a compiled PB schema into a module at compile time.

This is the compile-time counterpart to the runtime flow of reading a
descriptor set, calling `PB.decode_descriptor_set/1`, and then
`PB.compile/1`. Runtime schema maps remain fully supported; use this
module when a schema is stable enough to travel with the compiled BEAM code.

## Descriptor file

    defmodule MyApp.Schema do
      use PB.Schema, descriptor: "priv/proto/schema.binpb"
    end

The descriptor file must be a binary `google.protobuf.FileDescriptorSet`, such
as one produced by:

    protoc --descriptor_set_out=priv/proto/schema.binpb --include_imports your.proto

Relative descriptor paths are resolved from the compiler working directory
(the Mix project root during normal Mix compilation). The generated module is
marked with `@external_resource`, so Mix recompiles it when the descriptor file
changes.

Add `:projections` to attach message-level adapters and structural term
representations to the embedded schema:

    defmodule MyApp.Schema do
      use PB.Schema,
        descriptor: "priv/proto/schema.binpb",
        projections: [
          {:"google.protobuf.Timestamp", adapter: MyApp.Timestamp.adapter()},
          {:"my.app.User", struct: MyApp.User},
          {:"my.app.Wrapper", unwrap: :value, preserved_unknown_fields: :reject},
          {:"my.app.Event", oneofs: [data: [representation: :identity]]}
        ]
    end

Structural projections can also be declared in proto source with the
`elixir.pb.v1` custom options. Use compile-time `:projections` only when
attaching adapters or compiling overrides for third-party schemas or
generated descriptor sets you do not own.

## Descriptor set map

For hand-authored schemas or tests, pass the decoded descriptor set map
directly:

    defmodule MyApp.Schema do
      use PB.Schema,
        descriptor_set: %{
          file: [
            %{
              name: "greeter.proto",
              package: :greeter,
              syntax: :proto3,
              message_type: [
                %{
                  name: :HelloRequest,
                  field: [
                    %{name: :name, number: 1, type: :TYPE_STRING, label: :LABEL_OPTIONAL}
                  ]
                }
              ]
            }
          ]
        }
    end

## Generated API

A schema module exposes:

  * `__pb_schema__/0` — callback returning the embedded compiled schema.
  * `schema/0` — public convenience alias for the callback.
  * `encode/2,3` and `encode!/2,3` — call `PB.encode/4` and `PB.encode!/4`.
  * `encode_iodata/2,3` and `encode_iodata!/2,3` — call `PB.encode_iodata/4`
    and `PB.encode_iodata!/4` for zero-copy iodata output.
  * `decode/2,3` and `decode!/2,3` — call `PB.decode/4` and `PB.decode!/4`.
  * `normalize/2,3` and `normalize!/2,3` — call `PB.normalize/4` and
    `PB.normalize!/4`.

The top-level `PB`, `PB.JSON`, and `PB.TextFormat` entry points accept the
schema module directly in place of a compiled schema map.

## Plugins

Add `:plugins` to compile `PB.Schema.Plugin` data into the embedded schema,
as with `PB.compile/2`. A plugin may also add functions to the schema module:

    defmodule MyApp.Schema do
      use PB.Schema, descriptor: "priv/proto/schema.binpb", plugins: [MyPlugin]
    end

The schema is built once when the module is loaded and kept in
`:persistent_term`, so a schema module cannot declare its own `@on_load`.

## Missing imports

The descriptor set must contain the files its files import. Pass
`allow_missing_imports: true` to compile one that deliberately lacks some,
as with `PB.compile/2` (see "Imports" there).

## Introspection

This module also exposes lookup helpers that return public `PB.Schema.Info`
structs — use these instead of reading the compiled-schema map directly:

  * `list_messages/1`, `fetch_message/2`, `message!/2`
  * `list_enums/1`, `fetch_enum/2`, `enum!/2`
  * `list_services/1`, `fetch_service/2`, `service!/2`
  * `list_extensions/1`, `fetch_extension/2`, `extension!/2`

Every lookup accepts either a compiled schema map or a `use PB.Schema`
module. The bang variants raise `PB.SchemaError` (with `kind: :unknown_*` and
`operation: :introspect`) on miss; the `fetch_*` variants return
`{:ok, info} | :error`. The returned Info structs are the public stable
surface — the compiled-schema internals may change without notice.

Fields and messages carry their declared options; `option/2` reads a custom
option from one.

# `enum!`

```elixir
@spec enum!(PB.schema_source(), PB.enum_name()) :: PB.Schema.Info.Enum.t()
```

Looks up an enum by fully-qualified name, raising `PB.SchemaError` with
`kind: :unknown_enum` if the enum is not in the schema.

# `extension!`

```elixir
@spec extension!(PB.schema_source(), PB.extension_name()) :: PB.Schema.Info.Field.t()
```

Looks up an extension by fully-qualified name, raising `PB.SchemaError` with
`kind: :unknown_extension` if the extension is not in the schema.

# `fetch_enum`

```elixir
@spec fetch_enum(PB.schema_source(), PB.enum_name()) ::
  {:ok, PB.Schema.Info.Enum.t()} | :error
```

Looks up an enum by fully-qualified name, returning `{:ok,
%PB.Schema.Info.Enum{}}` or `:error`.

# `fetch_extension`

```elixir
@spec fetch_extension(PB.schema_source(), PB.extension_name()) ::
  {:ok, PB.Schema.Info.Field.t()} | :error
```

Looks up an extension by fully-qualified name, returning `{:ok,
%PB.Schema.Info.Field{}}` or `:error`.

# `fetch_message`

```elixir
@spec fetch_message(PB.schema_source(), PB.message_name()) ::
  {:ok, PB.Schema.Info.Message.t()} | :error
```

Looks up a message by fully-qualified name, returning `{:ok,
%PB.Schema.Info.Message{}}` or `:error`.

# `fetch_service`

```elixir
@spec fetch_service(PB.schema_source(), PB.service_name()) ::
  {:ok, PB.Schema.Info.Service.t()} | :error
```

Looks up a service by fully-qualified name, returning `{:ok,
%PB.Schema.Info.Service{}}` or `:error`.

# `list_enums`

```elixir
@spec list_enums(PB.schema_source()) :: [PB.enum_name()]
```

Returns the fully-qualified names of every enum in `schema_source`, sorted
alphabetically.

# `list_extensions`

```elixir
@spec list_extensions(PB.schema_source()) :: [PB.extension_name()]
```

Returns the fully-qualified names of every extension in `schema_source`,
sorted alphabetically.

# `list_messages`

```elixir
@spec list_messages(PB.schema_source()) :: [PB.message_name()]
```

Returns the fully-qualified names of every message in `schema_source`,
sorted alphabetically.

`schema_source` is either a compiled schema map or a module that
`use PB.Schema`.

# `list_services`

```elixir
@spec list_services(PB.schema_source()) :: [PB.service_name()]
```

Returns the fully-qualified names of every service in `schema_source`,
sorted alphabetically.

# `message!`

```elixir
@spec message!(PB.schema_source(), PB.message_name()) :: PB.Schema.Info.Message.t()
```

Looks up a message by fully-qualified name, raising `PB.SchemaError` with
`kind: :unknown_message` if the message is not in the schema.

# `option`

```elixir
@spec option(
  PB.Schema.Info.Field.t() | PB.Schema.Info.Message.t(),
  PB.extension_name()
) :: term()
```

Returns the value of the custom option `extension_name` declared on a field
or message, or `nil` if it isn't set.

    PB.Schema.option(field, :"acme.v1.sensitive")
    #=> true

Standard options are plain keys of `options`, such as
`field.options[:deprecated]`.

# `service!`

```elixir
@spec service!(PB.schema_source(), PB.service_name()) :: PB.Schema.Info.Service.t()
```

Looks up a service by fully-qualified name, raising `PB.SchemaError` with
`kind: :unknown_service` if the service is not in the schema.

---

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