use exonum::{
proto::schema::{INCLUDES as EXONUM_INCLUDES, PROTO_SOURCES as EXONUM_PROTO_SOURCES},
runtime::{versioning::Version, ArtifactId, RuntimeIdentifier},
};
use exonum_api::{self as api, ApiBuilder};
use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap;
use super::RustRuntime;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProtoSourceFile {
pub name: String,
pub content: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ProtoSourcesQuery {
Core,
Artifact {
name: String,
version: Version,
},
}
#[derive(Debug, Default, Clone, PartialEq)]
pub struct ArtifactProtobufSpec {
pub sources: Vec<ProtoSourceFile>,
pub includes: Vec<ProtoSourceFile>,
}
impl ArtifactProtobufSpec {
pub fn new(
sources: impl IntoIterator<Item = impl Into<ProtoSourceFile>>,
includes: impl IntoIterator<Item = impl Into<ProtoSourceFile>>,
) -> Self {
Self {
sources: sources.into_iter().map(|x| x.into()).collect(),
includes: includes.into_iter().map(|x| x.into()).collect(),
}
}
}
impl From<&(&str, &str)> for ProtoSourceFile {
fn from(v: &(&str, &str)) -> Self {
Self {
name: v.0.to_owned(),
content: v.1.to_owned(),
}
}
}
fn exonum_proto_sources() -> Vec<ProtoSourceFile> {
let proto = EXONUM_PROTO_SOURCES
.as_ref()
.iter()
.map(From::from)
.collect::<Vec<_>>();
let includes = EXONUM_INCLUDES
.as_ref()
.iter()
.map(From::from)
.collect::<Vec<_>>();
proto.into_iter().chain(includes).collect()
}
fn filter_exonum_proto_sources(
files: Vec<ProtoSourceFile>,
exonum_sources: &[ProtoSourceFile],
) -> Vec<ProtoSourceFile> {
files
.into_iter()
.filter(|file| !exonum_sources.contains(file))
.collect()
}
pub fn endpoints(runtime: &RustRuntime) -> impl IntoIterator<Item = (String, ApiBuilder)> {
let artifact_proto_sources = runtime
.available_artifacts
.iter()
.map(|(artifact_id, service_factory)| {
(
artifact_id.clone(),
service_factory.artifact_protobuf_spec(),
)
})
.collect::<HashMap<_, _>>();
let exonum_sources = exonum_proto_sources();
let filtered_sources = artifact_proto_sources
.into_iter()
.map(|(artifact_id, sources)| {
let mut proto = sources.sources;
proto.extend(filter_exonum_proto_sources(
sources.includes,
&exonum_sources,
));
(artifact_id, proto)
})
.collect::<HashMap<_, _>>();
let mut builder = ApiBuilder::new();
builder
.public_scope()
.endpoint("proto-sources", {
move |query: ProtoSourcesQuery| -> api::Result<Vec<ProtoSourceFile>> {
if let ProtoSourcesQuery::Artifact { name, version } = query {
let artifact_id = ArtifactId::new(RuntimeIdentifier::Rust, name, version)
.map_err(|e| {
api::Error::bad_request()
.title("Invalid query")
.detail(format!("Invalid artifact query: {}", e))
})?;
filtered_sources.get(&artifact_id).cloned().ok_or_else(|| {
api::Error::not_found()
.title("Artifact sources not found")
.detail(format!(
"Unable to find sources for artifact {}",
artifact_id
))
})
} else {
Ok(exonum_sources.clone())
}
}
});
std::iter::once((["runtimes/", RustRuntime::NAME].concat(), builder))
}