Skip to content

4.3.3

Choose a tag to compare

@github-actions github-actions released this 05 Oct 07:39
· 30 commits to main since this release
Immutable release. Only release title and notes can be modified.
v4.3.3
4b271bc

Added

  • The openehr-its client sends DPoP-bound tokens (#3558).
    Credentials::Dpop writes Authorization: DPoP <token> (RFC 9449 §7.1),
    and a DpopProver given with Client::with_dpop_prover signs the DPoP
    proof over each request's final method, URL and access token. A
    use_dpop_nonce challenge (RFC 9449 §8 and §9) is answered with one re-send
    carrying the supplied nonce.
  • openehr-sdt prints a SMART scope back in the grammar (#3557).
    SmartScope and ResourceScope implement Display, writing the canonical
    form (permissions in c/r/u/d/s order), and
    SmartScope::format_all prints a scope list as the space-delimited string
    SmartScope::parse_all reads.

Changed

  • ClientError::DeadlineElapsed says whether a send went out (#3560).
    Its new sent field is false when the deadline passed before anything was
    sent, and true when it passed before a retry or the re-send answering a
    DPoP use_dpop_nonce challenge, after the service had received the request.
  • The container images name FerroHEALTH as vendor and author (#3561).
    org.opencontainers.image.vendor is FerroHEALTH and
    org.opencontainers.image.authors is
    FerroHEALTH <https://github.com/FerroHEALTH> on every image.
  • openehr-its types the Prefer-dependent response bodies (#3548). A
    response the OAS declares as oneOf a resource and Identifier (the
    201 and 200 answers of every create and update: EHR, EHR_STATUS,
    COMPOSITION, DIRECTORY, CONTRIBUTION and the demographic parties) is now
    rest::runtime::Representation<T>, either Full(T) or
    Identifier(Identifier), in place of serde_json::Value. A generator test
    pins the bodies that remain untyped, with their reason.
  • The generated CONTRIBUTION envelope reads a deleted version and an
    AUDIT_DETAILS audit
    (#3547). UpdateVersion.data is optional, because a
    logical deletion commits a version with no data (RM common master06
    §Logical Deletion; ORIGINAL_VERSION.data is 0..1), and UpdateAudit
    reads _type: "AUDIT_DETAILS" as its base, as the contribution_create
    docs text tells servers to.
  • ferroehr-rest decodes every ITS-REST request through the generated
    contract
    (#3545). Each handler reads its parameters with the generated
    *Params::from_request, the CDR's own parameter decoder is gone, and the
    Simplified Formats CONTRIBUTION path reads the typed NewContribution
    envelope.
  • Every ITS-REST request body has a public decoder (#3551). Each operation
    with a body gets {operation}_request_body(content_type, bytes) in its API
    group, which returns the type the server trait receives (for
    contribution_create, a Payload of the canonical or the Simplified Formats
    envelope) and is the decoding the generated router itself runs. The body
    decoding moved to rest::decode, so it needs only the rest feature.
  • ROUTE_REQUEST_MEDIA admits every media type the Content-Type parameter
    declares
    (#3552). The released OAS lists only application/json under
    requestBody.content where the operation's Content-Type parameter and the
    docs text also admit XML and both Simplified Formats; request_media now
    carries the union, and a test holds it equal to the parameter.
  • The openehr-* crates step to 0.0.81.
  • openehr-its names the openEHR identifier class of every path
    parameter
    (#3539). rest::routes::Param gains identifier, an
    IdentifierClass whose as_str() is the BASE class name: the
    class the ITS-REST OAS states only in the parameter's description, as the
    BMM type of the RM attribute the value is "taken from". ehr_id,
    versioned_object_uid and contribution_uid carry HIER_OBJECT_ID,
    version_uid carries OBJECT_VERSION_ID, and uid_based_id keeps its
    per-operation class: UID_BASED_ID on a read, HIER_OBJECT_ID on an
    update, OBJECT_VERSION_ID on a delete (IdentifierClass::UidBased,
    HierObject, ObjectVersion). The table is generated from a
    decision map that a codegen test holds to the vendored OAS descriptions.
  • Every generated *Params struct decodes a matched request (#3540).
    from_request(matched, query, headers) takes the RouteMatch from
    routes::lookup, the query string and the headers, and runs the decoding
    the generated router runs: the declared keys into their fields, the members
    of a form-exploded object such as the AQL query_parameters read as JSON
    where they parse, + as a literal plus, and a 400 naming the parameter
    for a missing, repeated or invalid one. The generated handlers call the
    same decoding, so an intermediary and the router cannot read a request
    differently. The decoding moved from rest::server to the new
    rest::decode, which needs only the rest feature, not the axum server
    half.
  • The ITS-REST route table names each operation's request-body media
    types
    (#3543). Each API group carries ROUTE_REQUEST_MEDIA, aligned with
    ROUTES, holding the OAS requestBody.content keys, and RouteMatch
    carries them as request_media. definition_query_version_store, which
    declares no Content-Type parameter, now names text/plain, so a
    forwarding intermediary can set the header.
  • A Simplified Formats CONTRIBUTION has a typed envelope (#3541). The
    generated NewContribution is generic over its version content,
    NewContribution<T = Versionable>, so the bare name stays the canonical
    body and NewContribution<serde_json::Value> reads the envelope of a FLAT
    or STRUCTURED CONTRIBUTION, whose envelope stays canonical while each
    versions[i].data is simplified (ITS-REST contribution_create
    §Simplified Formats). The generated server admits
    application/openehr.wt.flat+json and
    application/openehr.wt.structured+json on every operation whose
    Content-Type parameter declares them, and hands the implementation a
    rest::runtime::Payload (Canonical, Flat, Structured); XML and
    any other type stay 415.
  • The openehr-* crates step to 0.0.80.
  • The openehr-rm attribute model covers the BASE foundation types and
    names reference targets
    (#3537). The model (v1_1::model,
    v1_2::model) now holds every class of the BASE + RM BMM, including the
    primitives (Boolean, Character, Integer, Integer64, Octet,
    Real, Double, String, Uri), the Ordered and Numeric markers and
    the containers. Each class carries its BMM package, whether the
    generated crates carry a type for it (generated_type), and its full BMM
    ancestry as ancestors, so DV_ORDERED carries Ordered. The new
    is_primitive, is_foundation_type and conforms_to_ordered lookups
    answer the AQL ORDER BY question ("primitives and Ordered types", QUERY
    master03 §ORDER BY). The AQL engine expands a declared type to its
    descendants for RM classes only; a foundation type stays a value type. A
    reference-typed attribute (OBJECT_REF, PARTY_REF, LOCATABLE_REF)
    carries ref_target, the class the RM text says it points to
    (EHR.ehr_status → EHR_STATUS, PARTY_RELATIONSHIP.target →
    VERSIONED_PARTY), or None where the text names no single class. A
    consumer that read "not in the model" as "primitive" now asks
    is_primitive.
  • The openehr-* crates step to 0.0.79.
  • openehr-its exposes the Authorization value it sends (#3535).
    Credentials::header_value is public, so a consumer can validate a
    credential at configuration load with exactly the value the client sends.
    Basic credentials are checked against RFC 7617 §2 (no colon in the user-id,
    no control character in either part) and bearer tokens against the
    b64token syntax of RFC 6750 §2.1. A refusal is a typed
    InvalidCredentials, which never carries the secret; a request whose
    credentials break these rules fails with ClientError::InvalidCredentials
    before anything is sent.
  • The openehr-* crates step to 0.0.78.
  • openehr-query classifies AQL function calls (#3529). A call naming
    one of the built-in functions in QUERY master03 §Functions parses to
    FunctionCall::Builtin with a typed BuiltinFunction (StringFunction,
    NumericFunction, DateTimeFunction); any other name parses to
    FunctionCall::Other. Both keep the name as written, so printer::to_aql
    returns it unchanged, CONTAINS included. FunctionCall::Named is gone, and
    the Token::Contains lexer token now carries its spelling. The CDR's AQL
    engine uses this classification instead of a function list of its own.
  • openehr-its names every operation's declared parameters (#3530).
    Each generated *Params struct carries a PARAMS table of the path, query
    and header parameters it holds (name as the OAS spells it, location,
    required, explode, and the value kind its schema states), written in the
    same pass as the struct. Each API group carries ROUTE_PARAMS, aligned with
    ROUTES, and routes::lookup returns the matched operation's parameters
    with query_param, query_key and header_param lookups, so an
    intermediary can pass on exactly what an operation declares.
  • openehr-its ReqwestTransport constructors take a
    reqwest::ClientBuilder
    (#3531). ReqwestTransport::new and the new
    with_builder_timeout (replacing with_client_timeout) build the client
    themselves, with redirects switched off.
  • The openehr-* crates step to 0.0.77.
  • FerroTERM moves to 0.1.6 at ghcr.io/ferrohealth/ferroterm (#3521).
    FerroTERM moved to the FerroHEALTH organization, and its releases from 0.1.6
    on publish there. The terminology compose overlay, the hosted sandbox, the
    Helm chart (terminology.image, chart 10.1.6) and the FerroTERM integration
    test pin 0.1.6@sha256:7ec4c962…, and the book links
    https://github.com/FerroHEALTH/FerroTERM.

Fixed

  • AQL honours the per-EHR EHR_ACCESS decision (#3562). A query could
    return rows from an EHR whose path routes refused the caller: the gate ran
    only on routes carrying an ehr_id in the path. Ad-hoc, stored and cohort
    queries now withhold every EHR the caller may not read, whether the query
    scopes it by the WHERE clause, an EHR predicate, the ehr_id parameter,
    or not at all.
  • A + in a query string is a literal plus (#3545). The CDR decoded
    query strings as HTML forms do, where + means a space, so an unencoded
    offset such as version_at_time=2024-01-01T00:00:00+01:00 lost its sign.
    Query strings now decode per RFC 3986, as the OAS style: form
    parameters (RFC 6570) define; a space is sent as %20.
  • Client::forward returns a redirect as received (#3531). The client
    built by ReqwestTransport::with_timeout used reqwest's default redirect
    policy and followed up to ten redirects, re-sending the request and its
    credentials to whatever host a Location named. Every ReqwestTransport
    constructor now switches redirects off, so a 3xx reaches the caller and
    nothing is sent further. No ITS-REST 1.1.0 operation declares a 3xx, so a
    generated client call that receives one refuses it as an undocumented
    status.
  • A FerroTERM pin the sandbox's index cannot serve is refused on the pull
    request
    (#3520). FerroTERM 0.1.5 moved its artifact layout to 7, and the
    v4.3.2 sandbox leg failed on the layout-6 index the box carried. A
    ferroterm-index-layout CI job reads the layout at the pinned FerroTERM tag
    and refuses a pin that differs from deploy/hosted/ferroterm-index.env, the
    record of the box's index. The box now runs FerroTERM 0.1.6 on a rebuilt
    layout-7 index, and deploy/hosted/README.md documents the rebuild.
  • The generated ITS-REST types keep the members an open schema allows
    (#3526). OpenAPI 3.0.3 makes additionalProperties default to true, so
    every ITS-REST object schema that does not set it to false admits members
    beyond its declared properties. The openehr-its REST types for those
    schemas now carry them in an additional_properties map, written back at
    the object's own level, where they used to be dropped on decode. Error is
    one of them, and the server now builds its error body from the generated
    type with the error member in that map. The body carries the same three
    members, with error now written after message and validationErrors.