# Objects

An object is a fixed set of named fields. The base after `=` says what the
object is for:

```jadpo
type StockTag = Object {  // used inside the application
    amount: Stock
}

type CreateProduct = Input {  // arrives from outside, such as a request body
    name: ProductName
    stock: Stock
}

type ProductView = Output {  // sent back, such as a response
    name: ProductName
    stock: StockTag
}
```

Only an `Input` may leave fields out or give them defaults. A route accepts
only an `Input` and returns only an `Output`. There is no unnamed object: a
nested shape is declared once, with a name, and used by that name.

An object value is written with its type name and every field:

```jadpo
function view(name: ProductName, stock: Stock) -> ProductView {
    return ProductView {
        name: name
        stock: StockTag {
            amount: stock
        }
    }
}
```

Every declared field must be given, and unknown fields are errors. Fields can
hold other objects and lists, nested as deeply as needed.

An object literal never fails on a field. A field accepts its declared type or anything
that widens to it, and when the field adds rules of its own, the declared
ranges must prove the value follows them (`type.field_unproved`). A value
that might break them is built first, on its own line, as the field's type
(below), where its failure is mapped. Only a rule the type writes across
its fields can make a literal fail ("Rules across fields", below):

```jadpo
type Badge = Object {
    name: Text {
        max_length: 12
    }
}

function make_badge(profile: Profile) fails BadgeNameTooLong -> Badge {
    var name: Badge.name = attempt Badge.name(profile.display_name) {
        max_length: BadgeNameTooLong
    }
    return Badge {
        name: name
    }
}
```

## Names from reserved words

