---
title: "Date Range Rust Host Integration"
slug: "date-range-rust-host-integration"
description: "Guia para hosts Rust publicarem atalhos corporativos de date range como metadata JSON resolvida, sem executar regras de negocio no frontend."
doc_type: "guide"
document_kind: "host-guide"
component: "dynamic-fields"
category: "integration"
audience:
  - "backend"
  - "host"
  - "frontend"
  - "architect"
level: "enterprise"
status: "active"
owner: "praxis-ui"
tags:
  - "dynamic-fields"
  - "date-range"
  - "rust"
  - "metadata"
  - "business-periods"
order: 45
icon: "calendar_month"
toc: true
sidebar: true
search_boost: 1.2
reading_time: 24
estimated_setup_time: 35
version: "1.0"
related_docs:
  - "pdx-material-date-range-json-api"
  - "pdx-inline-date-range-json-api"
  - "dynamic-fields-inline-filter-runtime-contract"
  - "date-range-business-shortcuts-evolution-plan"
keywords:
  - "StaticDateRangePreset"
  - "serde"
  - "inlineQuickPresets.position"
  - "startDate"
  - "endDate"
  - "calculateRange"
last_updated: "2026-07-11"
---

# Date Range Rust Host Integration

## Objetivo

Orientar hosts Rust que publicam metadata Praxis para `pdx-material-date-range`
e `pdx-inline-date-range` com atalhos corporativos resolvidos pelo dominio.

O contrato de runtime Angular aceita built-ins, presets programaticos
TypeScript e presets estaticos serializaveis. Hosts Rust devem publicar apenas
os built-ins e os presets estaticos. Funcoes JavaScript como `calculateRange`
nao pertencem ao JSON publicado pelo backend.

## Pre-requisitos

- Host Rust responsavel por publicar metadata Praxis para schema, recurso ou
  endpoint equivalente.
- Dominio capaz de resolver periodos de negocio antes da serializacao JSON.
- Conhecimento do contrato `pdx-material-date-range` ou
  `pdx-inline-date-range`.
- Pipeline de testes que consiga validar serializacao Rust e consumir a
  metadata em uma superficie Praxis oficial.

## Responsabilidades

| Camada | Responsabilidade |
| --- | --- |
| Rust/backend | Calcular regras eleitorais, legais, fiscais, contratuais, feriados, dias contaveis, autorizacao, confidencialidade e vigencia antes de publicar metadata. |
| Metadata publicada | Transportar somente intervalos resolvidos, labels, descricoes, icones semanticos, tons semanticos e configuracao de layout. |
| Angular/Praxis UI | Materializar o catalogo recebido, preservar constraints do campo, teclado, foco, RTL, tema e payload canonico. |
| Auditoria de negocio | Permanecer no backend/dominio; `shortcutId`/`id` pode apoiar observabilidade de UI, mas nunca substitui as datas. |

O frontend nao executa regras fiscais, eleitorais, juridicas ou de dias uteis
recebidas por JSON. Se a regra muda por calendario, jurisdicao, tenant,
permissao ou feriado, o Rust publica um novo intervalo ja resolvido.

## Contrato JSON

### Built-in

```json
{
  "controlType": "dateRange",
  "shortcuts": ["today", "thisWeek", "thisMonth"]
}
```

Built-ins sao identificadores conhecidos pelo runtime. Use-os apenas quando a
semantica relativa for aceitavel para o campo.

### Preset estatico

```json
{
  "id": "periodo-votacao-2026",
  "label": "Periodo de votacao 2026",
  "description": "Janela oficial resolvida pelo dominio eleitoral.",
  "startDate": "2026-07-06",
  "endDate": "2026-10-04",
  "timeZone": "America/Sao_Paulo",
  "icon": "how_to_vote",
  "tone": "info",
  "effectiveFrom": "2026-06-01",
  "effectiveTo": "2026-10-04"
}
```

Campos suportados:

