# `Snodo.Tool.Simple`
[🔗](https://github.com/joshrotenberg/snodo/blob/v0.4.1/lib/snodo/tool/simple.ex#L1)

Opt-in argument DSL for tools with straightforward object input schemas.

`Snodo.Tool.Simple` builds the same raw JSON Schema returned by `Snodo.Tool` and
leaves `call/2` untouched. Argument maps therefore retain protocol-native
string keys, and simple tools register in `Snodo.Router` exactly like raw tools.

    defmodule Search do
      use Snodo.Tool.Simple,
        name: "search",
        description: "Search packages",
        additional_properties: false

      argument("query", :string, required: true, min_length: 1)
      argument("page", :integer, minimum: 1)
      argument("sort", :string, enum: ["name", "downloads"])
      argument("tags", {:array, :string}, unique_items: true)

      @impl true
      def call(%{"query" => query} = arguments, _context) do
        {:ok, Snodo.Result.text("searching for #{query} on page #{arguments["page"] || 1}")}
      end
    end

An argument type may be one of the JSON primitive atoms, `{:array, type}`, or
a raw property-schema map. The `:schema` option merges arbitrary JSON Schema
keywords into a generated property, providing a local escape hatch without
abandoning the concise form. The raw `Snodo.Tool` DSL remains available when
the input root itself needs complete hand-authored control.

An `:object` or `{:array, :object}` argument may take a `do` block of
further `argument` declarations, which become the properties of that object
or of each array item. `output_schema/1` with a `do` block builds the output
schema from the same declarations:

    defmodule Order do
      use Snodo.Tool.Simple, name: "order", description: "Place an order"

      argument "customer", :object, required: true do
        argument "id", :string, required: true
        argument "email", :string
      end

      argument "lines", {:array, :object}, required: true, min_items: 1 do
        argument "sku", :string, required: true
        argument "quantity", :integer, required: true, minimum: 1
      end

      output_schema do
        argument "order_id", :string, required: true
        argument "total", :number, required: true
      end

      @impl true
      def call(_arguments, _context) do
        {:ok, Snodo.Result.structured(%{"order_id" => "o-1", "total" => 12.5})}
      end
    end

Both compile to plain JSON Schema maps, the same ones `Snodo.Tool.input_schema/1`
and `Snodo.Tool.output_schema/1` accept.

The `:title`, `:icons`, and `:metadata` options set the corresponding
`Snodo.Tool` definition fields. They are validated when the module compiles.
`:wrap` applies `Snodo.Component.Wrap` modules around `call/2`.

# `argument`
*macro* 

Declares one property of the tool's input schema.

`name` is the property name as a string. `type` is a JSON type atom
(`:string`, `:integer`, `:number`, `:boolean`, `:array`, `:object`, or
`:null`), `{:array, type}` for an array whose `"items"` has that type, or a
raw property-schema map.

Options:

  * `:required` - when `true`, adds `name` to the schema's `"required"`
    list. Defaults to `false`.
  * `:description`, `:default`, `:enum`, `:pattern` - set the JSON Schema
    keyword of the same name.
  * `:min_length`, `:max_length`, `:min_items`, `:max_items`, `:minimum`,
    `:maximum`, `:exclusive_minimum`, `:exclusive_maximum`, `:unique_items` -
    set the camel-case JSON Schema keyword, such as `"minLength"`.
  * `:schema` - a map of other JSON Schema keywords, merged into the
    property last, so it overrides the generated keys.

## Nested objects

When `type` is `:object` or `{:array, :object}`, a `do` block may follow.
Each `argument` inside it declares a property of that object, or of the
object in `"items"` for an array, and may itself take a block. `:required`
inside the block adds the name to the nested object's `"required"` list.
A block argument also accepts `:additional_properties`, a boolean or schema
map set as the nested object's `"additionalProperties"`. The other options,
including `:schema`, apply to the property itself, so for an array they sit
beside `"items"`.

    argument "filters", {:array, :object}, min_items: 1, additional_properties: false do
      argument "field", :string, required: true
      argument "value", :string
    end

A block argument's `:schema` cannot set the keys the block generates:
`"properties"` and `"required"` for `:object`, and `"items"` for
`{:array, :object}`.

An empty or repeated name, an unknown or repeated option, an option value
of the wrong type, or a block on any other type is a compile error.

# `output_schema`
*macro* 

Sets the tool's output schema, either from a JSON Schema map or from a
`do` block of `argument` declarations.

With a map, this is `Snodo.Tool.output_schema/1`:

    output_schema(%{"type" => "object", "properties" => %{"version" => %{"type" => "string"}}})

With a block, the declarations take the same forms as the input schema,
including nested blocks, and produce an object schema:

    output_schema do
      argument "version", :string, required: true
      argument "published_at", :string
    end

`:additional_properties` and `:schema` apply to the output root as the
matching `use Snodo.Tool.Simple` options apply to the input root. They go in
the same keyword list as the block:

    output_schema(
      additional_properties: false,
      do:
        (
          argument("version", :string, required: true)
          argument("published_at", :string)
        )
    )

With an output schema, `call/2` must return structured content; see
`Snodo.Tool.output_schema/1`. Declaring the output schema a second time
when either declaration is a block, or declaring it inside an argument
block, is a compile error.

---

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