openehr-its 0.0.128

openEHR ITS (Implementation Technology Specifications): canonical JSON + canonical XML serialization of the RM, the generated ITS-REST 1.1.0 API contract, and the OPT 1.4 and AOM2 archetype XML codecs
Documentation
// @generated by openehr-codegen (emit-rest) — DO NOT EDIT.
// SPDX-FileCopyrightText: Cadasto B.V.
// SPDX-FileCopyrightText: openEHR Foundation
// SPDX-License-Identifier: Apache-2.0
//! ITS-REST contract for the `system` API group: DTOs, per-operation
//! param structs, per-response headers structs, the `server` and
//! `client` halves, and the route table.

#![allow(
    clippy::all,
    clippy::pedantic,
    clippy::nursery,
    dead_code,
    unused_imports,
    unused_variables,
    reason = "mechanically generated contract text: the OAS is emitted in full (every DTO, param struct and route, whether or not this workspace consumes it yet), so style and dead-code lints do not apply — the hand-written runtime and the implementing adapter carry the lint bar"
)]
#![allow(
    clippy::disallowed_types,
    reason = "adjudicated free-form JSON slots: serde_json::Value is workspace-banned (#1694); a generated carrier exists only where the spec leaves the slot open — `allow`, not `expect`, because a carrier may sit inside a feature-gated region and fire only under that feature"
)]
use serde::{Deserialize, Serialize};

/// The `Options` transport DTO of this API group (an ITS-REST OAS
/// component schema).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Options {
    /// The `solution` property of `Options`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub solution: Option<String>,
    /// The `solution_version` property of `Options`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub solution_version: Option<String>,
    /// The `vendor` property of `Options`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub vendor: Option<String>,
    /// The `restapi_specs_version` property of `Options`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub restapi_specs_version: Option<String>,
    /// The `conformance_profile` property of `Options`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub conformance_profile: Option<String>,
    /// The `endpoints` property of `Options`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub endpoints: Option<Vec<String>>,
    /// The undeclared (`additionalProperties`) members of `Options`, which
    /// its ITS-REST OAS component schema leaves open (no `additionalProperties`, which defaults to `true`).
    #[serde(flatten)]
    pub additional_properties: std::collections::BTreeMap<String, serde_json::Value>,
}

/// Parameters for `options` (path/query/header).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptionsParams {
    #[serde(rename = "Accept")]
    #[serde(skip_serializing_if = "Option::is_none")]
    /// `Accept` (header)
    pub accept: Option<String>,
}

impl OptionsParams {
    /// The parameters of `options`, one per field, in field order.
    pub const PARAMS: &'static [crate::rest::routes::Param] = &[crate::rest::routes::Param {
        name: "Accept",
        location: crate::rest::routes::ParamLocation::Header,
        required: false,
        explode: false,
        kind: crate::rest::routes::ParamKind::Enum(&["application/json"]),
        identifier: None,
    }];

    /// Decodes the parameters of `options` from a request [`crate::rest::routes::lookup`]
    /// matched to it: its path parameters, its query string without the `?`, and its
    /// headers, exactly as the generated router decodes them.
    ///
    /// # Errors
    /// Returns [`crate::rest::runtime::ApiError::BadRequest`] naming the parameter that is
    /// missing, repeated where a single value is declared, not text, or not a valid value.
    pub fn from_request(
        _matched: &crate::rest::routes::RouteMatch,
        _query: Option<&str>,
        headers: &http::HeaderMap,
    ) -> Result<Self, crate::rest::runtime::ApiError> {
        Self::from_parts(headers)
    }

    /// Decodes the parameters of `options` from the request's decoded parts.
    pub(crate) fn from_parts(
        headers: &http::HeaderMap,
    ) -> Result<Self, crate::rest::runtime::ApiError> {
        Ok(Self {
            accept: crate::rest::decode::header_optional(headers, "Accept")?,
        })
    }
}

