community: trim premium backend surfaces
This commit is contained in:
Generated
-104
@@ -10,11 +10,8 @@ dependencies = [
|
||||
"axum",
|
||||
"axum-extra",
|
||||
"base64",
|
||||
"crank-adapter-grpc",
|
||||
"crank-adapter-soap",
|
||||
"crank-core",
|
||||
"crank-mapping",
|
||||
"crank-proto",
|
||||
"crank-registry",
|
||||
"crank-runtime",
|
||||
"crank-schema",
|
||||
@@ -383,20 +380,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crank-adapter-graphql"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"crank-core",
|
||||
"crank-mapping",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crank-adapter-grpc"
|
||||
version = "0.1.0"
|
||||
@@ -432,35 +415,6 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crank-adapter-soap"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"crank-core",
|
||||
"crank-mapping",
|
||||
"reqwest",
|
||||
"roxmltree",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crank-adapter-websocket"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"crank-core",
|
||||
"futures-util",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crank-core"
|
||||
version = "0.1.0"
|
||||
@@ -521,11 +475,7 @@ dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
"base64",
|
||||
"crank-adapter-graphql",
|
||||
"crank-adapter-grpc",
|
||||
"crank-adapter-rest",
|
||||
"crank-adapter-soap",
|
||||
"crank-adapter-websocket",
|
||||
"crank-core",
|
||||
"crank-mapping",
|
||||
"crank-schema",
|
||||
@@ -538,7 +488,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
@@ -604,12 +553,6 @@ dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
@@ -2160,12 +2103,6 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roxmltree"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.9.10"
|
||||
@@ -2894,22 +2831,6 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tungstenite",
|
||||
"webpki-roots 0.26.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.18"
|
||||
@@ -3108,25 +3029,6 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.9.2",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
@@ -3206,12 +3108,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
|
||||
@@ -9,10 +9,6 @@ members = [
|
||||
"crates/crank-registry",
|
||||
"crates/crank-runtime",
|
||||
"crates/crank-adapter-rest",
|
||||
"crates/crank-adapter-graphql",
|
||||
"crates/crank-adapter-grpc",
|
||||
"crates/crank-adapter-websocket",
|
||||
"crates/crank-adapter-soap",
|
||||
]
|
||||
resolver = "3"
|
||||
|
||||
|
||||
@@ -10,11 +10,8 @@ argon2.workspace = true
|
||||
axum.workspace = true
|
||||
axum-extra.workspace = true
|
||||
base64.workspace = true
|
||||
crank-adapter-grpc = { path = "../../crates/crank-adapter-grpc", features = ["test-support"] }
|
||||
crank-adapter-soap = { path = "../../crates/crank-adapter-soap" }
|
||||
crank-core = { path = "../../crates/crank-core" }
|
||||
crank-mapping = { path = "../../crates/crank-mapping" }
|
||||
crank-proto = { path = "../../crates/crank-proto" }
|
||||
crank-registry = { path = "../../crates/crank-registry" }
|
||||
crank-runtime = { path = "../../crates/crank-runtime" }
|
||||
crank-schema = { path = "../../crates/crank-schema" }
|
||||
|
||||
@@ -29,9 +29,8 @@ use crate::{
|
||||
operations::{
|
||||
archive_operation, create_operation, create_version, delete_operation,
|
||||
export_operation, generate_draft, get_operation, get_operation_version,
|
||||
list_grpc_services, list_operations, list_soap_services, publish_operation, run_test,
|
||||
update_operation, upload_descriptor_set, upload_input_json, upload_output_json,
|
||||
upload_proto_descriptor, upload_wsdl_descriptor, upload_xsd_descriptor,
|
||||
list_operations, publish_operation, run_test, update_operation, upload_input_json,
|
||||
upload_output_json,
|
||||
},
|
||||
secrets::{create_secret, delete_secret, get_secret, list_secrets, rotate_secret},
|
||||
streaming::{
|
||||
@@ -75,30 +74,6 @@ pub fn build_app(state: AppState) -> Router {
|
||||
"/operations/{operation_id}/samples/output-json",
|
||||
post(upload_output_json),
|
||||
)
|
||||
.route(
|
||||
"/operations/{operation_id}/descriptors/proto",
|
||||
post(upload_proto_descriptor),
|
||||
)
|
||||
.route(
|
||||
"/operations/{operation_id}/descriptors/descriptor-set",
|
||||
post(upload_descriptor_set),
|
||||
)
|
||||
.route(
|
||||
"/operations/{operation_id}/descriptors/wsdl",
|
||||
post(upload_wsdl_descriptor),
|
||||
)
|
||||
.route(
|
||||
"/operations/{operation_id}/descriptors/xsd",
|
||||
post(upload_xsd_descriptor),
|
||||
)
|
||||
.route(
|
||||
"/operations/{operation_id}/grpc/services",
|
||||
get(list_grpc_services),
|
||||
)
|
||||
.route(
|
||||
"/operations/{operation_id}/soap/services",
|
||||
get(list_soap_services),
|
||||
)
|
||||
.route(
|
||||
"/operations/{operation_id}/drafts/generate",
|
||||
post(generate_draft),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use axum::{
|
||||
Json,
|
||||
body::Bytes,
|
||||
extract::{Extension, Path, Query, State},
|
||||
http::{HeaderMap, StatusCode, header},
|
||||
response::IntoResponse,
|
||||
@@ -232,118 +231,6 @@ pub async fn upload_output_json(
|
||||
})))
|
||||
}
|
||||
|
||||
pub async fn upload_proto_descriptor(
|
||||
Path(path): Path<WorkspaceOperationPath>,
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
body: Bytes,
|
||||
) -> Result<Json<Value>, ApiError> {
|
||||
let source_name = header_file_name(&headers);
|
||||
let descriptor = state
|
||||
.service
|
||||
.upload_proto_file(
|
||||
&path.workspace_id.as_str().into(),
|
||||
&path.operation_id.as_str().into(),
|
||||
source_name.as_deref(),
|
||||
body.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(json!(descriptor)))
|
||||
}
|
||||
|
||||
pub async fn upload_descriptor_set(
|
||||
Path(path): Path<WorkspaceOperationPath>,
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
body: Bytes,
|
||||
) -> Result<Json<Value>, ApiError> {
|
||||
let source_name = header_file_name(&headers);
|
||||
let descriptor = state
|
||||
.service
|
||||
.upload_descriptor_set(
|
||||
&path.workspace_id.as_str().into(),
|
||||
&path.operation_id.as_str().into(),
|
||||
source_name.as_deref(),
|
||||
body.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(json!(descriptor)))
|
||||
}
|
||||
|
||||
pub async fn upload_wsdl_descriptor(
|
||||
Path(path): Path<WorkspaceOperationPath>,
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
body: Bytes,
|
||||
) -> Result<Json<Value>, ApiError> {
|
||||
let source_name = header_file_name(&headers);
|
||||
let descriptor = state
|
||||
.service
|
||||
.upload_wsdl_file(
|
||||
&path.workspace_id.as_str().into(),
|
||||
&path.operation_id.as_str().into(),
|
||||
source_name.as_deref(),
|
||||
body.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(json!(descriptor)))
|
||||
}
|
||||
|
||||
pub async fn upload_xsd_descriptor(
|
||||
Path(path): Path<WorkspaceOperationPath>,
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
body: Bytes,
|
||||
) -> Result<Json<Value>, ApiError> {
|
||||
let source_name = header_file_name(&headers);
|
||||
let descriptor = state
|
||||
.service
|
||||
.upload_xsd_file(
|
||||
&path.workspace_id.as_str().into(),
|
||||
&path.operation_id.as_str().into(),
|
||||
source_name.as_deref(),
|
||||
body.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(json!(descriptor)))
|
||||
}
|
||||
|
||||
pub async fn list_grpc_services(
|
||||
Path(path): Path<WorkspaceOperationPath>,
|
||||
Query(query): Query<ExportQuery>,
|
||||
State(state): State<AppState>,
|
||||
) -> Result<Json<Value>, ApiError> {
|
||||
let services = state
|
||||
.service
|
||||
.list_grpc_services(
|
||||
&path.workspace_id.as_str().into(),
|
||||
&path.operation_id.as_str().into(),
|
||||
query.version,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(json!({ "services": services })))
|
||||
}
|
||||
|
||||
pub async fn list_soap_services(
|
||||
Path(path): Path<WorkspaceOperationPath>,
|
||||
Query(query): Query<ExportQuery>,
|
||||
State(state): State<AppState>,
|
||||
) -> Result<Json<Value>, ApiError> {
|
||||
let services = state
|
||||
.service
|
||||
.list_soap_services(
|
||||
&path.workspace_id.as_str().into(),
|
||||
&path.operation_id.as_str().into(),
|
||||
query.version,
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(json!({ "services": services })))
|
||||
}
|
||||
|
||||
pub async fn generate_draft(
|
||||
Path(path): Path<WorkspaceOperationPath>,
|
||||
State(state): State<AppState>,
|
||||
@@ -394,10 +281,3 @@ pub async fn import_operation(
|
||||
.await?;
|
||||
Ok(Json(json!(imported)))
|
||||
}
|
||||
|
||||
fn header_file_name(headers: &HeaderMap) -> Option<String> {
|
||||
headers
|
||||
.get("x-file-name")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
+16
-478
@@ -2,7 +2,6 @@ use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
|
||||
use crank_adapter_soap::{SoapServiceSummary, inspect_wsdl};
|
||||
use crank_core::{
|
||||
Agent, AgentId, AgentOperationBinding, AgentStatus, AgentVersion, AggregationMode,
|
||||
AsyncJobHandle, AuthConfig, AuthKind, AuthProfile, AuthProfileId, ConfigExport,
|
||||
@@ -17,7 +16,6 @@ use crank_core::{
|
||||
Workspace, WorkspaceId, WorkspaceStatus,
|
||||
};
|
||||
use crank_mapping::{JsonPathRoot, MappingSet, infer_mapping_from_samples};
|
||||
use crank_proto::{ProtoService, services_from_descriptor_set_bytes};
|
||||
use crank_registry::{
|
||||
AgentSummary, AgentVersionRecord, AsyncJobFilter, CreateAgentRequest, CreateAsyncJobRequest,
|
||||
CreateInvitationRequest, CreateInvocationLogRequest, CreatePlatformApiKeyRequest,
|
||||
@@ -26,10 +24,10 @@ use crank_registry::{
|
||||
OperationAgentRef, OperationSampleMetadata, OperationSummary, OperationUsageSummary,
|
||||
OperationVersionRecord, Page, PlatformApiKeyRecord, PostgresRegistry, PublishAgentRequest,
|
||||
PublishRequest, RegistryError, RegistryOperation, RotateSecretRequest, SampleKind,
|
||||
SaveAgentBindingsRequest, SaveAuthProfileRequest, SaveDescriptorMetadataRequest,
|
||||
SaveSampleMetadataRequest, StreamSessionFilter, UpdateAsyncJobStatusRequest,
|
||||
UpdateWorkspaceRequest, UsageAgentBreakdown, UsageBucket, UsageOperationBreakdown, UsageQuery,
|
||||
UsageSummary, UsageTimelinePoint, WorkspaceMembershipRecord, WorkspaceRecord,
|
||||
SaveAgentBindingsRequest, SaveAuthProfileRequest, SaveSampleMetadataRequest,
|
||||
StreamSessionFilter, UpdateAsyncJobStatusRequest, UpdateWorkspaceRequest,
|
||||
UsageAgentBreakdown, UsageBucket, UsageOperationBreakdown, UsageQuery, UsageSummary,
|
||||
UsageTimelinePoint, WorkspaceMembershipRecord, WorkspaceRecord,
|
||||
};
|
||||
use crank_runtime::{
|
||||
PreparedRequest, ResolvedAuth, RuntimeError, RuntimeExecutor, RuntimeOperation,
|
||||
@@ -696,8 +694,6 @@ pub struct GrpcMethodSummary {
|
||||
pub output_schema: Schema,
|
||||
}
|
||||
|
||||
pub type SoapServiceCatalog = Vec<SoapServiceSummary>;
|
||||
|
||||
fn default_operation_category() -> String {
|
||||
"general".to_owned()
|
||||
}
|
||||
@@ -3265,332 +3261,6 @@ impl AdminService {
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self, payload), fields(operation_id = %operation_id.as_str(), source_name = source_name.unwrap_or("descriptor-set.bin")))]
|
||||
pub async fn upload_descriptor_set(
|
||||
&self,
|
||||
workspace_id: &WorkspaceId,
|
||||
operation_id: &OperationId,
|
||||
source_name: Option<&str>,
|
||||
payload: &[u8],
|
||||
) -> Result<DescriptorUploadResponse, ApiError> {
|
||||
let summary = self.get_operation(workspace_id, operation_id).await?;
|
||||
if summary.protocol != Protocol::Grpc {
|
||||
return Err(ApiError::validation(
|
||||
"descriptor upload is only allowed for grpc operations",
|
||||
));
|
||||
}
|
||||
|
||||
let services = services_from_descriptor_set_bytes(payload)
|
||||
.map_err(|error| ApiError::validation(error.to_string()))?;
|
||||
let descriptor_id = crank_core::DescriptorId::new(new_prefixed_id("desc"));
|
||||
let created_at = OffsetDateTime::now_utc();
|
||||
let storage_ref = self
|
||||
.storage
|
||||
.write_descriptor(
|
||||
operation_id,
|
||||
summary.current_draft_version,
|
||||
crank_registry::DescriptorKind::DescriptorSet,
|
||||
&descriptor_id,
|
||||
source_name,
|
||||
payload,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.registry
|
||||
.save_descriptor_metadata(SaveDescriptorMetadataRequest {
|
||||
descriptor: &crank_registry::DescriptorMetadata {
|
||||
id: descriptor_id.clone(),
|
||||
operation_id: Some(operation_id.clone()),
|
||||
version: Some(summary.current_draft_version),
|
||||
descriptor_kind: crank_registry::DescriptorKind::DescriptorSet,
|
||||
storage_ref,
|
||||
source_name: source_name.map(ToOwned::to_owned),
|
||||
package_index: Some(
|
||||
serde_json::to_value(&services)
|
||||
.map_err(|error| ApiError::internal(error.to_string()))?,
|
||||
),
|
||||
created_at,
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
info!(
|
||||
operation_id = %operation_id.as_str(),
|
||||
descriptor_id = %descriptor_id.as_str(),
|
||||
version = summary.current_draft_version,
|
||||
"descriptor set uploaded"
|
||||
);
|
||||
|
||||
Ok(DescriptorUploadResponse {
|
||||
descriptor_id: descriptor_id.as_str().to_owned(),
|
||||
version: summary.current_draft_version,
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self, payload), fields(operation_id = %operation_id.as_str(), source_name = source_name.unwrap_or("schema.proto")))]
|
||||
pub async fn upload_proto_file(
|
||||
&self,
|
||||
workspace_id: &WorkspaceId,
|
||||
operation_id: &OperationId,
|
||||
source_name: Option<&str>,
|
||||
payload: &[u8],
|
||||
) -> Result<DescriptorUploadResponse, ApiError> {
|
||||
let summary = self.get_operation(workspace_id, operation_id).await?;
|
||||
if summary.protocol != Protocol::Grpc {
|
||||
return Err(ApiError::validation(
|
||||
"proto upload is only allowed for grpc operations",
|
||||
));
|
||||
}
|
||||
|
||||
let descriptor_id = crank_core::DescriptorId::new(new_prefixed_id("desc"));
|
||||
let created_at = OffsetDateTime::now_utc();
|
||||
let storage_ref = self
|
||||
.storage
|
||||
.write_descriptor(
|
||||
operation_id,
|
||||
summary.current_draft_version,
|
||||
crank_registry::DescriptorKind::ProtoUpload,
|
||||
&descriptor_id,
|
||||
source_name,
|
||||
payload,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.registry
|
||||
.save_descriptor_metadata(SaveDescriptorMetadataRequest {
|
||||
descriptor: &crank_registry::DescriptorMetadata {
|
||||
id: descriptor_id.clone(),
|
||||
operation_id: Some(operation_id.clone()),
|
||||
version: Some(summary.current_draft_version),
|
||||
descriptor_kind: crank_registry::DescriptorKind::ProtoUpload,
|
||||
storage_ref,
|
||||
source_name: source_name.map(ToOwned::to_owned),
|
||||
package_index: None,
|
||||
created_at,
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
info!(
|
||||
operation_id = %operation_id.as_str(),
|
||||
descriptor_id = %descriptor_id.as_str(),
|
||||
version = summary.current_draft_version,
|
||||
"proto file uploaded"
|
||||
);
|
||||
|
||||
Ok(DescriptorUploadResponse {
|
||||
descriptor_id: descriptor_id.as_str().to_owned(),
|
||||
version: summary.current_draft_version,
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self), fields(operation_id = %operation_id.as_str(), version))]
|
||||
pub async fn list_grpc_services(
|
||||
&self,
|
||||
workspace_id: &WorkspaceId,
|
||||
operation_id: &OperationId,
|
||||
version: Option<u32>,
|
||||
) -> Result<Vec<GrpcServiceSummary>, ApiError> {
|
||||
let summary = self.get_operation(workspace_id, operation_id).await?;
|
||||
if summary.protocol != Protocol::Grpc {
|
||||
return Err(ApiError::validation(
|
||||
"grpc services are only available for grpc operations",
|
||||
));
|
||||
}
|
||||
|
||||
let version = version.unwrap_or(summary.current_draft_version);
|
||||
let descriptor = self
|
||||
.registry
|
||||
.list_descriptor_metadata(operation_id, version)
|
||||
.await?
|
||||
.into_iter()
|
||||
.rev()
|
||||
.find(|descriptor| {
|
||||
descriptor.descriptor_kind == crank_registry::DescriptorKind::DescriptorSet
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
ApiError::not_found_with_context(
|
||||
"descriptor set was not uploaded",
|
||||
json!({
|
||||
"operation_id": operation_id.as_str(),
|
||||
"version": version,
|
||||
"descriptor_kind": "descriptor_set",
|
||||
}),
|
||||
)
|
||||
})?;
|
||||
let bytes = self.storage.read_bytes(&descriptor.storage_ref).await?;
|
||||
let services = services_from_descriptor_set_bytes(&bytes)
|
||||
.map_err(|error| ApiError::validation(error.to_string()))?;
|
||||
|
||||
services
|
||||
.into_iter()
|
||||
.map(proto_service_summary)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
}
|
||||
|
||||
#[instrument(skip(self, payload), fields(operation_id = %operation_id.as_str(), source_name = source_name.unwrap_or("service.wsdl")))]
|
||||
pub async fn upload_wsdl_file(
|
||||
&self,
|
||||
workspace_id: &WorkspaceId,
|
||||
operation_id: &OperationId,
|
||||
source_name: Option<&str>,
|
||||
payload: &[u8],
|
||||
) -> Result<DescriptorUploadResponse, ApiError> {
|
||||
let summary = self.get_operation(workspace_id, operation_id).await?;
|
||||
if summary.protocol != Protocol::Soap {
|
||||
return Err(ApiError::validation(
|
||||
"wsdl upload is only allowed for soap operations",
|
||||
));
|
||||
}
|
||||
|
||||
let services =
|
||||
inspect_wsdl(payload).map_err(|error| ApiError::validation(error.to_string()))?;
|
||||
let descriptor_id = crank_core::DescriptorId::new(new_prefixed_id("desc"));
|
||||
let created_at = OffsetDateTime::now_utc();
|
||||
let storage_ref = self
|
||||
.storage
|
||||
.write_descriptor(
|
||||
operation_id,
|
||||
summary.current_draft_version,
|
||||
crank_registry::DescriptorKind::WsdlUpload,
|
||||
&descriptor_id,
|
||||
source_name,
|
||||
payload,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.registry
|
||||
.save_descriptor_metadata(SaveDescriptorMetadataRequest {
|
||||
descriptor: &crank_registry::DescriptorMetadata {
|
||||
id: descriptor_id.clone(),
|
||||
operation_id: Some(operation_id.clone()),
|
||||
version: Some(summary.current_draft_version),
|
||||
descriptor_kind: crank_registry::DescriptorKind::WsdlUpload,
|
||||
storage_ref,
|
||||
source_name: source_name.map(ToOwned::to_owned),
|
||||
package_index: Some(
|
||||
serde_json::to_value(&services)
|
||||
.map_err(|error| ApiError::internal(error.to_string()))?,
|
||||
),
|
||||
created_at,
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
info!(
|
||||
operation_id = %operation_id.as_str(),
|
||||
descriptor_id = %descriptor_id.as_str(),
|
||||
version = summary.current_draft_version,
|
||||
"wsdl uploaded"
|
||||
);
|
||||
|
||||
Ok(DescriptorUploadResponse {
|
||||
descriptor_id: descriptor_id.as_str().to_owned(),
|
||||
version: summary.current_draft_version,
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self, payload), fields(operation_id = %operation_id.as_str(), source_name = source_name.unwrap_or("schema.xsd")))]
|
||||
pub async fn upload_xsd_file(
|
||||
&self,
|
||||
workspace_id: &WorkspaceId,
|
||||
operation_id: &OperationId,
|
||||
source_name: Option<&str>,
|
||||
payload: &[u8],
|
||||
) -> Result<DescriptorUploadResponse, ApiError> {
|
||||
let summary = self.get_operation(workspace_id, operation_id).await?;
|
||||
if summary.protocol != Protocol::Soap {
|
||||
return Err(ApiError::validation(
|
||||
"xsd upload is only allowed for soap operations",
|
||||
));
|
||||
}
|
||||
|
||||
let descriptor_id = crank_core::DescriptorId::new(new_prefixed_id("desc"));
|
||||
let created_at = OffsetDateTime::now_utc();
|
||||
let storage_ref = self
|
||||
.storage
|
||||
.write_descriptor(
|
||||
operation_id,
|
||||
summary.current_draft_version,
|
||||
crank_registry::DescriptorKind::XsdUpload,
|
||||
&descriptor_id,
|
||||
source_name,
|
||||
payload,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.registry
|
||||
.save_descriptor_metadata(SaveDescriptorMetadataRequest {
|
||||
descriptor: &crank_registry::DescriptorMetadata {
|
||||
id: descriptor_id.clone(),
|
||||
operation_id: Some(operation_id.clone()),
|
||||
version: Some(summary.current_draft_version),
|
||||
descriptor_kind: crank_registry::DescriptorKind::XsdUpload,
|
||||
storage_ref,
|
||||
source_name: source_name.map(ToOwned::to_owned),
|
||||
package_index: None,
|
||||
created_at,
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
info!(
|
||||
operation_id = %operation_id.as_str(),
|
||||
descriptor_id = %descriptor_id.as_str(),
|
||||
version = summary.current_draft_version,
|
||||
"xsd uploaded"
|
||||
);
|
||||
|
||||
Ok(DescriptorUploadResponse {
|
||||
descriptor_id: descriptor_id.as_str().to_owned(),
|
||||
version: summary.current_draft_version,
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self), fields(operation_id = %operation_id.as_str(), version))]
|
||||
pub async fn list_soap_services(
|
||||
&self,
|
||||
workspace_id: &WorkspaceId,
|
||||
operation_id: &OperationId,
|
||||
version: Option<u32>,
|
||||
) -> Result<SoapServiceCatalog, ApiError> {
|
||||
let summary = self.get_operation(workspace_id, operation_id).await?;
|
||||
if summary.protocol != Protocol::Soap {
|
||||
return Err(ApiError::validation(
|
||||
"soap services are only available for soap operations",
|
||||
));
|
||||
}
|
||||
|
||||
let version = version.unwrap_or(summary.current_draft_version);
|
||||
let descriptor = self
|
||||
.registry
|
||||
.list_descriptor_metadata(operation_id, version)
|
||||
.await?
|
||||
.into_iter()
|
||||
.rev()
|
||||
.find(|descriptor| {
|
||||
descriptor.descriptor_kind == crank_registry::DescriptorKind::WsdlUpload
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
ApiError::not_found_with_context(
|
||||
"wsdl was not uploaded",
|
||||
json!({
|
||||
"operation_id": operation_id.as_str(),
|
||||
"version": version,
|
||||
"descriptor_kind": "wsdl_upload",
|
||||
}),
|
||||
)
|
||||
})?;
|
||||
let package_index = descriptor.package_index.ok_or_else(|| {
|
||||
ApiError::not_found_with_context(
|
||||
"wsdl inspection metadata is not available",
|
||||
json!({
|
||||
"operation_id": operation_id.as_str(),
|
||||
"version": version,
|
||||
"descriptor_kind": "wsdl_upload",
|
||||
}),
|
||||
)
|
||||
})?;
|
||||
|
||||
serde_json::from_value(package_index).map_err(|error| ApiError::internal(error.to_string()))
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
pub async fn get_auth_profile(
|
||||
&self,
|
||||
@@ -4802,11 +4472,7 @@ fn protocol_capability_view(protocol: Protocol, edition: ProductEdition) -> Prot
|
||||
.filter(|behavior| protocol.supports_transport_behavior(*behavior))
|
||||
.collect()
|
||||
};
|
||||
let supports_upload_artifacts = match protocol {
|
||||
Protocol::Rest | Protocol::Graphql | Protocol::Websocket => Vec::new(),
|
||||
Protocol::Grpc => vec!["proto".to_owned(), "descriptor_set".to_owned()],
|
||||
Protocol::Soap => vec!["wsdl".to_owned(), "xsd".to_owned()],
|
||||
};
|
||||
let supports_upload_artifacts = Vec::new();
|
||||
|
||||
ProtocolCapabilityView {
|
||||
protocol,
|
||||
@@ -5072,13 +4738,9 @@ fn validate_response_cache_policy(
|
||||
|
||||
match target {
|
||||
Target::Rest(rest_target) if rest_target.method == crank_core::HttpMethod::Get => {}
|
||||
Target::Graphql(graphql_target)
|
||||
if graphql_target.operation_type == crank_core::GraphqlOperationType::Query => {}
|
||||
Target::Grpc(grpc_target) if grpc_target.read_only => {}
|
||||
_ => {
|
||||
return Err(ApiError::validation_with_context(
|
||||
"response cache is supported only for REST GET, GraphQL query, and read-only unary gRPC operations"
|
||||
.to_owned(),
|
||||
"response cache is supported only for REST GET operations".to_owned(),
|
||||
json!({
|
||||
"field": "execution_config.response_cache",
|
||||
}),
|
||||
@@ -5103,8 +4765,7 @@ mod tests {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crank_core::{
|
||||
ExecutionConfig, GraphqlOperationType, GraphqlTarget, GrpcTarget, HttpMethod,
|
||||
ResponseCachePolicy, RestTarget, StreamingConfig, Target, TransportBehavior,
|
||||
ExecutionConfig, HttpMethod, ResponseCachePolicy, RestTarget, Target,
|
||||
};
|
||||
|
||||
use super::validate_response_cache_policy;
|
||||
@@ -5121,44 +4782,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_response_cache_for_graphql_query() {
|
||||
let target = Target::Graphql(GraphqlTarget {
|
||||
endpoint: "http://example.invalid/graphql".to_owned(),
|
||||
operation_type: GraphqlOperationType::Query,
|
||||
operation_name: "LookupLead".to_owned(),
|
||||
query_template:
|
||||
"query LookupLead($email: String!) { lookupLead(email: $email) { id } }".to_owned(),
|
||||
response_path: "$.response.body.data.lookupLead".to_owned(),
|
||||
});
|
||||
|
||||
let result = validate_response_cache_policy(&target, &cacheable_execution_config());
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_response_cache_for_graphql_mutation() {
|
||||
let target = Target::Graphql(GraphqlTarget {
|
||||
endpoint: "http://example.invalid/graphql".to_owned(),
|
||||
operation_type: GraphqlOperationType::Mutation,
|
||||
operation_name: "CreateLead".to_owned(),
|
||||
query_template:
|
||||
"mutation CreateLead($email: String!) { createLead(email: $email) { id } }"
|
||||
.to_owned(),
|
||||
response_path: "$.response.body.data.createLead".to_owned(),
|
||||
});
|
||||
|
||||
let error =
|
||||
validate_response_cache_policy(&target, &cacheable_execution_config()).unwrap_err();
|
||||
|
||||
assert!(matches!(error, crate::error::ApiError::Validation { .. }));
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
"response cache is supported only for REST GET, GraphQL query, and read-only unary gRPC operations"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_response_cache_for_rest_get() {
|
||||
let target = Target::Rest(RestTarget {
|
||||
@@ -5174,32 +4797,14 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_response_cache_for_read_only_unary_grpc() {
|
||||
let target = Target::Grpc(GrpcTarget {
|
||||
server_addr: "http://127.0.0.1:50051".to_owned(),
|
||||
package: "echo".to_owned(),
|
||||
service: "EchoService".to_owned(),
|
||||
method: "UnaryEcho".to_owned(),
|
||||
read_only: true,
|
||||
descriptor_ref: "desc_echo".into(),
|
||||
descriptor_set_b64: "ZmFrZQ==".to_owned(),
|
||||
});
|
||||
|
||||
let result = validate_response_cache_policy(&target, &cacheable_execution_config());
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_response_cache_for_non_read_only_grpc() {
|
||||
let target = Target::Grpc(GrpcTarget {
|
||||
server_addr: "http://127.0.0.1:50051".to_owned(),
|
||||
package: "echo".to_owned(),
|
||||
service: "EchoService".to_owned(),
|
||||
method: "UnaryEcho".to_owned(),
|
||||
read_only: false,
|
||||
descriptor_ref: "desc_echo".into(),
|
||||
descriptor_set_b64: "ZmFrZQ==".to_owned(),
|
||||
fn rejects_response_cache_for_non_rest_target() {
|
||||
let target = Target::Graphql(crank_core::GraphqlTarget {
|
||||
endpoint: "http://example.invalid/graphql".to_owned(),
|
||||
operation_type: crank_core::GraphqlOperationType::Query,
|
||||
operation_name: "LookupLead".to_owned(),
|
||||
query_template:
|
||||
"query LookupLead($email: String!) { lookupLead(email: $email) { id } }".to_owned(),
|
||||
response_path: "$.response.body.data.lookupLead".to_owned(),
|
||||
});
|
||||
|
||||
let error =
|
||||
@@ -5208,52 +4813,7 @@ mod tests {
|
||||
assert!(matches!(error, crate::error::ApiError::Validation { .. }));
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
"response cache is supported only for REST GET, GraphQL query, and read-only unary gRPC operations"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_response_cache_for_streaming_grpc() {
|
||||
let target = Target::Grpc(GrpcTarget {
|
||||
server_addr: "http://127.0.0.1:50051".to_owned(),
|
||||
package: "echo".to_owned(),
|
||||
service: "EchoService".to_owned(),
|
||||
method: "ServerEcho".to_owned(),
|
||||
read_only: true,
|
||||
descriptor_ref: "desc_echo".into(),
|
||||
descriptor_set_b64: "ZmFrZQ==".to_owned(),
|
||||
});
|
||||
let mut execution_config = cacheable_execution_config();
|
||||
execution_config.streaming = Some(StreamingConfig {
|
||||
mode: crank_core::ExecutionMode::Window,
|
||||
transport_behavior: TransportBehavior::ServerStream,
|
||||
window_duration_ms: Some(1_000),
|
||||
poll_interval_ms: None,
|
||||
upstream_timeout_ms: Some(1_000),
|
||||
idle_timeout_ms: None,
|
||||
max_session_lifetime_ms: None,
|
||||
max_items: Some(1),
|
||||
max_bytes: None,
|
||||
aggregation_mode: crank_core::AggregationMode::RawItems,
|
||||
summary_path: None,
|
||||
items_path: None,
|
||||
cursor_path: None,
|
||||
status_path: None,
|
||||
done_path: None,
|
||||
redacted_paths: Vec::new(),
|
||||
truncate_item_fields: false,
|
||||
max_field_length: None,
|
||||
drop_duplicates: false,
|
||||
sampling_rate: None,
|
||||
tool_family: crank_core::ToolFamilyConfig::default(),
|
||||
});
|
||||
|
||||
let error = validate_response_cache_policy(&target, &execution_config).unwrap_err();
|
||||
|
||||
assert!(matches!(error, crate::error::ApiError::Validation { .. }));
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
"response cache is supported only for unary operations"
|
||||
"response cache is supported only for REST GET operations"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5283,25 +4843,3 @@ fn enrich_operation_summary(
|
||||
agent_refs,
|
||||
}
|
||||
}
|
||||
|
||||
fn proto_service_summary(service: ProtoService) -> Result<GrpcServiceSummary, ApiError> {
|
||||
let methods = service
|
||||
.unary_methods()
|
||||
.map(|method| {
|
||||
Ok(GrpcMethodSummary {
|
||||
name: method.name.clone(),
|
||||
kind: "unary".to_owned(),
|
||||
input_schema: crank_proto::message_to_schema(&method.input)
|
||||
.map_err(|error| ApiError::validation(error.to_string()))?,
|
||||
output_schema: crank_proto::message_to_schema(&method.output)
|
||||
.map_err(|error| ApiError::validation(error.to_string()))?,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, ApiError>>()?;
|
||||
|
||||
Ok(GrpcServiceSummary {
|
||||
package: service.package,
|
||||
service: service.name,
|
||||
methods,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crank_core::{DescriptorId, OperationId, SampleId};
|
||||
use crank_registry::{DescriptorKind, SampleKind};
|
||||
use crank_core::{OperationId, SampleId};
|
||||
use crank_registry::SampleKind;
|
||||
use serde_json::Value;
|
||||
use thiserror::Error;
|
||||
use tokio::fs;
|
||||
@@ -58,40 +58,6 @@ impl LocalArtifactStorage {
|
||||
Ok(serde_json::from_slice(&bytes)?)
|
||||
}
|
||||
|
||||
pub async fn write_descriptor(
|
||||
&self,
|
||||
operation_id: &OperationId,
|
||||
version: u32,
|
||||
descriptor_kind: DescriptorKind,
|
||||
descriptor_id: &DescriptorId,
|
||||
source_name: Option<&str>,
|
||||
payload: &[u8],
|
||||
) -> Result<String, StorageError> {
|
||||
let file_name = match descriptor_kind {
|
||||
DescriptorKind::ProtoUpload => source_name.unwrap_or("schema.proto").to_owned(),
|
||||
DescriptorKind::DescriptorSet => source_name.unwrap_or("descriptor-set.bin").to_owned(),
|
||||
DescriptorKind::ReflectionSnapshot => {
|
||||
source_name.unwrap_or("reflection.bin").to_owned()
|
||||
}
|
||||
DescriptorKind::WsdlUpload => source_name.unwrap_or("service.wsdl").to_owned(),
|
||||
DescriptorKind::XsdUpload => source_name.unwrap_or("schema.xsd").to_owned(),
|
||||
};
|
||||
let path = self
|
||||
.root
|
||||
.join("descriptors")
|
||||
.join(operation_id.as_str())
|
||||
.join(format!("v{version}"))
|
||||
.join(format!("{}_{}", descriptor_id.as_str(), file_name));
|
||||
|
||||
write_bytes(&path, payload).await?;
|
||||
|
||||
Ok(to_storage_ref(&path))
|
||||
}
|
||||
|
||||
pub async fn read_bytes(&self, storage_ref: &str) -> Result<Vec<u8>, StorageError> {
|
||||
let path = from_storage_ref(storage_ref)?;
|
||||
Ok(fs::read(path).await?)
|
||||
}
|
||||
}
|
||||
|
||||
async fn write_json_file(path: &Path, payload: &Value) -> Result<(), StorageError> {
|
||||
@@ -104,15 +70,6 @@ async fn write_json_file(path: &Path, payload: &Value) -> Result<(), StorageErro
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_bytes(path: &Path, payload: &[u8]) -> Result<(), StorageError> {
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent).await?;
|
||||
}
|
||||
|
||||
fs::write(path, payload).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn to_storage_ref(path: &Path) -> String {
|
||||
format!("file://{}", path.display())
|
||||
}
|
||||
|
||||
@@ -6,26 +6,17 @@ rust-version.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[features]
|
||||
default = [
|
||||
"protocol-graphql",
|
||||
"protocol-grpc",
|
||||
"protocol-soap",
|
||||
"protocol-websocket",
|
||||
]
|
||||
protocol-graphql = ["dep:crank-adapter-graphql"]
|
||||
protocol-grpc = ["dep:crank-adapter-grpc"]
|
||||
protocol-soap = ["dep:crank-adapter-soap"]
|
||||
protocol-websocket = ["dep:crank-adapter-websocket"]
|
||||
default = []
|
||||
protocol-graphql = []
|
||||
protocol-grpc = []
|
||||
protocol-soap = []
|
||||
protocol-websocket = []
|
||||
|
||||
[dependencies]
|
||||
aes-gcm.workspace = true
|
||||
async-trait = "0.1"
|
||||
base64.workspace = true
|
||||
crank-adapter-graphql = { path = "../crank-adapter-graphql", optional = true }
|
||||
crank-adapter-grpc = { path = "../crank-adapter-grpc", optional = true }
|
||||
crank-adapter-rest = { path = "../crank-adapter-rest" }
|
||||
crank-adapter-soap = { path = "../crank-adapter-soap", optional = true }
|
||||
crank-adapter-websocket = { path = "../crank-adapter-websocket", optional = true }
|
||||
crank-core = { path = "../crank-core" }
|
||||
crank-mapping = { path = "../crank-mapping" }
|
||||
crank-schema = { path = "../crank-schema" }
|
||||
@@ -40,8 +31,6 @@ tracing.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
axum.workspace = true
|
||||
crank-adapter-grpc = { path = "../crank-adapter-grpc", features = ["test-support"] }
|
||||
futures-util = "0.3"
|
||||
time.workspace = true
|
||||
tokio-tungstenite.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
|
||||
@@ -824,12 +824,8 @@ fn response_cache_ttl(operation: &RuntimeOperation) -> Option<Duration> {
|
||||
return None;
|
||||
}
|
||||
|
||||
let is_cacheable_protocol = match &operation.target {
|
||||
Target::Rest(target) => target.method == HttpMethod::Get,
|
||||
Target::Graphql(target) => target.operation_type == crank_core::GraphqlOperationType::Query,
|
||||
Target::Grpc(target) => target.read_only,
|
||||
_ => false,
|
||||
};
|
||||
let is_cacheable_protocol =
|
||||
matches!(&operation.target, Target::Rest(target) if target.method == HttpMethod::Get);
|
||||
if !is_cacheable_protocol {
|
||||
return None;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user