DoculaDevelopersDezvoltatori
API 0.8.0 · BuildVersiune produs 0.33.0
Documentation navigationNavigare documentație

Guide 1 of 7Ghidul 1 din 7 · API 0.8.0

Declarative v1 transformsTransformări declarative v1

The bounded mapping language that turns a validated input document into the JSON a template expects, without executable code.Limbajul de mapare limitat care transformă un document de intrare validat în JSON-ul așteptat de un șablon, fără cod executabil.

Derived from:Derivat din: docs/DECLARATIVE-V1.mdcontracts/declarative-v1.schema.json

One envelope, YAML or JSONUn singur plic, YAML sau JSON

Every transform is a YAML or JSON document with exactly two top-level keys: language, which must equal declarative-v1, and output, the expression tree that produces the result. The JSON Schema in contracts/declarative-v1.schema.json rejects any other top-level key.Fiecare transformare este un document YAML sau JSON cu exact două chei de nivel superior: language, care trebuie să fie egală cu declarative-v1, și output, arborele de expresii care produce rezultatul. Schema JSON din contracts/declarative-v1.schema.json respinge orice altă cheie de nivel superior.

YAML and JSON are equivalent. Both normalize to the same canonical definition and the same SHA-256 hash, so a version created from YAML and one created from the equivalent JSON carry identical content hashes. Objects, arrays, strings, numbers, booleans and null recurse as output expressions: a plain object becomes an output object, a plain array becomes an output array, and scalars are emitted as they are.YAML și JSON sunt echivalente. Ambele se normalizează la aceeași definiție canonică și la același hash SHA-256, astfel încât o versiune creată din YAML și una creată din JSON-ul echivalent poartă hash-uri de conținut identice. Obiectele, tablourile, șirurile, numerele, valorile booleene și null se evaluează recursiv ca expresii de ieșire: un obiect simplu devine un obiect de ieșire, un tablou simplu devine un tablou de ieșire, iar scalarele sunt emise ca atare.

An object that contains a key starting with $ must be exactly one operator object. Mixing an operator key with ordinary keys, or placing two operator keys in the same object, is a parse error.Un obiect care conține o cheie ce începe cu $ trebuie să fie exact un obiect-operator. Amestecarea unei chei-operator cu chei obișnuite sau plasarea a doi operatori în același obiect este o eroare de parsare.

declarative-transform/transform.json · A complete declarative-v1 program: the envelope plus every operator.Un program declarative-v1 complet: anvelopa plus fiecare operator.
{
  "language": "declarative-v1",
  "output": {
    "first_name": { "$copy": "/first_name" },
    "middle_name": { "$copy_optional": "/middle_name" },
    "display_name": {
      "$concat": [
        { "$copy": "/first_name" },
        { "$literal": " " },
        { "$copy": "/last_name" }
      ]
    },
    "preferred_contact": {
      "$coalesce": [
        { "$copy_optional": "/phone" },
        { "$copy_optional": "/email" },
        { "$literal": "none" }
      ]
    },
    "source": { "$literal": "docs-example" }
  }
}

The five operatorsCei cinci operatori

The idiom for a display blank is $coalesce over $copy_optional and an explicit $literal. The default stays visible in the transform instead of hiding in a template.Idiomul pentru un câmp gol la afișare este $coalesce peste $copy_optional și un $literal explicit. Valoarea implicită rămâne vizibilă în transformare, nu ascunsă într-un șablon.

  • $copy takes one RFC 6901 pointer into the input, including the empty string for the root, and returns the referenced value. A missing path is an evaluation error.$copy primește un pointer RFC 6901 în intrare, inclusiv șirul gol pentru rădăcină, și întoarce valoarea referită. O cale lipsă este o eroare de evaluare.
  • $copy_optional takes the same pointer form and returns null when the path or array slot is absent. Malformed array indices and traversal through a non-container value remain errors; the operator never defaults or rewrites input.$copy_optional primește aceeași formă de pointer și întoarce null când calea sau poziția din tablou lipsește. Indicii de tablou malformați și traversarea printr-o valoare care nu este container rămân erori; operatorul nu aplică valori implicite și nu rescrie intrarea.
  • $literal returns any JSON value verbatim. Operator-shaped content inside a literal is not evaluated, which is how an object that merely looks like an operator can be emitted as data.$literal întoarce orice valoare JSON exact așa cum este. Conținutul cu formă de operator dintr-un literal nu este evaluat; așa poate fi emis ca date un obiect care doar seamănă cu un operator.
  • $concat takes a non-empty array of string-producing expressions and concatenates them. There is no implicit coercion: a number or null inside $concat is an error, not an empty string.$concat primește un tablou nevid de expresii care produc șiruri și le concatenează. Nu există coerciție implicită: un număr sau null în $concat este o eroare, nu un șir gol.
  • $coalesce takes a non-empty array of expressions and returns the first evaluated non-null value. Evaluation errors inside the list are not swallowed.$coalesce primește un tablou nevid de expresii și întoarce prima valoare evaluată diferită de null. Erorile de evaluare din listă nu sunt ascunse.