| Campo | Obrigatorio | Semantica |
| --- | --- | --- |
| `id` | Sim | Identificador estavel do atalho para estado ativo e observabilidade. |
| `label` | Sim | Texto publicado pelo dominio ou por i18n governado do host. |
| `startDate` | Sim | Inicio inclusivo do intervalo resolvido. |
| `endDate` | Sim | Fim inclusivo do intervalo resolvido. |
| `timeZone` | Nao | Timezone IANA usado pelo dominio ao resolver o periodo. |
| `icon` | Nao | Nome de icone semantico suportado pelo host/Praxis. |
| `description` | Nao | Explicacao operacional do periodo. |
| `tone` | Nao | `neutral`, `info`, `success` ou `warning`; nunca cor arbitraria. |
| `effectiveFrom` | Nao | Inicio da vigencia da metadata do atalho. |
| `effectiveTo` | Nao | Fim da vigencia da metadata do atalho. |

### Lista mista e composicao inline

```json
{
  "controlType": "inlineDateRange",
  "label": "Periodo",
  "shortcuts": [
    "today",
    {
      "id": "competencia-fiscal-2026-03",
      "label": "Competencia fiscal 03/2026",
      "description": "Periodo fiscal fechado pelo dominio tributario.",
      "startDate": "2026-03-01",
      "endDate": "2026-03-31",
      "timeZone": "America/Sao_Paulo",
      "icon": "account_balance",
      "tone": "success"
    }
  ],
  "inlineQuickPresets": {
    "enabled": true,
    "maxVisible": 4,
    "position": "start"
  },
  "inlineOverlay": {
    "applyMode": "explicit",
    "actions": {
      "apply": { "label": "Aplicar", "appearance": "filled", "colorRole": "primary" },
      "cancel": { "label": "Cancelar", "appearance": "text", "colorRole": "neutral" }
    }
  }
}
```

`inlineQuickPresets.position` aceita:

| Valor | Uso |
| --- | --- |
| `footer` | Atalhos no rodape, antes de Cancelar/Aplicar. |
| `start` | Rail logica antes do calendario; esquerda em LTR e direita em RTL. |
| `end` | Rail logica depois do calendario; direita em LTR e esquerda em RTL. |
| `auto` | Runtime escolhe rail ou rodape conforme espaco, touch e viewport. |

Em viewport estreito, por exemplo 390 px, `auto`, `start` e `end` podem cair
para `footer` para preservar legibilidade, foco e ordem de tabulacao.

## Datas, timezone e payload

Use `YYYY-MM-DD` para data civil quando o periodo e contado em dias. O runtime
trata date-only como data local civil e preserva a semantica de intervalo
inclusivo de inicio/fim.

Use datetime somente se o contrato do recurso exigir instantes com hora. Para
filtros de periodo civil, prefira date-only e deixe o backend traduzir para
limites de query conforme timezone, banco e regra do dominio.

O payload final do filtro continua canonico:

```json
{
  "startDate": "2026-03-01",
  "endDate": "2026-03-31"
}
```

O `id` do preset pode aparecer em estado interno, destaque visual ou telemetria
opcional. Ele nao substitui `startDate` e `endDate` no contrato de filtro.

## Exemplo Rust reproduzivel

Dependencias sugeridas:

```toml
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
chrono-tz = "0.10"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "2"
```

DTOs serializaveis:

```rust
use chrono::NaiveDate;
use chrono_tz::Tz;
use serde::Serialize;
use thiserror::Error;

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct StaticDateRangePresetDto {
    pub id: String,
    pub label: String,
    pub start_date: String,
    pub end_date: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub time_zone: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub icon: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tone: Option<StaticDateRangePresetToneDto>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub effective_from: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub effective_to: Option<String>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum StaticDateRangePresetToneDto {
    Neutral,
    Info,
    Success,
    Warning,
}

#[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
pub enum DateRangeShortcutDto {
    BuiltIn(String),
    Static(StaticDateRangePresetDto),
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct InlineQuickPresetsDto {
    pub enabled: bool,
    pub max_visible: Option<u8>,
    pub position: InlineQuickPresetsPositionDto,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum InlineQuickPresetsPositionDto {
    Auto,
    Footer,
    Start,
    End,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DateRangeFieldMetadataDto {
    pub control_type: String,
    pub label: String,
    pub shortcuts: Vec<DateRangeShortcutDto>,
    pub inline_quick_presets: InlineQuickPresetsDto,
}

#[derive(Debug, Error)]
pub enum DateRangeMetadataError {
    #[error("invalid date {value}; expected YYYY-MM-DD")]
    InvalidDate { value: String },
    #[error("startDate must be before or equal to endDate")]
    InvertedInterval,
    #[error("invalid IANA timezone {value}")]
    InvalidTimeZone { value: String },
}

fn parse_date(value: &str) -> Result<NaiveDate, DateRangeMetadataError> {
    NaiveDate::parse_from_str(value, "%Y-%m-%d")
        .map_err(|_| DateRangeMetadataError::InvalidDate { value: value.to_owned() })
}

pub fn validate_static_preset(
    preset: &StaticDateRangePresetDto,
) -> Result<(), DateRangeMetadataError> {
    let start = parse_date(&preset.start_date)?;
    let end = parse_date(&preset.end_date)?;
    if start > end {
        return Err(DateRangeMetadataError::InvertedInterval);
    }
    if let Some(zone) = &preset.time_zone {
        zone.parse::<Tz>()
            .map_err(|_| DateRangeMetadataError::InvalidTimeZone { value: zone.clone() })?;
    }
    Ok(())
}
```

Periodo de negocio resolvido pelo backend:

```rust
pub fn voting_period_metadata() -> Result<DateRangeFieldMetadataDto, DateRangeMetadataError> {
    let voting_period = StaticDateRangePresetDto {
        id: "periodo-votacao-2026".into(),
        label: "Periodo de votacao 2026".into(),
        description: Some("Janela oficial resolvida pelo dominio eleitoral.".into()),
        start_date: "2026-07-06".into(),
        end_date: "2026-10-04".into(),
        time_zone: Some("America/Sao_Paulo".into()),
        icon: Some("how_to_vote".into()),
        tone: Some(StaticDateRangePresetToneDto::Info),
        effective_from: Some("2026-06-01".into()),
        effective_to: Some("2026-10-04".into()),
    };

    validate_static_preset(&voting_period)?;

    Ok(DateRangeFieldMetadataDto {
        control_type: "inlineDateRange".into(),
        label: "Periodo".into(),
        shortcuts: vec![
            DateRangeShortcutDto::BuiltIn("today".into()),
            DateRangeShortcutDto::Static(voting_period),
        ],
        inline_quick_presets: InlineQuickPresetsDto {
            enabled: true,
            max_visible: Some(4),
            position: InlineQuickPresetsPositionDto::Auto,
        },
    })
}
```

Teste de serializacao:

```rust
#[test]
fn serializes_resolved_business_period_metadata() {
    let metadata = voting_period_metadata().expect("valid metadata");
    let json = serde_json::to_value(metadata).expect("serializable");

    assert_eq!(json["controlType"], "inlineDateRange");
    assert_eq!(json["shortcuts"][0], "today");
    assert_eq!(json["shortcuts"][1]["id"], "periodo-votacao-2026");
    assert_eq!(json["shortcuts"][1]["startDate"], "2026-07-06");
    assert_eq!(json["shortcuts"][1]["endDate"], "2026-10-04");
    assert!(json["shortcuts"][1].get("calculateRange").is_none());
}

#[test]
fn rejects_inverted_interval() {
    let preset = StaticDateRangePresetDto {
        id: "invalid".into(),
        label: "Invalid".into(),
        start_date: "2026-10-04".into(),
        end_date: "2026-07-06".into(),
        time_zone: Some("America/Sao_Paulo".into()),
        icon: None,
        description: None,
        tone: None,
        effective_from: None,
        effective_to: None,
    };

    assert!(matches!(
        validate_static_preset(&preset),
        Err(DateRangeMetadataError::InvertedInterval)
    ));
}

#[test]
fn rejects_invalid_timezone() {
    let preset = StaticDateRangePresetDto {
        id: "invalid-zone".into(),
        label: "Invalid zone".into(),
        start_date: "2026-07-06".into(),
        end_date: "2026-10-04".into(),
        time_zone: Some("America/Sao_Paulo/BRT".into()),
        icon: None,
        description: None,
        tone: None,
        effective_from: None,
        effective_to: None,
    };

    assert!(matches!(
        validate_static_preset(&preset),
        Err(DateRangeMetadataError::InvalidTimeZone { .. })
    ));
}
```