A reserved word ([Reserved words](/docs/naming.md#reserved-words))
may name a field in the body of a type or entity, and a variant in the body
of an enum. Other languages' data is full of a field called `type`, and
nothing here can be misread: a field is declared as `word: Type`, set as
`word: value` and read as `thing.word`, and a variant is always written
after its enum's name.

```jadpo
type Kind = Enum {
    library
    package
    type
}
```

```jadpo
use depot/types { ParcelName }

type Haulier = Enum {
    public
    test: {
        for: ParcelName
    }
}
```

```jadpo
use depot/types { Kind, ParcelName }

entity Parcel {
    id: Uuid
    package: ParcelName
    type: Kind
    in: ParcelName?

    policy: {
        create: [Access.public]
        read: [Access.public]
    }
}
```

```jadpo
use depot/entities { Parcel }
use depot/types { Kind }

/// A parcel as clients see it.
type ParcelView = Output {
    pick: [Parcel.package, Parcel.in]
    type: Kind
}
```

```jadpo
use depot/entities { Parcel }
use depot/types { Kind, ParcelView }

query parcels_of(name: Parcel.package, asked: Kind) -> List<ParcelView> {
    from: Parcel
    where: Parcel.package == name
    where: Parcel.type == asked
    order: [Parcel.in asc]
    limit: 20
}
```

```jadpo
use depot/entities { Parcel }
use depot/types { Haulier, Kind, ParcelView, Waybill }
use depot/libraries { notices }

/// What shipping a parcel takes.
type ShipParcel = Input {
    package: Parcel.package
    type: Kind
}

action ship_parcel(input: ShipParcel) -> ParcelView {
    var parcel: Parcel = Parcel.create {
        set: {
            package: input.package
            type: input.type
            in: notices.label(
                Waybill {
                    if: input.package
                    haulier: Haulier.public
                }
            )
        }
    }
    return ParcelView {
        package: parcel.package
        type: parcel.type
        in: parcel.in
    }
}
```

```jadpo
use depot/types { Haulier, ParcelName }

type Waybill = Object {
    if: ParcelName
    haulier: Haulier
}
```

```jadpo
use depot/types { Haulier, ParcelName, Waybill }

library notices {
    /// Who a parcel is for.
    function label(waybill: Waybill) -> ParcelName {
        return match waybill.haulier {
            Haulier.public -> waybill.if
            Haulier.test(details) -> details.for
        }
    }
}
```

```jadpo
use depot/types { ParcelView }
use depot/actions { ShipParcel, ship_parcel }

routes Parcels {
    prefix: /parcels

    /// Ships a parcel.
    POST / {
        authentication: RouteAuthentication.none
        input: ShipParcel
        output: ParcelView
        status: SuccessStatus.created
        run: ship_parcel(input)
    }
}
```

```jadpo
// Generated by Jadpo from the routes of API main; update it with `jadpo api update`, never by hand.
record api 1

GET /openapi.json
    generated openapi
    authentication none
    status 200

POST /parcels
    authentication none
    input ShipParcel (body)
    output ParcelView
    status 201

enum Kind
    variant library
    variant package
    variant type

output ParcelView
    in: Text { min_length: 1, max_length: 80 }?
    package: Text { min_length: 1, max_length: 80 }
    type: Kind

input ShipParcel
    package: Text { min_length: 1, max_length: 80 }
    type: Kind
```

- **What holds it says what it is.** A field is read as `thing.type`, a
  query's condition names it as `Parcel.type`, and a wording or a `match`
  arm names a variant as `Kind.library`.
- **A list of fields may name it alone**, as `unique: { both: [package, type]
  }`, a policy's `read: [for]` and `order: [type desc]` do, since nothing
  else a list holds is one word and nothing more.
- **Elsewhere, alone, the word is still the word.** A rule across fields names
  fields alone, so it can't read one named with a reserved word: `rule r:
  type == kind` is `syntax.expected_name`, and the field is renamed or the
  rule moved into a function. A field called `none` or `true` is the value
  wherever it stands alone, in a list too.
- **Only fields and variants.** A parameter, a path parameter, a `match`
  binding, a variable, a declaration, a
  rule, a `unique:` entry and a setting the language doesn't define still
  can't be named with one.
- **No enum has a `true`, `false` or `none`.** `Kind.none` beside a `Kind?`
  would read as the `none` it isn't, so a variant called one of these is
  `type.reserved_variant_name`, as `all` is. A field may be called one.
- **The wire has the name as written.** Records, OpenAPI and JSON all say
  `type`, with no spelling of their own, and a column is quoted like any
  other. `wire_name:` stays for provider packages, where the provider's
  spelling isn't the Jadpo name
  ([Spellings](/docs/decoding.md#spellings-wire_name)).
- `include`, `pick`, `omit` and `optional`, which aren't reserved words,
  build a type's fields and stay `type.reserved_field_name`.

## Field types

Every field of an object, entity or enum variant is a type of its own, written
`Owner.field`. `Customer.email` is a refinement of `Email`: it can be used
wherever an `Email` is expected, but an `Email` is not automatically a
`Customer.email`, and `Supplier.email` is never one.

```jadpo
entity Customer {
    id: Uuid
    email: Email
}

entity Supplier {
    id: Uuid
    email: Email
}

function contact_address(email: Email) -> Email {
    return email
}

function customer_contact(customer: Customer) -> Email {
    return contact_address(customer.email)
}

function supplier_contact(supplier: Supplier) -> Email {
    return contact_address(supplier.email)
}
```

Field types are how identifiers stay apart: `Customer.id` and `Supplier.id`
are both UUIDs, and passing one where the other is expected is
`type.sibling_mismatch`. To move a value into a field type on purpose, use the
field as a constructor, as in `Customer.email(email)`.

A field can add rules of its own in a block after its type:

```jadpo
type Badge = Object {
    label: ProductName {
        max_length: 12
    }
}
```

Reading through a nested object gives the inner object's field type:
`customer.billing_address.postal_code` has type `Address.postal_code`, whether
it came from the billing or the shipping address.

A field's type is always a type: a type's name, a field type such as
`Customer.email`, a package's type, `List<T>`, `T?`, or a type with rules in
a block. A value written there, whether a call (`TicketStatus("open")`), a
literal or an enum's variant (`TicketStatus.open`), is `type.not_a_type`,
naming the field, in every block of fields: an object's, an input's, an
event's, a failure's, a variant's, an entity's and `config { }`'s settings.

## Rules across fields

A field's rules say what one value may be. A rule across fields says how
the values of one object relate: a booking ends after it starts, a
discount is below the price. An `Object`, `Input` or `Output` names each
such rule in its block, and the rule is checked whenever a value of the
type is made.

```jadpo
type Booking = Object {
    starts: Moment
    ends: Moment
    rule ends_after_start: ends > starts
}

function book(starts: Moment, ends: Moment) fails BookingBackwards -> Booking {
    var booking: Booking = attempt Booking {  // nothing proves the rule
        starts: starts
        ends: ends
    } {
        ends_after_start: BookingBackwards
    }
    return booking
}

function book_checked(starts: Moment, ends: Moment) fails BookingBackwards -> Booking {
    if (ends <= starts) {
        reject BookingBackwards
    }
    return Booking {  // the condition proves `ends_after_start`
        starts: starts
        ends: ends
    }
}

test "a booking that ends before it starts is refused" {
    assert book("2026-03-02T10:00:00Z", "2026-03-02T09:00:00Z") fails BookingBackwards
    assert book_checked("2026-03-02T10:00:00Z", "2026-03-02T09:00:00Z") fails BookingBackwards
}
```

- **Written in the type's block.** `rule name: condition`, one per line,
  usually after the fields. A rule's name is lower_snake_case and its own:
  no field or other rule of the type has it, and it is never `invalid`,
  which an `attempt` keeps for every rule (`type.rule_name_taken`). `rule`
  isn't reserved: a field may still be called `rule`.
- **A condition over the type's own fields.** It names the fields bare
  (`ends`) and reads through them (`stay.ends`); it uses literals,
  variants, operators, `match` and the standard operations that always
  give a result from their arguments alone. Nothing else: no function or
  action of the application, constant, setting, clock, random value, key or
  record (`type.rule_outside_fields`). Not even a constant: a rule means
  the same wherever its type is read. It is a `Bool`
  (`type.rule_not_condition`). A field that may be `none` is read with
  `match`, as anywhere else.
- **Checked whenever a value is made,** once every field has its value,
  whether the object stands alone, as a list's item or as a map's entry. A
  value is made by an object literal, a construction, `json.read`, a
  request's body or query string, the output a route test reads,
  `text.parse` of a standard type's text, and a read from the store. Jadpo
  never stores a value that breaks a rule, so one read back from the store
  is a defect ([Entities, stores and migrations](/docs/data.md)).
- **One rule is reported.** The field rules run first, then each rule in the
  order written, and the first one broken is the one reported, so a report
  never depends on more than one rule.
- **A setting never holds an object,** so never one with rules
  (`configuration.type_unsupported`).
- **A query's result type has none** (`query.result_rule`,
  [Reads and writes](/docs/reads-and-writes.md)). A read has no `attempt` to
  map a broken rule, so the type is refused rather than each row checked as
  it is read.
- **An object literal can break one.** Building a type with rules is a
  construction, mapped as `Type(value)` is
  ([Construction failures by rule](/docs/creating-values.md#construction-failures-by-rule)). Each
  rule that neither the declared ranges nor a condition proves needs an arm
  keyed by its name, or `invalid:` covers it.
- **A literal is checked while compiling, or refused.** One whose values
  are all literals is worked out while compiling, so a booking written
  backwards is `type.invalid_literal`. A literal is never kept unchecked:
  text where a standard object type with rules across its parts goes is
  `type.invalid_literal`, since the compiler can't read its text, and any
  other literal a standard object can't be is `type.mismatch`
  ([Rules across a type's parts](/docs/every-standard-type-meets-one-contract.md#rules-across-a-types-parts)).
- **Proof reads the values given.** A rule is read with each field
  replaced by the value given for it, so `ends > starts`, built with
  `starts: begin` and `ends: finish`, is proved after `if (finish <= begin)
  { reject ... }` leaves, or inside `if (finish > begin)`. A comparison
  proves itself between any two values written the same way, instants and
  dates as well as numbers ([Conditions as proof](/docs/creating-values.md#conditions-as-proof)); numbers are proved by
  their declared ranges too, and `and`, `or` and `not` combine as they do
  in a condition. A rule that calls a standard operation or uses `match`
  is never proved this way, so a literal that may break it is always
  attempted.
- **Every failure is `invalid_value`.** The report names the rule
  (`ends_after_start`), never the values. JSON that breaks one, in a
  request or `json.read`, is a problem at the object's path, empty for the
  whole input, whose rule is the rule's name
  ([Data formats](/docs/data-formats-json-csv-tsv-and-json-lines.md)).
- **OpenAPI says them in words.** OpenAPI can't express most rules, so each
  object's schema description lists its rules as written ([HTTP](/docs/http.md)).
- **A rule belongs to the type that writes it.** `include` and `pick` copy
  fields, never rules: a type built from `Booking` writes the rule again if
  it holds there too. Only an `Object`, `Input` or `Output` has rules
  (`type.rule_outside_object`); events, failures, messages, enums and
  refinements have none. A rule on an entity, which the database would
  check and a write would map, is not designed yet.
- **The standard object types have rules too:** an `InstantRange`'s
  `start_not_after_end`, a `PostalAddress`'s `postal_code_of_country`. Each
  is named, proved, attempted and reported as a rule written here is.
  [Rules across a type's parts](/docs/every-standard-type-meets-one-contract.md#rules-across-a-types-parts)
  lists them.

In a rule, numbers are proved by their declared ranges as a field's rules
are, and a field that may be `none` is read with `match`:

```jadpo
type StaySearch = Input {
    arrives: StayDate
    leaves: StayDate
    adults: GuestCount
    children: ChildCount {
        optional: true
    }
    rule leaves_after_arrival: leaves > arrives
    rule children_with_adults: match children {
        some(count) -> count <= adults * 2
        none -> true
    }
}

type Party = Object {
    adults: GuestCount
    children: ChildCount
    rule not_crowded: children <= adults * 6
}

function party(adults: GuestCount, children: ChildCount) -> Party {
    return Party {  // at most 6 children, and at least 6 places: always holds
        adults: adults
        children: children
    }
}
```

A construction whose every rule is proved can't fail, so it takes no
`attempt`, and the error points at the condition that proves it, as it
does for any other construction:

```jadpo
type Moment = Instant {}

type BookingBackwards = Failure {
    kind: FailureKind.invalid_value
}

type Booking = Object {
    starts: Moment
    ends: Moment
    rule ends_after_start: ends > starts
}

function book(starts: Moment, ends: Moment) fails BookingBackwards -> Booking {
    if (ends <= starts) {
        reject BookingBackwards
    }
    var booking: Booking = attempt Booking {
        starts: starts
        ends: ends
    } {
        ends_after_start: BookingBackwards
    }
    return booking
}
```

```text
error: `Booking { ... }` can't fail, so it takes no `attempt`
 --> main.jadpo:17:28
   |
14 |     if (ends <= starts) {
   |         -------------- checked here
17 |     var booking: Booking = attempt Booking {
   |                            ^^^^^^^ nothing to pass on
  reason: The declared ranges and the condition `ends <= starts` prove `Booking { ... }` always follows its type's rules, so building it never fails; `attempt` marks only what can.
  next: Remove `attempt` (agent)
  rule: failure.attempt_infallible
  help: https://jadpo.dev/docs/errors/failure.attempt_infallible

check failed: 1 error, 1 file
```

## A record names its type alone

A record's head is its type's name, never type arguments. `Range { ... }`,
`Feature { ... }` and `FeatureCollection { ... }`, the standard types that take
them, get them from the type the record's place expects, or else from its
values, so `Feature<Point, Pin> { ... }` is
[`type.record_type_arguments`](/docs/errors/type.record_type_arguments.md).
