blob: 3a9a892a1510a485680958018792aff00c514efd [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
//! Caller-owned original references exposed through an ordinary local service.
use crate::{NativeCapability, NativeExport, NativeRegistry, actions::*};
use maka_event_log::{EventLog, applications::intents::CallerIntent};
use maka_plugin_core::{
contract::{Contract, Interface, Method, MethodMode, Transport},
deployment::DeploymentRef,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{collections::BTreeMap, sync::Arc};
/// Available only in a persisted UI application; composition selects instances.
pub const CALLER_HISTORY: &str = "maka.operations.history";
#[derive(Clone, Copy)]
enum Scope {
Consumer,
Deployment,
}
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct CallerHistoryPage {
#[schemars(range(max = 9_007_199_254_740_991_u64))]
pub after: u64,
#[schemars(range(min = 1, max = 128))]
pub limit: usize,
}
pub fn caller_history_contract() -> Contract {
Contract {
name: "maka.operations.CallerHistory".into(),
version: 1,
transport: Transport::Local,
interface: Interface::Service {
methods: BTreeMap::from([(
"page".into(),
Method {
progress: None,
mode: MethodMode::Query,
input: serde_json::to_value(schemars::schema_for!(CallerHistoryPage))
.expect("caller history request schema")
.into(),
output: serde_json::to_value(schemars::schema_for!(Vec<CallerIntent>))
.expect("caller history page schema")
.into(),
resources: BTreeMap::new(),
},
)]),
},
}
}
struct History {
source: DeploymentRef,
store: Arc<EventLog>,
scope: Scope,
}
impl NativeService for History {
fn call(&self, request: ServiceRequest) -> ServiceFuture {
let source = self.source.clone();
let store = self.store.clone();
let scope = self.scope;
Box::pin(async move {
let result = async {
if request.method != "page"
|| !request.resources.is_empty()
|| request.source.deployment().is_none_or(|caller| {
caller.state_domain != source.state_domain
|| caller.deployment != source.deployment
})
{
return Err("caller history requires its original persisted UI source".into());
}
let page: CallerHistoryPage =
serde_json::from_value(request.input).map_err(|error| error.to_string())?;
let intents = match scope {
Scope::Consumer => {
store
.application_intents(
source.deployment,
request.source.consumer().instance().clone(),
page.after,
page.limit,
)
.await
}
Scope::Deployment => {
store
.application_deployment_intents(
source.deployment,
page.after,
page.limit,
)
.await
}
}
.map_err(|error| error.to_string())?;
let value = serde_json::to_value(intents).map_err(|error| error.to_string())?;
maka_plugin_core::json::check_delivery(&value, 1024 * 1024)
.map_err(|error| error.to_string())?;
Ok(value)
}
.await;
// No per-request native resource is created. The database owner
// independently retains the Root until its actual SQL cleanup.
ServiceCompletion {
result,
cleanup: Ok(()),
}
})
}
}
/// Source and store come from the same actual loaded Root, before activation.
/// Neither caller namespace nor deployment can be selected through guest JSON.
pub(crate) fn provide<V: ?Sized + 'static>(
native: &mut NativeRegistry<V>,
source: DeploymentRef,
store: Arc<EventLog>,
) -> crate::Result<()> {
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Configuration {}
native.register(CALLER_HISTORY, move |configuration: &Value, _values| {
serde_json::from_value::<Configuration>(configuration.clone())
.map_err(|error| crate::Error::Invalid(error.to_string()))?;
// Scope is selected by the explicit export binding, never call input.
Ok([
("History", Scope::Consumer),
("DeploymentHistory", Scope::Deployment),
]
.into_iter()
.map(|(name, scope)| {
(
name.into(),
NativeExport {
contract: caller_history_contract(),
value: NativeCapability::Service(Arc::new(History {
source: source.clone(),
store: store.clone(),
scope,
})),
},
)
})
.collect())
})
}
pub fn native() -> maka_plugin_core::package::NativeDefinition {
maka_plugin_core::package::NativeDefinition::new(
CALLER_HISTORY,
serde_json::json!({"type":"object","properties":{},"required":[],"additionalProperties":false}),
[
("History", caller_history_contract()),
("DeploymentHistory", caller_history_contract()),
],
)
}