## Governanca e seguranca

- Decida permissao e confidencialidade antes de publicar metadata. Se revelar
  a existencia do periodo ja for sensivel, omita o atalho.
- O contrato atual de preset estatico nao possui campo publico `disabled`.
  Quando o usuario nao puder usar o periodo, prefira omitir. Publique um item
  indisponivel somente quando existir contrato governado para isso e quando a
  propria existencia do periodo puder ser exibida.
- Registre no backend a origem do periodo resolvido: calendario, norma,
  tenant, versao de regra, usuario/role e instante de publicacao.
- Quando a metadata expirar, publique novo snapshot ou remova o atalho. Nao
  conte com o frontend para recalcular vigencia.
- `tone` e semantico. Use tokens Praxis/host no frontend; nao envie cores
  arbitrarias como parte do preset.
- `icon`, `label` e `description` devem vir de catalogo governado ou i18n do
  host. Nao use labels como mecanismo primario de roteamento de intencao.
- Nunca envie callbacks, expressoes JavaScript, snippets, URLs executaveis ou
  `calculateRange` em JSON. Rust publica dados, nao codigo.

## Operacao e testes

Contrato minimo entre backend e frontend:

1. backend publica `shortcuts` como lista ordenada de built-ins e presets
   estaticos;
2. backend valida datas, timezone, autorizacao, confidencialidade e vigencia;
3. frontend materializa o catalogo e preserva `minDate`, `maxDate`,
   `dateFilter`, ordem inicio/fim, RTL, teclado, foco e tema;
4. submit/filtro envia `{ startDate, endDate }`.

Testes recomendados:

- teste Rust de serializacao com `serde_json`;
- teste Rust de rejeicao de datas invalidas, intervalos invertidos e timezone
  invalido;
- spec Angular focal de `@praxisui/core` para normalizacao de presets;
- spec Angular focal de `@praxisui/dynamic-fields` para materializacao em
  `pdx-material-date-range` e `pdx-inline-date-range`;
- E2E de consumidor na superficie oficial
  `src/app/features/filter-demo/praxis-filter-e2e-inventory.component.ts`,
  rota `http://localhost:4003/filter-demo-e2e-inventory`, cobrindo built-in,
  periodo corporativo estatico, `footer`, `start`, `end`, fallback `auto`,
  tema claro/escuro, desktop, 390 px, teclado, foco, Cancelar/Aplicar, payload
  e RTL quando houver composicao lateral.

## Checklist de publicacao

- [ ] O Rust calculou o periodo antes da serializacao.
- [ ] O JSON nao contem `calculateRange`, funcoes, snippets ou expressoes.
- [ ] Cada preset estatico tem `id`, `label`, `startDate` e `endDate`.
- [ ] Datas seguem `YYYY-MM-DD` quando representam periodo civil.
- [ ] `startDate <= endDate`.
- [ ] `timeZone`, quando presente, e IANA valido.
- [ ] Permissao e confidencialidade foram decididas antes de publicar.
- [ ] O payload observado no consumidor permanece `{ startDate, endDate }`.
- [ ] `inlineQuickPresets.position` foi validado em `footer`, `start`, `end`
  ou `auto` conforme a superficie usada.

## Criterio sobre skill dedicada

Esta documentacao nao cria uma skill nova. A evidencia atual mostra um contrato
publico estabilizado para atalhos estaticos, mas o fluxo Rust ainda e um guia
de integracao de host, nao uma rotina transversal repetida em multiplos hosts
Rust versionados no workspace.

Criar uma skill canonica somente quando houver evidencia concreta de repeticao:
mais de um host Rust publicando a mesma familia de metadata, erros recorrentes
de validacao/serializacao, manifestos em `praxis-codex-skills` atualizados e
sincronizacao oficial. A eventual skill deve orientar publicacao de metadata
resolvida pelo backend, sem duplicar a skill de Dynamic Fields nem mover regra
de negocio para Angular.