declarative-transform/input.json · Input the program runs against; the optional fields are omitted on purpose.Intrarea pe care rulează programul; câmpurile opționale lipsesc intenționat.
{
  "first_name": "Iulia",
  "last_name": "Popescu",
  "email": "[email protected]"
}

Pointer and parser rulesReguli pentru pointeri și parser

Pointer tokens decode ~1 as / and ~0 as ~, exactly as RFC 6901 specifies. Array indices are canonical unsigned decimals such as 0, 1 or 12. Leading zeroes and the - append token are invalid because a transform must never address a slot that does not exist yet.Token-urile pointerilor decodifică ~1 ca / și ~0 ca ~, exact cum specifică RFC 6901. Indicii de tablou sunt numere zecimale fără semn în formă canonică, precum 0, 1 sau 12. Zerourile inițiale și token-ul de adăugare - sunt invalide, pentru că o transformare nu trebuie să adreseze niciodată o poziție care nu există încă.

The parser rejects duplicate keys, multiple YAML documents, aliases, anchors, tags, merge keys, non-string keys, non-JSON scalars such as timestamps, unknown operators and malformed pointers. These rejections happen before evaluation, so a definition that parses is a definition that can be hashed and versioned.Parserul respinge cheile duplicate, documentele YAML multiple, alias-urile, ancorele, tag-urile, cheile de fuziune, cheile care nu sunt șiruri, scalarele non-JSON precum timestamp-urile, operatorii necunoscuți și pointerii malformați. Aceste respingeri au loc înainte de evaluare, așa că o definiție care se parsează este o definiție care poate fi hash-uită și versionată.

declarative-transform/expected-output.json · Canonical output produced by the engine for the input above; regenerated by the Go test.Ieșirea canonică produsă de motor pentru intrarea de mai sus; regenerată de testul Go.
{"display_name":"Iulia Popescu","first_name":"Iulia","middle_name":null,"preferred_contact":"[email protected]","source":"docs-example"}

Limits and determinismLimite și determinism

Limits are fixed: 256 KiB of source, 512 KiB of input and 512 KiB of output, a nesting depth of 64 and 4096 nodes. Validation returns at most 20 errors, each capped at 500 runes, with a location that points at the offending expression.Limitele sunt fixe: 256 KiB de sursă, 512 KiB de intrare și 512 KiB de ieșire, o adâncime de imbricare de 64 și 4096 de noduri. Validarea întoarce cel mult 20 de erori, fiecare limitată la 500 de rune, cu o locație care indică expresia problematică.

Execution has no clock, randomness, environment, filesystem, network, executable code, coercion, mutation, iteration, conditionals or date functions. The same input and the same definition therefore produce byte-identical canonical output and the same output_hash, which is what lets a release be validated once and replayed indefinitely.Execuția nu are ceas, aleatorism, mediu, sistem de fișiere, rețea, cod executabil, coerciție, mutație, iterație, condiții sau funcții de dată. Aceeași intrare și aceeași definiție produc deci o ieșire canonică identică la nivel de octet și același output_hash, ceea ce permite ca un release să fie validat o singură dată și reluat oricând.

Validate, test, version, publishValidează, testează, versionează, publică

The canonical transformer family is the API-first path: create the family with an Idempotency-Key, patch the draft under the strong revision precondition If-Match, validate, test against a stored case (the response returns a per-expression trace with path, operator and status), freeze the draft as an immutable version, and publish it with an effective_from timestamp.Familia canonică de transformatori este traseul API-first: creezi familia cu un Idempotency-Key, modifici ciorna sub precondiția fermă de revizie If-Match, validezi, testezi față de un caz stocat (răspunsul întoarce o trasă per expresie cu path, operator și status), îngheți ciorna ca versiune imuabilă și o publici cu un timestamp effective_from.