/// The response headers the OAS declares for the `200` answer of
/// `OPTIONS /`: each value the answer carries, `None` (or an empty list)
/// when it carries none.
#[derive(Debug, Clone, Default)]
pub struct OptionsOkHeaders {
    /// The `Allow` response header.
    pub allow: Option<String>,
    /// The `Content-Type` response header.
    pub content_type: Option<String>,
}

impl crate::rest::runtime::ResponseHeaders for OptionsOkHeaders {
    fn into_header_map(self) -> Result<http::HeaderMap, crate::rest::runtime::HeaderError> {
        let mut map = http::HeaderMap::new();
        crate::rest::runtime::set_header(&mut map, "Allow", self.allow)?;
        crate::rest::runtime::set_header(&mut map, "Content-Type", self.content_type)?;
        Ok(map)
    }
}

/// The server half of the `system` API group (ITS-REST): the `SystemApi`
/// trait an implementation provides, one success-answer enum per operation,
/// and `router`, which binds every operation of the route table to its
/// trait method over axum.
#[cfg(feature = "rest-server")]
pub mod server {
    use super::*;

    /// The answers `OPTIONS /` succeeds with: one variant per `2xx`/`3xx` status
    /// the OAS documents (an error is a [`crate::rest::runtime::Refusal`]).
    #[derive(Debug, Clone)]
    pub enum OptionsResponse {
        /// The `200` answer.
        Ok {
            /// The body, sent as canonical JSON.
            body: Options,
            /// The response headers the OAS declares for this answer.
            headers: OptionsOkHeaders,
        },
    }

    /// Server contract for the `system` API group (ITS-REST).
    ///
    /// Every method defaults to refusing with `ApiError::NotImplemented` (`501`),
    /// so an implementor overrides only the operations it supports; `router`
    /// serves an implementation over axum. A method refuses with a
    /// [`crate::rest::runtime::Refusal`]: `?` turns an `ApiError` into one,
    /// and `Refusal::with_headers` adds the headers the OAS declares for the
    /// answer (the `ETag` of a `412`, for one).
    #[async_trait::async_trait]
    pub trait SystemApi {
        /// `OPTIONS /`
        async fn options(
            &self,
            params: OptionsParams,
        ) -> Result<OptionsResponse, crate::rest::runtime::Refusal> {
            Err(crate::rest::runtime::ApiError::NotImplemented.into())
        }
    }

    /// The axum router serving every operation of the `system` group over `api`.
    ///
    /// Each route is bound at its OAS path relative to the API base (an RFC 6570
    /// query expansion dropped, path captures named by segment position). A
    /// handler decodes the request into the operation's params struct and body
    /// — a missing or unparseable parameter answers `400` naming it, a
    /// canonical-JSON body sent as another `Content-Type` answers `415` — and
    /// encodes the trait method's answer, or its `Refusal` as the ITS-REST
    /// `Error` body with the refusal's headers. Mount it under the base path
    /// with `axum::Router::nest`.
    ///
    /// The router carries no fallback, so group routers merge freely (axum
    /// refuses to merge two routers that both carry one); finish the merged
    /// router with `crate::rest::server::with_fallbacks` for the `404` and `405`
    /// answers, or take `crate::rest::server::router`, which does both.
    ///
    /// The typed bodies are canonical JSON only: a server that also serves
    /// canonical XML or a Simplified Format routes those requests itself, and
    /// the `accept` parameter reaches the trait method, which answers
    /// `ApiError::NotAcceptable` for a representation it does not serve.
    pub fn router<S>(api: std::sync::Arc<S>) -> axum::Router
    where
        S: SystemApi + Send + Sync + 'static,
    {
        axum::Router::new()
            .route(
                "/",
                axum::routing::on(axum::routing::MethodFilter::OPTIONS, handle_options::<S>),
            )
            .with_state(api)
    }

    /// Serves `OPTIONS /` through [`SystemApi::options`].
    async fn handle_options<S>(
        axum::extract::State(api): axum::extract::State<std::sync::Arc<S>>,
        headers: http::HeaderMap,
    ) -> axum::response::Response
    where
        S: SystemApi + Send + Sync + 'static,
    {
        let served: Result<axum::response::Response, crate::rest::runtime::Refusal> = async {
            let params = OptionsParams::from_parts(&headers)?;
            let reply: crate::rest::server::Reply = match api.options(params).await? {
                OptionsResponse::Ok { body, headers } => {
                    let mut reply = crate::rest::server::Reply::new(http::StatusCode::OK);
                    reply.json(&body)?;
                    reply.headers(headers)?;
                    reply
                }
            };
            Ok(reply.finish())
        }
        .await;
        crate::rest::server::respond(served)
    }
}