The legacy /api/v1/transforms family evaluates the identical engine and shares the request limit of 270,336 bytes, but it uses expected_revision in the body instead of If-Match and publishes without a calendar. Prefer the canonical family for new work.Familia veche /api/v1/transforms evaluează același motor și are aceeași limită de 270.336 de octeți per cerere, dar folosește expected_revision în body în loc de If-Match și publică fără calendar. Preferă familia canonică pentru lucrări noi.

declarative-transform/create-transform.request.json · createTransform body: the program is embedded as a string in source.Corpul createTransform: programul este încorporat ca șir în source.
{
  "name": "Person welcome projection",
  "description": "Docs example: copies, concatenates and coalesces person fields.",
  "source": "{\n  \"language\": \"declarative-v1\",\n  \"output\": {\n    \"first_name\": { \"$copy\": \"/first_name\" },\n    \"middle_name\": { \"$copy_optional\": \"/middle_name\" },\n    \"display_name\": {\n      \"$concat\": [\n        { \"$copy\": \"/first_name\" },\n        { \"$literal\": \" \" },\n        { \"$copy\": \"/last_name\" }\n      ]\n    },\n    \"preferred_contact\": {\n      \"$coalesce\": [\n        { \"$copy_optional\": \"/phone\" },\n        { \"$copy_optional\": \"/email\" },\n        { \"$literal\": \"none\" }\n      ]\n    },\n    \"source\": { \"$literal\": \"docs-example\" }\n  }\n}\n",
  "format": "json"
}
declarative-transform/curl.sh · Login, create and publish the contract, create and publish the transform.Autentificare, crearea și publicarea contractului, crearea și publicarea transformării.
#!/bin/sh
# Docula example: declarative transform.
# login -> create contract -> create version -> publish -> create transform -> create version -> publish.
# Requires curl and jq. Environment: DOCULA_API (API origin, e.g. https://docula.ro),
# DOCULA_ORIGIN (browser origin accepted by the API; defaults to DOCULA_API),
# DOCULA_USERNAME and DOCULA_PASSWORD (read from the environment, never stored here).
set -eu
API=${DOCULA_API:?set DOCULA_API to the API origin, for example http://127.0.0.1:18081}
ORIGIN=${DOCULA_ORIGIN:-$API}
HERE=$(cd "$(dirname "$0")" && pwd)
JAR=$(mktemp)
trap 'rm -f "$JAR"' EXIT

# 1. Login. The session cookie goes to the cookie jar; the CSRF token comes back in the JSON body.
CSRF=$(jq -n --arg u "${DOCULA_USERNAME:?}" --arg p "${DOCULA_PASSWORD:?}" '{username: $u, password: $p}' |
  curl --fail --silent --show-error -c "$JAR" -H "Origin: $ORIGIN" -H 'Content-Type: application/json' \
    --data-binary @- "$API/api/v1/auth/login" | jq -r '.csrf_token')

# api METHOD PATH [JSON-FILE]: every mutation needs the exact Origin, the session cookie and X-CSRF-Token.
api() {
  if [ "$#" -ge 3 ]; then
    curl --fail --silent --show-error -b "$JAR" -X "$1" -H "Origin: $ORIGIN" -H "X-CSRF-Token: $CSRF" \
      -H 'Content-Type: application/json' --data-binary @"$3" "$API$2"
  else
    curl --fail --silent --show-error -b "$JAR" -X "$1" -H "Origin: $ORIGIN" -H "X-CSRF-Token: $CSRF" "$API$2"
  fi
}

# 2. Create the contract draft (createContract). The schema travels as a JSON string.
CONTRACT=$(api POST /api/v1/contracts "$HERE/create-contract.request.json")
CONTRACT_ID=$(printf '%s' "$CONTRACT" | jq -r '.contract.id')
REVISION=$(printf '%s' "$CONTRACT" | jq -r '.contract.draft_revision')

# 3. Freeze the draft into version 1 (createContractVersion) using the draft revision you just read.
VERSION=$(jq --argjson rev "$REVISION" '.expected_revision = $rev' "$HERE/create-version.request.json" > "$JAR.body" && api POST "/api/v1/contracts/$CONTRACT_ID/versions" "$JAR.body")
CONTRACT_VERSION_ID=$(printf '%s' "$VERSION" | jq -r '.version.id')
CONTRACT_VERSION_NUMBER=$(printf '%s' "$VERSION" | jq -r '.version.number')

# 4. Publish the version (publishContractVersion). The body is an empty JSON object.
api POST "/api/v1/contracts/$CONTRACT_ID/versions/$CONTRACT_VERSION_NUMBER/publish" "$HERE/publish.request.json" > /dev/null

# 5. Create the transform draft (createTransform). The declarative-v1 program travels as a JSON string.
TRANSFORM=$(api POST /api/v1/transforms "$HERE/create-transform.request.json")
TRANSFORM_ID=$(printf '%s' "$TRANSFORM" | jq -r '.transform.id')
TRANSFORM_REVISION=$(printf '%s' "$TRANSFORM" | jq -r '.transform.draft_revision')

# 6. Freeze and publish transform version 1 (createTransformVersion, publishTransformVersion).
jq --argjson rev "$TRANSFORM_REVISION" '.expected_revision = $rev' "$HERE/create-version.request.json" > "$JAR.body"
TRANSFORM_VERSION=$(api POST "/api/v1/transforms/$TRANSFORM_ID/versions" "$JAR.body")
TRANSFORM_VERSION_ID=$(printf '%s' "$TRANSFORM_VERSION" | jq -r '.version.id')
TRANSFORM_VERSION_NUMBER=$(printf '%s' "$TRANSFORM_VERSION" | jq -r '.version.number')
api POST "/api/v1/transforms/$TRANSFORM_ID/versions/$TRANSFORM_VERSION_NUMBER/publish" "$HERE/publish.request.json" > /dev/null
rm -f "$JAR.body"

printf 'contract_version_id=%s\ntransform_version_id=%s\n' "$CONTRACT_VERSION_ID" "$TRANSFORM_VERSION_ID"

curl sequence sketchSchiță de secvență curl

  1. Step 1 · copy onlyPasul 1 · doar pentru copiere
    curl --request POST "$DOCULA_URL/api/v1/transformers" --cookie "$COOKIE_JAR" \
      --header "Content-Type: application/json" --header "Origin: $DOCULA_URL" \
      --header "X-CSRF-Token: $CSRF_TOKEN" --header "Idempotency-Key: $IDEMPOTENCY_KEY" \
      --data-binary @transformer-family.json
  2. Step 2 · copy onlyPasul 2 · doar pentru copiere
    curl --request GET "$DOCULA_URL/api/v1/transformers/$TRANSFORMER_ID/draft" --cookie "$COOKIE_JAR" --include   # read the ETag
  3. Step 3 · copy onlyPasul 3 · doar pentru copiere
    curl --request PATCH "$DOCULA_URL/api/v1/transformers/$TRANSFORMER_ID/draft" --cookie "$COOKIE_JAR" \
      --header "Content-Type: application/json" --header "Origin: $DOCULA_URL" \
      --header "X-CSRF-Token: $CSRF_TOKEN" --header "Idempotency-Key: $IDEMPOTENCY_KEY" --header "If-Match: $ETAG" \
      --data-binary @transformer-draft.json
  4. Step 4 · copy onlyPasul 4 · doar pentru copiere
    curl --request POST "$DOCULA_URL/api/v1/transformers/$TRANSFORMER_ID/test" --cookie "$COOKIE_JAR" \
      --header "Content-Type: application/json" --header "Origin: $DOCULA_URL" --header "X-CSRF-Token: $CSRF_TOKEN" \
      --data-binary @transformer-test.json
  5. Step 5 · copy onlyPasul 5 · doar pentru copiere
    curl --request POST "$DOCULA_URL/api/v1/transformers/$TRANSFORMER_ID/versions" --cookie "$COOKIE_JAR" \
      --header "Content-Type: application/json" --header "Origin: $DOCULA_URL" \
      --header "X-CSRF-Token: $CSRF_TOKEN" --header "Idempotency-Key: $IDEMPOTENCY_KEY" --header "If-Match: $ETAG" \
      --data-binary '{}'
  6. Step 6 · copy onlyPasul 6 · doar pentru copiere
    curl --request POST "$DOCULA_URL/api/v1/transformers/$TRANSFORMER_ID/versions/$VERSION_NUMBER/publish" --cookie "$COOKIE_JAR" \
      --header "Content-Type: application/json" --header "Origin: $DOCULA_URL" \
      --header "X-CSRF-Token: $CSRF_TOKEN" --header "Idempotency-Key: $IDEMPOTENCY_KEY" \
      --data-binary @effective-from.json