/// The client half of the `system` API group (ITS-REST): one method per
/// operation over a [`crate::rest::client::Client`], answering an outcome
/// enum with one variant per status the OAS documents for it.
#[cfg(feature = "rest-client")]
pub mod client {
    use super::*;

    /// The outcome of `OPTIONS /`: one variant per status the OAS documents.
    /// A status outside this set is a [`crate::rest::client::ClientError`].
    #[derive(Debug, Clone)]
    pub enum OptionsOutcome {
        /// The `200` answer.
        Ok {
            /// The body, decoded from canonical JSON.
            body: Options,
            /// The response headers the OAS declares for this answer.
            headers: OptionsOkHeaders,
        },
    }

    /// The `system` API group over one configured CDR.
    #[derive(Debug, Clone)]
    pub struct SystemClient<'c, T> {
        client: &'c crate::rest::client::Client<T>,
        options: crate::rest::client::CallOptions,
    }

    impl<'c, T: crate::rest::client::Transport> SystemClient<'c, T> {
        /// The `system` API group over `client`.
        #[must_use]
        pub fn new(client: &'c crate::rest::client::Client<T>) -> Self {
            Self {
                client,
                options: crate::rest::client::CallOptions::default(),
            }
        }

        /// This group client applying `options` (a deadline, extra headers) to
        /// every call it makes.
        #[must_use]
        pub fn with_options(mut self, options: crate::rest::client::CallOptions) -> Self {
            self.options = options;
            self
        }

        /// `OPTIONS /`
        ///
        /// # Errors
        /// A status the OAS does not document for this operation, a refused
        /// credential, a service failure, an undecodable body, or a request
        /// that could not be sent — see [`crate::rest::client::ClientError`].
        pub async fn options(
            &self,
            params: &OptionsParams,
        ) -> Result<OptionsOutcome, crate::rest::client::ClientError> {
            let mut request =
                crate::rest::client::Request::new(http::Method::OPTIONS, String::from("/"));
            if let Some(value) = params.accept.as_ref() {
                request.header("Accept", &value.to_string())?;
            }
            request.apply_options(&self.options);
            let answer = self.client.execute(request).await?;
            match answer.status() {
                http::StatusCode::OK => Ok(OptionsOutcome::Ok {
                    body: answer.json()?,
                    headers: OptionsOkHeaders {
                        allow: answer.header("Allow"),
                        content_type: answer.header("Content-Type"),
                    },
                }),
                _ => Err(answer.into_undocumented()),
            }
        }
    }
}
/// The operations of this group as `(method, path, operation_id)`, in OAS
/// document order: `server::router` binds each to its trait method, and
/// `crate::rest::routes::lookup` matches a request path against them.
pub const ROUTES: &[(&str, &str, &str)] = &[("OPTIONS", "/", "options")];

/// The declared parameters of each operation, index-aligned with [`ROUTES`]:
/// the operation's `*Params::PARAMS`, empty when it declares none.
pub const ROUTE_PARAMS: &[&[crate::rest::routes::Param]] = &[OptionsParams::PARAMS];

const _: () = assert!(
    ROUTE_PARAMS.len() == ROUTES.len(),
    "ROUTE_PARAMS carries one row per ROUTES entry"
);

/// The request-body media types of each operation, index-aligned with
/// [`ROUTES`]: the `requestBody.content` keys of the OAS, in document order,
/// empty when the operation takes no body.
pub const ROUTE_REQUEST_MEDIA: &[&[&str]] = &[&[]];

const _: () = assert!(
    ROUTE_REQUEST_MEDIA.len() == ROUTES.len(),
    "ROUTE_REQUEST_MEDIA carries one row per ROUTES entry"
);