mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-08 21:58:20 +00:00
feat: Add wasmer-api crate
Provides bindings to the Wasmer GraphQL api.
This commit is contained in:
25
Cargo.lock
generated
25
Cargo.lock
generated
@@ -5841,6 +5841,29 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmer-api"
|
||||
version = "0.0.23"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.13.1",
|
||||
"cynic",
|
||||
"edge-schema 0.0.2",
|
||||
"futures",
|
||||
"harsh",
|
||||
"pin-project-lite",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
"webc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmer-api"
|
||||
version = "0.0.23"
|
||||
@@ -6173,7 +6196,7 @@ dependencies = [
|
||||
"uuid",
|
||||
"virtual-mio 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"virtual-net 0.6.1",
|
||||
"wasmer-api",
|
||||
"wasmer-api 0.0.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wasmer-registry 5.10.0",
|
||||
"wasmer-toml",
|
||||
"webc",
|
||||
|
||||
@@ -39,6 +39,7 @@ members = [
|
||||
"fuzz",
|
||||
"lib/api",
|
||||
"lib/api/macro-wasmer-universal-test",
|
||||
"lib/backend-api",
|
||||
"lib/c-api",
|
||||
"lib/c-api/examples/wasmer-capi-examples-runner",
|
||||
"lib/c-api/tests/wasmer-c-api-test-runner",
|
||||
|
||||
94
lib/backend-api/CHANGELOG.md
Normal file
94
lib/backend-api/CHANGELOG.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 0.0.1 (2023-04-27)
|
||||
|
||||
* Removed legacy API implementation in favour of the new cynic client
|
||||
* Fixed log querying
|
||||
* Added methods for retrieving DeployApp/Version by unique id
|
||||
|
||||
## 0.1.0-alpha.1 (2023-03-29)
|
||||
|
||||
<csr-id-7195702c618c0fb0937244034ee4600531b97034/>
|
||||
<csr-id-b8b9af7429a8c9b0880be9bf72f1b8a732d05d75/>
|
||||
<csr-id-a40f5127796316069aa2ac646ea5c3817f7a29fa/>
|
||||
<csr-id-065513b5f6ee350b9f2607d5aa7003df352f5cbc/>
|
||||
<csr-id-38336ebe12a85b142871a4c0fa50541df815a441/>
|
||||
<csr-id-8febcde88d1d7df8a7a86c79afcf960c2cb868a3/>
|
||||
<csr-id-33f822d9701e87c0f0564574a76adef83ccf72a5/>
|
||||
<csr-id-d968694ade1191afff80e6d141598b9c10b2f3c7/>
|
||||
<csr-id-b1245bfa194e05d49809973b716e2565689bfccf/>
|
||||
|
||||
### Refactor (BREAKING)
|
||||
|
||||
- <csr-id-7195702c618c0fb0937244034ee4600531b97034/> Rename wasmer-deploy-core to wasmer-deploy-schema
|
||||
-schema is a more sensible / expressive name for the crate,
|
||||
since it just holds type definitions.
|
||||
|
||||
Done in preparation for publishing the crate, since it will need to be
|
||||
used by downstream consumers like the Wasmer repo
|
||||
|
||||
### Chore
|
||||
|
||||
- <csr-id-bbc3105d5f04bc4c35dad794443f53980106981e/> Add description to wasmer-api Cargo.toml
|
||||
Required for releasing.
|
||||
|
||||
### Other
|
||||
|
||||
- <csr-id-b8b9af7429a8c9b0880be9bf72f1b8a732d05d75/> Dependency cleanup
|
||||
* Lift some dependencies to workspace.dependencies to avoid duplication
|
||||
* Remove a bunch of unused dependencies
|
||||
- <csr-id-a40f5127796316069aa2ac646ea5c3817f7a29fa/> Add crate metadata and prepare for first CLI release
|
||||
- <csr-id-065513b5f6ee350b9f2607d5aa7003df352f5cbc/> "app list" filters
|
||||
Extend the "app list" command to query either a namespace, a users apps,
|
||||
or all apps accessible by a user.
|
||||
(--namepsace X, --all)
|
||||
- <csr-id-38336ebe12a85b142871a4c0fa50541df815a441/> Add a webc app config fetch tests
|
||||
- <csr-id-8febcde88d1d7df8a7a86c79afcf960c2cb868a3/> Make serde_json a workspace dependency
|
||||
To avoid duplication...
|
||||
- <csr-id-33f822d9701e87c0f0564574a76adef83ccf72a5/> Lift serde to be a workspace dependency
|
||||
Easier version management...
|
||||
- <csr-id-d968694ade1191afff80e6d141598b9c10b2f3c7/> Lift anyhow, time and clap to workspace dependnecies
|
||||
Less version management...
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- <csr-id-07a199f0bcccd3178e8f773ae96d100febfb88d0/> Use token for webc fetching
|
||||
If the api is configured with a token, use the token for fetching webcs.
|
||||
Previously it just used anonymous access.
|
||||
- <csr-id-c6ad494a45968bb4a69455f3c292b6fcf9631770/> Update deployment config generation to backend changes
|
||||
The generateDeployConfig GraphQL API has changed
|
||||
|
||||
* Takes a DeployConfigVersion id instead of DeployConfig id
|
||||
* Returns a DeployConfigVersion
|
||||
|
||||
### New Features
|
||||
|
||||
- <csr-id-f96944cd8097aff11d51e8e0c3f6fa1efc6b1ec6/> Add generate_deploy_token to new cynic GQL api client
|
||||
Will be needed for various commands
|
||||
- <csr-id-33e469b921256a241866a5a972278665905dcdf4/> Add getPackage GQL query
|
||||
- <csr-id-4783d6a5c53875724bbea3ed5a65b13e5056c001/> Add query for DeployAppVersion
|
||||
- <csr-id-b4aa770fb388970c837f2e5429caa5803eef64bf/> Add new namespace and app commands
|
||||
- <csr-id-63ec5f98dca19c417df0e42a5bfbbd963c9b19c2/> Add a CapabilityLoggingV1 config
|
||||
Allows to configure the logging behaviour of workloads.
|
||||
|
||||
Will be used very soon to implement instance log forwarding.
|
||||
|
||||
### Documentation
|
||||
|
||||
- <csr-id-98b9313b34e93c7973b778ef0867f042bf7aed57/> add some changelogs
|
||||
- <csr-id-845a8b3ebece96d5fff941110a425ab19a3a2eed/> Add REAMDE to Cargo.toml of to-be-published crates
|
||||
|
||||
### Chore
|
||||
|
||||
- <csr-id-b1245bfa194e05d49809973b716e2565689bfccf/> Remove download_url from WebcPackageIdentifierV1
|
||||
Not needed anymore, since now we have a deployment config registry.
|
||||
|
||||
<csr-unknown>
|
||||
Needed to update relevant callsites<csr-unknown/>
|
||||
<csr-unknown/>
|
||||
|
||||
39
lib/backend-api/Cargo.toml
Normal file
39
lib/backend-api/Cargo.toml
Normal file
@@ -0,0 +1,39 @@
|
||||
[package]
|
||||
name = "wasmer-api"
|
||||
version = "0.0.23"
|
||||
description = "Wasmer API client library."
|
||||
readme = "README.md"
|
||||
homepage = "https://wasmer.io"
|
||||
documentation = "https://docs.rs/wasmer-api"
|
||||
# NOTE: Using a distinct license for the CLI, since it might be different from
|
||||
# other crates.
|
||||
license = "MIT"
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
# Wasmer dependencies.
|
||||
edge-schema = "0.0.2"
|
||||
webc = "5"
|
||||
|
||||
# crates.io dependencies.
|
||||
anyhow = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
time = { version = "0.3", features = ["formatting", "parsing"] }
|
||||
tokio = { version = "1.23.0" }
|
||||
serde_json = "1"
|
||||
url = "2"
|
||||
futures = "0.3"
|
||||
tracing = "0.1"
|
||||
cynic = { version = "3.2.2", features = ["http-reqwest"] }
|
||||
pin-project-lite = "0.2.10"
|
||||
serde_path_to_error = "0.1.14"
|
||||
harsh = "0.2.2"
|
||||
reqwest = { version = "0.11.13", default-features = false, features = ["json"] }
|
||||
|
||||
[dev-dependencies]
|
||||
base64 = "0.13.1"
|
||||
tokio = { version = "1.3", features = ["macros", "rt"] }
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
46
lib/backend-api/README.md
Normal file
46
lib/backend-api/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# wasmer-api
|
||||
|
||||
GraphQL API client for the [Wasmer](https://wasmer.io) backend.
|
||||
|
||||
## Development
|
||||
|
||||
This client is built on the [cynic][cynic-api-docs] crate,
|
||||
a GraphQL client library that allows tight integration between Rust and
|
||||
GraphQL types.
|
||||
|
||||
It was chosen over other implementations like `graphql-client` because it
|
||||
significantly reduces boilerplate and improves the development experience.
|
||||
|
||||
The downside is that the underlying GraphQL queries are much less obvious when
|
||||
looking at the code. This can be remedied with some strategies mentioned below.
|
||||
|
||||
Consult the Cynic docs at [cynic-rs.dev][cynic-website] for more
|
||||
information.
|
||||
|
||||
### Backend GraphQL Schema
|
||||
|
||||
The GraphQL schema for the backend is stored in `./schema.graphql`.
|
||||
|
||||
To update the schema, simply download the latest version and replace the local
|
||||
file.
|
||||
|
||||
It can be retrieved from
|
||||
https://github.com/wasmerio/wapm.io-backend/blob/master/backend/graphql/schema.graphql.
|
||||
|
||||
### Writing/Updating Queries
|
||||
|
||||
You can use the [Cynic web UI][cynic-web-ui] to easily create the types for new
|
||||
queries.
|
||||
|
||||
Simply upload the local schema from `./schema.graphql` and use the UI to build
|
||||
your desired query.
|
||||
|
||||
NOTE: Where possible, do not duplicate types that are already defined,
|
||||
and instead reuse/extend them where possible.
|
||||
|
||||
This is not always sensible though, depending on which nested data you want to
|
||||
fetch.
|
||||
|
||||
[cynic-api-docs]: https://docs.rs/cynic/latest/cynic/
|
||||
[cynic-web-ui]: https://docs.rs/cynic/latest/cynic/
|
||||
[cynic-website]: https://cynic-rs.dev
|
||||
2624
lib/backend-api/schema.graphql
Normal file
2624
lib/backend-api/schema.graphql
Normal file
File diff suppressed because it is too large
Load Diff
209
lib/backend-api/src/client.rs
Normal file
209
lib/backend-api/src/client.rs
Normal file
@@ -0,0 +1,209 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{bail, Context as _};
|
||||
use cynic::{http::CynicReqwestError, GraphQlResponse, Operation};
|
||||
use url::Url;
|
||||
|
||||
use crate::GraphQLApiFailure;
|
||||
|
||||
/// API client for the Wasmer API.
|
||||
///
|
||||
/// Use the queries in [`crate::queries`] to interact with the API.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WasmerClient {
|
||||
auth_token: Option<String>,
|
||||
graphql_endpoint: Url,
|
||||
|
||||
pub(crate) client: reqwest::Client,
|
||||
pub(crate) user_agent: reqwest::header::HeaderValue,
|
||||
#[allow(unused)]
|
||||
extra_debugging: bool,
|
||||
}
|
||||
|
||||
impl WasmerClient {
|
||||
pub fn graphql_endpoint(&self) -> &Url {
|
||||
&self.graphql_endpoint
|
||||
}
|
||||
|
||||
pub fn auth_token(&self) -> Option<&str> {
|
||||
self.auth_token.as_deref()
|
||||
}
|
||||
|
||||
fn parse_user_agent(user_agent: &str) -> Result<reqwest::header::HeaderValue, anyhow::Error> {
|
||||
if user_agent.is_empty() {
|
||||
bail!("user agent must not be empty");
|
||||
}
|
||||
user_agent
|
||||
.parse()
|
||||
.with_context(|| format!("invalid user agent: '{}'", user_agent))
|
||||
}
|
||||
|
||||
pub fn new_with_client(
|
||||
client: reqwest::Client,
|
||||
graphql_endpoint: Url,
|
||||
user_agent: &str,
|
||||
) -> Result<Self, anyhow::Error> {
|
||||
Ok(Self {
|
||||
client,
|
||||
auth_token: None,
|
||||
user_agent: Self::parse_user_agent(user_agent)?,
|
||||
graphql_endpoint,
|
||||
extra_debugging: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new(graphql_endpoint: Url, user_agent: &str) -> Result<Self, anyhow::Error> {
|
||||
let client = reqwest::Client::builder()
|
||||
.connect_timeout(Duration::from_secs(10))
|
||||
.timeout(Duration::from_secs(90))
|
||||
.build()
|
||||
.context("could not construct http client")?;
|
||||
Self::new_with_client(client, graphql_endpoint, user_agent)
|
||||
}
|
||||
|
||||
pub fn with_auth_token(mut self, auth_token: String) -> Self {
|
||||
self.auth_token = Some(auth_token);
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) async fn run_graphql_raw<ResponseData, Vars>(
|
||||
&self,
|
||||
operation: Operation<ResponseData, Vars>,
|
||||
) -> Result<cynic::GraphQlResponse<ResponseData>, anyhow::Error>
|
||||
where
|
||||
Vars: serde::Serialize + std::fmt::Debug,
|
||||
ResponseData: serde::de::DeserializeOwned + std::fmt::Debug + 'static,
|
||||
{
|
||||
let req = self
|
||||
.client
|
||||
.post(self.graphql_endpoint.as_str())
|
||||
.header(reqwest::header::USER_AGENT, &self.user_agent);
|
||||
let req = if let Some(token) = &self.auth_token {
|
||||
req.bearer_auth(token)
|
||||
} else {
|
||||
req
|
||||
};
|
||||
|
||||
if self.extra_debugging {
|
||||
tracing::trace!(
|
||||
query=%operation.query,
|
||||
vars=?operation.variables,
|
||||
"running GraphQL query"
|
||||
);
|
||||
}
|
||||
let query = operation.query.clone();
|
||||
|
||||
tracing::trace!(
|
||||
endpoint=%self.graphql_endpoint,
|
||||
query=serde_json::to_string(&operation).unwrap_or_default(),
|
||||
"sending graphql query"
|
||||
);
|
||||
|
||||
let res = req.json(&operation).send().await;
|
||||
|
||||
let res = match res {
|
||||
Ok(response) => {
|
||||
let status = response.status();
|
||||
if !status.is_success() {
|
||||
let body_string = match response.text().await {
|
||||
Ok(b) => b,
|
||||
Err(err) => {
|
||||
tracing::error!("could not load response body: {err}");
|
||||
"<could not retrieve body>".to_string()
|
||||
}
|
||||
};
|
||||
|
||||
match serde_json::from_str::<GraphQlResponse<ResponseData>>(&body_string) {
|
||||
Ok(response) => Ok(response),
|
||||
Err(_) => Err(CynicReqwestError::ErrorResponse(status, body_string)),
|
||||
}
|
||||
} else {
|
||||
let body = response.bytes().await?;
|
||||
|
||||
let jd = &mut serde_json::Deserializer::from_slice(&body);
|
||||
let data: Result<GraphQlResponse<ResponseData>, _> =
|
||||
serde_path_to_error::deserialize(jd).map_err(|err| {
|
||||
let body_txt = String::from_utf8_lossy(&body);
|
||||
CynicReqwestError::ErrorResponse(
|
||||
reqwest::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Could not decode JSON response: {err} -- '{body_txt}'"),
|
||||
)
|
||||
});
|
||||
|
||||
data
|
||||
}
|
||||
}
|
||||
Err(e) => Err(CynicReqwestError::ReqwestError(e)),
|
||||
};
|
||||
let res = match res {
|
||||
Ok(res) => {
|
||||
tracing::trace!(?res, "GraphQL query succeeded");
|
||||
res
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!(?err, "GraphQL query failed");
|
||||
return Err(err.into());
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(errors) = &res.errors {
|
||||
if !errors.is_empty() {
|
||||
tracing::warn!(
|
||||
?errors,
|
||||
data=?res.data,
|
||||
%query,
|
||||
endpoint=%self.graphql_endpoint,
|
||||
"GraphQL query succeeded, but returned errors",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub(crate) async fn run_graphql<ResponseData, Vars>(
|
||||
&self,
|
||||
operation: Operation<ResponseData, Vars>,
|
||||
) -> Result<ResponseData, anyhow::Error>
|
||||
where
|
||||
Vars: serde::Serialize + std::fmt::Debug,
|
||||
ResponseData: serde::de::DeserializeOwned + std::fmt::Debug + 'static,
|
||||
{
|
||||
let res = self.run_graphql_raw(operation).await?;
|
||||
|
||||
if let Some(data) = res.data {
|
||||
Ok(data)
|
||||
} else if let Some(errs) = res.errors {
|
||||
let errs = GraphQLApiFailure { errors: errs };
|
||||
Err(errs).context("GraphQL query failed")
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Query did not return any data"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a GraphQL query, but fail (return an Error) if any error is returned
|
||||
/// in the response.
|
||||
pub(crate) async fn run_graphql_strict<ResponseData, Vars>(
|
||||
&self,
|
||||
operation: Operation<ResponseData, Vars>,
|
||||
) -> Result<ResponseData, anyhow::Error>
|
||||
where
|
||||
Vars: serde::Serialize + std::fmt::Debug,
|
||||
ResponseData: serde::de::DeserializeOwned + std::fmt::Debug + 'static,
|
||||
{
|
||||
let res = self.run_graphql_raw(operation).await?;
|
||||
|
||||
if let Some(errs) = res.errors {
|
||||
if !errs.is_empty() {
|
||||
let errs = GraphQLApiFailure { errors: errs };
|
||||
return Err(errs).context("GraphQL query failed");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(data) = res.data {
|
||||
Ok(data)
|
||||
} else {
|
||||
Err(anyhow::anyhow!("Query did not return any data"))
|
||||
}
|
||||
}
|
||||
}
|
||||
36
lib/backend-api/src/error.rs
Normal file
36
lib/backend-api/src/error.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
/// One or multiple errors returned by the GraphQL API.
|
||||
// Mainly exists to implement [`std::error::Error`].
|
||||
#[derive(Debug)]
|
||||
pub struct GraphQLApiFailure {
|
||||
pub errors: Vec<cynic::GraphQlError>,
|
||||
}
|
||||
|
||||
impl GraphQLApiFailure {
|
||||
pub fn from_errors(
|
||||
msg: impl Into<String>,
|
||||
errors: Option<Vec<cynic::GraphQlError>>,
|
||||
) -> anyhow::Error {
|
||||
let msg = msg.into();
|
||||
if let Some(errs) = errors {
|
||||
if !errs.is_empty() {
|
||||
let err = GraphQLApiFailure { errors: errs };
|
||||
return anyhow::Error::new(err).context(msg);
|
||||
}
|
||||
}
|
||||
anyhow::anyhow!("{msg} - query did not return any data")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for GraphQLApiFailure {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let errs = self
|
||||
.errors
|
||||
.iter()
|
||||
.map(|err| err.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
write!(f, "GraphQL API failure: {}", errs)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for GraphQLApiFailure {}
|
||||
483
lib/backend-api/src/global_id.rs
Normal file
483
lib/backend-api/src/global_id.rs
Normal file
@@ -0,0 +1,483 @@
|
||||
//! [`GlobalId`]s are used by the backend to identify a specific object.
|
||||
//!
|
||||
//! This module provides a parser/encoder and related type defintions
|
||||
//! for global ids.
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
#[repr(u16)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum NodeKind {
|
||||
User = 0,
|
||||
SocialAuth = 1,
|
||||
Namespace = 2,
|
||||
Package = 3,
|
||||
PackageVersion = 4,
|
||||
PackageCollaborator = 5,
|
||||
PackageCollaboratorInvite = 6,
|
||||
NativeExecutable = 7,
|
||||
PackageVersionNPMBinding = 8,
|
||||
PackageVersionPythonBinding = 9,
|
||||
PackageTransferRequest = 10,
|
||||
Interface = 11,
|
||||
InterfaceVersion = 12,
|
||||
PublicKey = 13,
|
||||
UserNotification = 14,
|
||||
ActivityEvent = 15,
|
||||
NamespaceCollaborator = 16,
|
||||
NamespaceCollaboratorInvite = 17,
|
||||
BindingsGenerator = 18,
|
||||
DeployConfigVersion = 19,
|
||||
DeployConfigInfo = 20,
|
||||
DeployApp = 21,
|
||||
DeployAppVersion = 22,
|
||||
Waitlist = 23,
|
||||
WaitlistMember = 24,
|
||||
CardPaymentMethod = 25,
|
||||
PaymentIntent = 26,
|
||||
AppAlias = 27,
|
||||
Nonce = 28,
|
||||
TermsOfService = 29,
|
||||
}
|
||||
|
||||
impl NodeKind {
|
||||
pub fn from_num(x: u64) -> Option<Self> {
|
||||
match x {
|
||||
0 => Some(Self::User),
|
||||
1 => Some(Self::SocialAuth),
|
||||
2 => Some(Self::Namespace),
|
||||
3 => Some(Self::Package),
|
||||
4 => Some(Self::PackageVersion),
|
||||
5 => Some(Self::PackageCollaborator),
|
||||
6 => Some(Self::PackageCollaboratorInvite),
|
||||
7 => Some(Self::NativeExecutable),
|
||||
8 => Some(Self::PackageVersionNPMBinding),
|
||||
9 => Some(Self::PackageVersionPythonBinding),
|
||||
10 => Some(Self::PackageTransferRequest),
|
||||
11 => Some(Self::Interface),
|
||||
12 => Some(Self::InterfaceVersion),
|
||||
13 => Some(Self::PublicKey),
|
||||
14 => Some(Self::UserNotification),
|
||||
15 => Some(Self::ActivityEvent),
|
||||
16 => Some(Self::NamespaceCollaborator),
|
||||
17 => Some(Self::NamespaceCollaboratorInvite),
|
||||
18 => Some(Self::BindingsGenerator),
|
||||
19 => Some(Self::DeployConfigVersion),
|
||||
20 => Some(Self::DeployConfigInfo),
|
||||
21 => Some(Self::DeployApp),
|
||||
22 => Some(Self::DeployAppVersion),
|
||||
23 => Some(Self::Waitlist),
|
||||
24 => Some(Self::WaitlistMember),
|
||||
25 => Some(Self::CardPaymentMethod),
|
||||
26 => Some(Self::PaymentIntent),
|
||||
27 => Some(Self::AppAlias),
|
||||
28 => Some(Self::Nonce),
|
||||
29 => Some(Self::TermsOfService),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_prefix(s: &str) -> Option<NodeKind> {
|
||||
match s {
|
||||
"u" => Some(Self::User),
|
||||
"su" => Some(Self::SocialAuth),
|
||||
"ns" => Some(Self::Namespace),
|
||||
"pk" => Some(Self::Package),
|
||||
"pkv" => Some(Self::PackageVersion),
|
||||
"pc" => Some(Self::PackageCollaborator),
|
||||
"pci" => Some(Self::PackageCollaboratorInvite),
|
||||
"ne" => Some(Self::NativeExecutable),
|
||||
"pkvbjs" => Some(Self::PackageVersionNPMBinding),
|
||||
"pkvbpy" => Some(Self::PackageVersionPythonBinding),
|
||||
"pt" => Some(Self::PackageTransferRequest),
|
||||
"in" => Some(Self::Interface),
|
||||
"inv" => Some(Self::InterfaceVersion),
|
||||
"pub" => Some(Self::PublicKey),
|
||||
"nt" => Some(Self::UserNotification),
|
||||
"ae" => Some(Self::ActivityEvent),
|
||||
"nsc" => Some(Self::NamespaceCollaborator),
|
||||
"nsci" => Some(Self::NamespaceCollaboratorInvite),
|
||||
"bg" => Some(Self::BindingsGenerator),
|
||||
"dcv" => Some(Self::DeployConfigVersion),
|
||||
"dci" => Some(Self::DeployConfigInfo),
|
||||
"da" => Some(Self::DeployApp),
|
||||
"dav" => Some(Self::DeployAppVersion),
|
||||
"wl" => Some(Self::Waitlist),
|
||||
"wlm" => Some(Self::WaitlistMember),
|
||||
"cpm" => Some(Self::CardPaymentMethod),
|
||||
"pi" => Some(Self::PaymentIntent),
|
||||
"daa" => Some(Self::AppAlias),
|
||||
"nnc" => Some(Self::Nonce),
|
||||
"tos" => Some(Self::TermsOfService),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_prefix(&self) -> &'static str {
|
||||
match self {
|
||||
Self::User => "u",
|
||||
Self::SocialAuth => "su",
|
||||
Self::Namespace => "ns",
|
||||
Self::Package => "pk",
|
||||
Self::PackageVersion => "pkv",
|
||||
Self::PackageCollaborator => "pc",
|
||||
Self::PackageCollaboratorInvite => "pci",
|
||||
Self::NativeExecutable => "ne",
|
||||
Self::PackageVersionNPMBinding => "pkvbjs",
|
||||
Self::PackageVersionPythonBinding => "pkvbpy",
|
||||
Self::PackageTransferRequest => "pt",
|
||||
Self::Interface => "in",
|
||||
Self::InterfaceVersion => "inv",
|
||||
Self::PublicKey => "pub",
|
||||
Self::UserNotification => "nt",
|
||||
Self::ActivityEvent => "ae",
|
||||
Self::NamespaceCollaborator => "nsc",
|
||||
Self::NamespaceCollaboratorInvite => "nsci",
|
||||
Self::BindingsGenerator => "bg",
|
||||
Self::DeployConfigVersion => "dcv",
|
||||
Self::DeployConfigInfo => "dci",
|
||||
Self::DeployApp => "da",
|
||||
Self::DeployAppVersion => "dav",
|
||||
Self::Waitlist => "wl",
|
||||
Self::WaitlistMember => "wlm",
|
||||
Self::CardPaymentMethod => "cpm",
|
||||
Self::PaymentIntent => "pi",
|
||||
Self::AppAlias => "daa",
|
||||
Self::Nonce => "nnc",
|
||||
Self::TermsOfService => "tos",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for NodeKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let name = match self {
|
||||
Self::User => "User",
|
||||
Self::SocialAuth => "SocialAuth",
|
||||
Self::Namespace => "Namespace",
|
||||
Self::Package => "Package",
|
||||
Self::PackageVersion => "PackageVersion",
|
||||
Self::PackageCollaborator => "PackageCollaborator",
|
||||
Self::PackageCollaboratorInvite => "PackageCollaboratorInvite",
|
||||
Self::NativeExecutable => "NativeExecutable",
|
||||
Self::PackageVersionNPMBinding => "PackageVersionNPMBinding",
|
||||
Self::PackageVersionPythonBinding => "PackageVersionPythonBinding",
|
||||
Self::PackageTransferRequest => "PackageTransferRequest",
|
||||
Self::Interface => "Interface",
|
||||
Self::InterfaceVersion => "InterfaceVersion",
|
||||
Self::PublicKey => "PublicKey",
|
||||
Self::UserNotification => "UserNotification",
|
||||
Self::ActivityEvent => "ActivityEvent",
|
||||
Self::NamespaceCollaborator => "NamespaceCollaborator",
|
||||
Self::NamespaceCollaboratorInvite => "NamespaceCollaboratorInvite",
|
||||
Self::BindingsGenerator => "BindingsGenerator",
|
||||
Self::DeployConfigVersion => "DeployConfigVersion",
|
||||
Self::DeployConfigInfo => "DeployConfigInfo",
|
||||
Self::DeployApp => "DeployApp",
|
||||
Self::DeployAppVersion => "DeployAppVersion",
|
||||
Self::Waitlist => "Waitlist",
|
||||
Self::WaitlistMember => "WaitlistMember",
|
||||
Self::CardPaymentMethod => "CardPaymentMethod",
|
||||
Self::PaymentIntent => "PaymentIntent",
|
||||
Self::AppAlias => "AppAlias",
|
||||
Self::Nonce => "Nonce",
|
||||
Self::TermsOfService => "TermsOfService",
|
||||
};
|
||||
write!(f, "{name}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Global id of backend nodes.
|
||||
///
|
||||
/// IDs are encoded using the "hashid" scheme, which uses a given alphabet and
|
||||
/// a salt to encode u64 numbers into a string hash.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct GlobalId {
|
||||
/// The node type of the ID.
|
||||
kind: NodeKind,
|
||||
/// The database ID of the node.
|
||||
database_id: u64,
|
||||
}
|
||||
|
||||
impl GlobalId {
|
||||
/// Salt used by the backend to encode hashes.
|
||||
const SALT: &'static str = "wasmer salt hashid";
|
||||
/// Minimum length of the encoded hashes.
|
||||
const MIN_LENGTH: usize = 12;
|
||||
|
||||
/// Hash alphabet used for the prefix id variant.
|
||||
const ALPHABET_PREFIXED: &'static str =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
||||
|
||||
/// Hash alphabet used for the non-prefixed id variant.
|
||||
const ALPHABET_URL: &'static str = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
pub fn new(kind: NodeKind, database_id: u64) -> Self {
|
||||
Self { kind, database_id }
|
||||
}
|
||||
|
||||
fn build_harsh(alphabet: &str, salt: &[u8]) -> harsh::Harsh {
|
||||
harsh::HarshBuilder::new()
|
||||
.alphabet(alphabet.as_bytes())
|
||||
.salt(salt)
|
||||
.length(GlobalId::MIN_LENGTH)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn build_harsh_prefixed() -> harsh::Harsh {
|
||||
Self::build_harsh(Self::ALPHABET_PREFIXED, Self::SALT.as_bytes())
|
||||
}
|
||||
|
||||
fn build_harsh_url() -> harsh::Harsh {
|
||||
Self::build_harsh(Self::ALPHABET_URL, Self::SALT.as_bytes())
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> NodeKind {
|
||||
self.kind
|
||||
}
|
||||
|
||||
pub fn database_id(&self) -> u64 {
|
||||
self.database_id
|
||||
}
|
||||
|
||||
/// Encode a prefixed global id.
|
||||
pub fn encode_prefixed(&self) -> String {
|
||||
let hash = Self::build_harsh_prefixed().encode(&[
|
||||
// scope
|
||||
1,
|
||||
// version
|
||||
2,
|
||||
self.kind as u64,
|
||||
self.database_id,
|
||||
]);
|
||||
|
||||
format!("{}_{}", self.kind.as_prefix(), hash)
|
||||
}
|
||||
|
||||
fn parse_values(values: &[u64]) -> Result<Self, ErrorKind> {
|
||||
let scope = values.first().cloned().ok_or(ErrorKind::MissingScope)?;
|
||||
|
||||
if scope != 1 {
|
||||
return Err(ErrorKind::UnknownScope(scope));
|
||||
}
|
||||
|
||||
let version = values.get(1).cloned().ok_or(ErrorKind::MissingVersion)?;
|
||||
if version != 2 {
|
||||
return Err(ErrorKind::UnknownVersion(version));
|
||||
}
|
||||
|
||||
let ty_raw = values.get(2).cloned().ok_or(ErrorKind::MissingNodeType)?;
|
||||
let ty_parsed = NodeKind::from_num(ty_raw).ok_or(ErrorKind::UnknownNodeType(ty_raw))?;
|
||||
|
||||
let db_id = values.get(3).cloned().ok_or(ErrorKind::MissingDatabaseId)?;
|
||||
|
||||
Ok(Self {
|
||||
kind: ty_parsed,
|
||||
database_id: db_id,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a prefixed global id.
|
||||
pub fn parse_prefixed(hash: &str) -> Result<Self, GlobalIdParseError> {
|
||||
let (prefix, value) = hash
|
||||
.split_once('_')
|
||||
.ok_or_else(|| GlobalIdParseError::new(hash, ErrorKind::MissingPrefix))?;
|
||||
|
||||
if prefix.is_empty() {
|
||||
return Err(GlobalIdParseError::new(hash, ErrorKind::MissingPrefix));
|
||||
}
|
||||
|
||||
let ty_prefix = NodeKind::parse_prefix(prefix).ok_or_else(|| {
|
||||
GlobalIdParseError::new(hash, ErrorKind::UnknownPrefix(prefix.to_string()))
|
||||
})?;
|
||||
|
||||
let values = Self::build_harsh_prefixed()
|
||||
.decode(value)
|
||||
.map_err(|err| GlobalIdParseError::new(hash, ErrorKind::Decode(err.to_string())))?;
|
||||
|
||||
let s = Self::parse_values(&values).map_err(|kind| GlobalIdParseError::new(hash, kind))?;
|
||||
|
||||
if ty_prefix != s.kind {
|
||||
return Err(GlobalIdParseError::new(hash, ErrorKind::PrefixTypeMismatch));
|
||||
}
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
/// Encode a non-prefixed global id.
|
||||
///
|
||||
/// Note: URL ids use a different alphabet than prefixed ids.
|
||||
pub fn encode_url(&self) -> String {
|
||||
Self::build_harsh_url().encode(&[
|
||||
// scope
|
||||
1,
|
||||
// version
|
||||
2,
|
||||
self.kind as u64,
|
||||
self.database_id,
|
||||
])
|
||||
}
|
||||
|
||||
/// Parse a non-prefixed URL global id variant.
|
||||
///
|
||||
/// Note: URL ids use a different alphabet than prefixed ids.
|
||||
pub fn parse_url(hash: &str) -> Result<Self, GlobalIdParseError> {
|
||||
let values = Self::build_harsh_url()
|
||||
.decode(hash)
|
||||
.map_err(|err| GlobalIdParseError::new(hash, ErrorKind::Decode(err.to_string())))?;
|
||||
|
||||
Self::parse_values(&values).map_err(|kind| GlobalIdParseError::new(hash, kind))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct GlobalIdParseError {
|
||||
id: String,
|
||||
kind: ErrorKind,
|
||||
}
|
||||
|
||||
impl GlobalIdParseError {
|
||||
fn new(id: impl Into<String>, kind: ErrorKind) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error type for parsing of [`GlobalId`]s.
|
||||
// Note: kept private on purpose, not useful to export.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
enum ErrorKind {
|
||||
MissingPrefix,
|
||||
UnknownPrefix(String),
|
||||
PrefixTypeMismatch,
|
||||
MissingScope,
|
||||
UnknownScope(u64),
|
||||
MissingVersion,
|
||||
UnknownVersion(u64),
|
||||
MissingNodeType,
|
||||
UnknownNodeType(u64),
|
||||
MissingDatabaseId,
|
||||
Decode(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for GlobalIdParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "could not parse global id '{}': ", self.id)?;
|
||||
|
||||
match &self.kind {
|
||||
ErrorKind::UnknownPrefix(p) => {
|
||||
write!(f, "unknown type prefix '{}'", p)
|
||||
}
|
||||
ErrorKind::Decode(s) => {
|
||||
write!(f, "decode error: {}", s)
|
||||
}
|
||||
ErrorKind::MissingScope => {
|
||||
write!(f, "missing scope value")
|
||||
}
|
||||
ErrorKind::UnknownScope(x) => {
|
||||
write!(f, "unknown scope value {}", x)
|
||||
}
|
||||
ErrorKind::MissingVersion => {
|
||||
write!(f, "missing version value")
|
||||
}
|
||||
ErrorKind::UnknownVersion(v) => {
|
||||
write!(f, "unknown version value {}", v)
|
||||
}
|
||||
ErrorKind::UnknownNodeType(t) => {
|
||||
write!(f, "unknown node type '{}'", t)
|
||||
}
|
||||
ErrorKind::MissingPrefix => write!(f, "missing prefix"),
|
||||
ErrorKind::PrefixTypeMismatch => write!(f, "prefix type mismatch"),
|
||||
ErrorKind::MissingNodeType => write!(f, "missing node type"),
|
||||
ErrorKind::MissingDatabaseId => write!(f, "missing database id"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for GlobalIdParseError {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_global_id() {
|
||||
// Roundtrip.
|
||||
let x1 = GlobalId {
|
||||
kind: NodeKind::DeployApp,
|
||||
database_id: 123,
|
||||
};
|
||||
assert_eq!(Ok(x1), GlobalId::parse_prefixed(&x1.encode_prefixed()),);
|
||||
assert_eq!(Ok(x1), GlobalId::parse_url(&x1.encode_url()));
|
||||
|
||||
assert_eq!(
|
||||
GlobalId::parse_prefixed("da_MRrWI0t5U582"),
|
||||
Ok(GlobalId {
|
||||
kind: NodeKind::DeployApp,
|
||||
database_id: 273,
|
||||
})
|
||||
);
|
||||
|
||||
// Error conditions.
|
||||
assert_eq!(
|
||||
GlobalId::parse_prefixed("oOtQIDI7q").err().unwrap().kind,
|
||||
ErrorKind::MissingPrefix,
|
||||
);
|
||||
assert_eq!(
|
||||
GlobalId::parse_prefixed("oOtQIDI7q").err().unwrap().kind,
|
||||
ErrorKind::MissingPrefix,
|
||||
);
|
||||
assert_eq!(
|
||||
GlobalId::parse_prefixed("_oOtQIDI7q").err().unwrap().kind,
|
||||
ErrorKind::MissingPrefix,
|
||||
);
|
||||
assert_eq!(
|
||||
GlobalId::parse_prefixed("lala_oOtQIDI7q")
|
||||
.err()
|
||||
.unwrap()
|
||||
.kind,
|
||||
ErrorKind::UnknownPrefix("lala".to_string()),
|
||||
);
|
||||
|
||||
let kind = GlobalId::parse_prefixed("da_xxx").err().unwrap().kind;
|
||||
assert!(matches!(kind, ErrorKind::Decode(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_global_id_parse_values() {
|
||||
assert_eq!(GlobalId::parse_values(&[]), Err(ErrorKind::MissingScope),);
|
||||
assert_eq!(
|
||||
GlobalId::parse_values(&[2]),
|
||||
Err(ErrorKind::UnknownScope(2)),
|
||||
);
|
||||
assert_eq!(GlobalId::parse_values(&[1]), Err(ErrorKind::MissingVersion),);
|
||||
assert_eq!(
|
||||
GlobalId::parse_values(&[1, 999]),
|
||||
Err(ErrorKind::UnknownVersion(999)),
|
||||
);
|
||||
assert_eq!(
|
||||
GlobalId::parse_values(&[1, 2]),
|
||||
Err(ErrorKind::MissingNodeType),
|
||||
);
|
||||
assert_eq!(
|
||||
GlobalId::parse_values(&[1, 2, 99999]),
|
||||
Err(ErrorKind::UnknownNodeType(99999)),
|
||||
);
|
||||
assert_eq!(
|
||||
GlobalId::parse_values(&[1, 2, 1]),
|
||||
Err(ErrorKind::MissingDatabaseId),
|
||||
);
|
||||
assert_eq!(
|
||||
GlobalId::parse_values(&[1, 2, 1, 1]),
|
||||
Ok(GlobalId {
|
||||
kind: NodeKind::SocialAuth,
|
||||
database_id: 1,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
2
lib/backend-api/src/gql.rs
Normal file
2
lib/backend-api/src/gql.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
types
|
||||
29
lib/backend-api/src/lib.rs
Normal file
29
lib/backend-api/src/lib.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
// Allowed because it makes code more readable.
|
||||
#![allow(clippy::bool_comparison, clippy::match_like_matches_macro)]
|
||||
|
||||
mod client;
|
||||
mod error;
|
||||
|
||||
pub mod global_id;
|
||||
pub mod query;
|
||||
pub mod stream;
|
||||
pub mod types;
|
||||
|
||||
use url::Url;
|
||||
|
||||
pub use self::{client::WasmerClient, error::GraphQLApiFailure};
|
||||
|
||||
/// Api endpoint for the dev environment.
|
||||
pub const ENDPOINT_DEV: &str = "https://registry.wasmer.wtf/graphql";
|
||||
/// Api endpoint for the prod environment.
|
||||
pub const ENDPOINT_PROD: &str = "https://registry.wasmer.io/graphql";
|
||||
|
||||
/// API endpoint for the dev environment.
|
||||
pub fn endpoint_dev() -> Url {
|
||||
Url::parse(ENDPOINT_DEV).unwrap()
|
||||
}
|
||||
|
||||
/// API endpoint for the prod environment.
|
||||
pub fn endpoint_prod() -> Url {
|
||||
Url::parse(ENDPOINT_PROD).unwrap()
|
||||
}
|
||||
796
lib/backend-api/src/query.rs
Normal file
796
lib/backend-api/src/query.rs
Normal file
@@ -0,0 +1,796 @@
|
||||
use std::{collections::HashSet, time::Duration};
|
||||
|
||||
use anyhow::{bail, Context};
|
||||
use cynic::{MutationBuilder, QueryBuilder};
|
||||
use edge_schema::schema::{NetworkTokenV1, WebcIdent};
|
||||
use futures::StreamExt;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::Instrument;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
types::{
|
||||
self, CreateNamespaceVars, DeployApp, DeployAppConnection, DeployAppVersion,
|
||||
DeployAppVersionConnection, GetDeployAppAndVersion, GetDeployAppVersionsVars,
|
||||
GetNamespaceAppsVars, Log, PackageVersionConnection, PublishDeployAppVars,
|
||||
},
|
||||
GraphQLApiFailure, WasmerClient,
|
||||
};
|
||||
|
||||
/// Load a webc package from the registry.
|
||||
///
|
||||
/// NOTE: this uses the public URL instead of the download URL available through
|
||||
/// the API, and should not be used where possible.
|
||||
pub async fn fetch_webc_package(
|
||||
client: &WasmerClient,
|
||||
ident: &WebcIdent,
|
||||
default_registry: &Url,
|
||||
) -> Result<webc::compat::Container, anyhow::Error> {
|
||||
let url = ident.build_download_url_with_default_registry(default_registry);
|
||||
let data = client
|
||||
.client
|
||||
.get(url)
|
||||
.header(reqwest::header::USER_AGENT, &client.user_agent)
|
||||
.header(reqwest::header::ACCEPT, "application/webc")
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.bytes()
|
||||
.await?;
|
||||
|
||||
webc::compat::Container::from_bytes(data).context("failed to parse webc package")
|
||||
}
|
||||
|
||||
/// Get the currently logged in used, together with all accessible namespaces.
|
||||
///
|
||||
/// You can optionally filter the namespaces by the user role.
|
||||
pub async fn current_user_with_namespaces(
|
||||
client: &WasmerClient,
|
||||
namespace_role: Option<types::GrapheneRole>,
|
||||
) -> Result<types::UserWithNamespaces, anyhow::Error> {
|
||||
client
|
||||
.run_graphql(types::GetCurrentUser::build(types::GetCurrentUserVars {
|
||||
namespace_role,
|
||||
}))
|
||||
.await?
|
||||
.viewer
|
||||
.context("not logged in")
|
||||
}
|
||||
|
||||
/// Retrieve an app.
|
||||
pub async fn get_app(
|
||||
client: &WasmerClient,
|
||||
owner: String,
|
||||
name: String,
|
||||
) -> Result<Option<types::DeployApp>, anyhow::Error> {
|
||||
client
|
||||
.run_graphql(types::GetDeployApp::build(types::GetDeployAppVars {
|
||||
name,
|
||||
owner,
|
||||
}))
|
||||
.await
|
||||
.map(|x| x.get_deploy_app)
|
||||
}
|
||||
|
||||
/// Retrieve an app by its global alias.
|
||||
pub async fn get_app_by_alias(
|
||||
client: &WasmerClient,
|
||||
alias: String,
|
||||
) -> Result<Option<types::DeployApp>, anyhow::Error> {
|
||||
client
|
||||
.run_graphql(types::GetDeployAppByAlias::build(
|
||||
types::GetDeployAppByAliasVars { alias },
|
||||
))
|
||||
.await
|
||||
.map(|x| x.get_app_by_global_alias)
|
||||
}
|
||||
|
||||
/// Retrieve an app version.
|
||||
pub async fn get_app_version(
|
||||
client: &WasmerClient,
|
||||
owner: String,
|
||||
name: String,
|
||||
version: String,
|
||||
) -> Result<Option<types::DeployAppVersion>, anyhow::Error> {
|
||||
client
|
||||
.run_graphql(types::GetDeployAppVersion::build(
|
||||
types::GetDeployAppVersionVars {
|
||||
name,
|
||||
owner,
|
||||
version,
|
||||
},
|
||||
))
|
||||
.await
|
||||
.map(|x| x.get_deploy_app_version)
|
||||
}
|
||||
|
||||
/// Retrieve an app together with a specific version.
|
||||
pub async fn get_app_with_version(
|
||||
client: &WasmerClient,
|
||||
owner: String,
|
||||
name: String,
|
||||
version: String,
|
||||
) -> Result<GetDeployAppAndVersion, anyhow::Error> {
|
||||
client
|
||||
.run_graphql(types::GetDeployAppAndVersion::build(
|
||||
types::GetDeployAppAndVersionVars {
|
||||
name,
|
||||
owner,
|
||||
version,
|
||||
},
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Retrieve an app together with a specific version.
|
||||
pub async fn get_app_and_package_by_name(
|
||||
client: &WasmerClient,
|
||||
vars: types::GetPackageAndAppVars,
|
||||
) -> Result<(Option<types::Package>, Option<types::DeployApp>), anyhow::Error> {
|
||||
let res = client
|
||||
.run_graphql(types::GetPackageAndApp::build(vars))
|
||||
.await?;
|
||||
Ok((res.get_package, res.get_deploy_app))
|
||||
}
|
||||
|
||||
/// Retrieve apps.
|
||||
pub async fn get_deploy_apps(
|
||||
client: &WasmerClient,
|
||||
vars: types::GetDeployAppsVars,
|
||||
) -> Result<DeployAppConnection, anyhow::Error> {
|
||||
let res = client
|
||||
.run_graphql(types::GetDeployApps::build(vars))
|
||||
.await?;
|
||||
res.get_deploy_apps.context("no apps returned")
|
||||
}
|
||||
|
||||
/// Retrieve apps as a stream that will automatically paginate.
|
||||
pub fn get_deploy_apps_stream(
|
||||
client: &WasmerClient,
|
||||
vars: types::GetDeployAppsVars,
|
||||
) -> impl futures::Stream<Item = Result<Vec<DeployApp>, anyhow::Error>> + '_ {
|
||||
futures::stream::try_unfold(
|
||||
Some(vars),
|
||||
move |vars: Option<types::GetDeployAppsVars>| async move {
|
||||
let vars = match vars {
|
||||
Some(vars) => vars,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let page = get_deploy_apps(client, vars.clone()).await?;
|
||||
|
||||
let end_cursor = page.page_info.end_cursor;
|
||||
|
||||
let items = page
|
||||
.edges
|
||||
.into_iter()
|
||||
.filter_map(|x| x.and_then(|x| x.node))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let new_vars = end_cursor.map(|c| types::GetDeployAppsVars {
|
||||
after: Some(c),
|
||||
..vars
|
||||
});
|
||||
|
||||
Ok(Some((items, new_vars)))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Retrieve versions for an app.
|
||||
pub async fn get_deploy_app_versions(
|
||||
client: &WasmerClient,
|
||||
vars: GetDeployAppVersionsVars,
|
||||
) -> Result<DeployAppVersionConnection, anyhow::Error> {
|
||||
let res = client
|
||||
.run_graphql_strict(types::GetDeployAppVersions::build(vars))
|
||||
.await?;
|
||||
let versions = res.get_deploy_app.context("app not found")?.versions;
|
||||
Ok(versions)
|
||||
}
|
||||
|
||||
/// Load all versions of an app.
|
||||
///
|
||||
/// Will paginate through all versions and return them in a single list.
|
||||
pub async fn all_app_versions(
|
||||
client: &WasmerClient,
|
||||
owner: String,
|
||||
name: String,
|
||||
) -> Result<Vec<DeployAppVersion>, anyhow::Error> {
|
||||
let mut vars = GetDeployAppVersionsVars {
|
||||
owner,
|
||||
name,
|
||||
offset: None,
|
||||
before: None,
|
||||
after: None,
|
||||
first: Some(10),
|
||||
last: None,
|
||||
sort_by: None,
|
||||
};
|
||||
|
||||
let mut all_versions = Vec::<DeployAppVersion>::new();
|
||||
|
||||
loop {
|
||||
let page = get_deploy_app_versions(client, vars.clone()).await?;
|
||||
dbg!(&page);
|
||||
if page.edges.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
for edge in page.edges {
|
||||
let edge = match edge {
|
||||
Some(edge) => edge,
|
||||
None => continue,
|
||||
};
|
||||
let version = match edge.node {
|
||||
Some(item) => item,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
// Sanity check to avoid duplication.
|
||||
if all_versions.iter().any(|v| v.id == version.id) == false {
|
||||
all_versions.push(version);
|
||||
}
|
||||
|
||||
// Update pagination.
|
||||
vars.after = Some(edge.cursor);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(all_versions)
|
||||
}
|
||||
|
||||
/// Activate a particular version of an app.
|
||||
pub async fn app_version_activate(
|
||||
client: &WasmerClient,
|
||||
version: String,
|
||||
) -> Result<DeployApp, anyhow::Error> {
|
||||
let res = client
|
||||
.run_graphql_strict(types::MarkAppVersionAsActive::build(
|
||||
types::MarkAppVersionAsActiveVars {
|
||||
input: types::MarkAppVersionAsActiveInput {
|
||||
app_version: version.into(),
|
||||
},
|
||||
},
|
||||
))
|
||||
.await?;
|
||||
res.mark_app_version_as_active
|
||||
.context("app not found")
|
||||
.map(|x| x.app)
|
||||
}
|
||||
|
||||
/// Retrieve a node based on its global id.
|
||||
pub async fn get_node(
|
||||
client: &WasmerClient,
|
||||
id: String,
|
||||
) -> Result<Option<types::Node>, anyhow::Error> {
|
||||
client
|
||||
.run_graphql(types::GetNode::build(types::GetNodeVars { id: id.into() }))
|
||||
.await
|
||||
.map(|x| x.node)
|
||||
}
|
||||
|
||||
/// Retrieve an app by its global id.
|
||||
pub async fn get_app_by_id(
|
||||
client: &WasmerClient,
|
||||
app_id: String,
|
||||
) -> Result<DeployApp, anyhow::Error> {
|
||||
client
|
||||
.run_graphql(types::GetDeployAppById::build(
|
||||
types::GetDeployAppByIdVars {
|
||||
app_id: app_id.into(),
|
||||
},
|
||||
))
|
||||
.await?
|
||||
.app
|
||||
.context("app not found")?
|
||||
.into_deploy_app()
|
||||
.context("app conversion failed")
|
||||
}
|
||||
|
||||
/// Retrieve an app together with a specific version.
|
||||
pub async fn get_app_with_version_by_id(
|
||||
client: &WasmerClient,
|
||||
app_id: String,
|
||||
version_id: String,
|
||||
) -> Result<(DeployApp, DeployAppVersion), anyhow::Error> {
|
||||
let res = client
|
||||
.run_graphql(types::GetDeployAppAndVersionById::build(
|
||||
types::GetDeployAppAndVersionByIdVars {
|
||||
app_id: app_id.into(),
|
||||
version_id: version_id.into(),
|
||||
},
|
||||
))
|
||||
.await?;
|
||||
|
||||
let app = res
|
||||
.app
|
||||
.context("app not found")?
|
||||
.into_deploy_app()
|
||||
.context("app conversion failed")?;
|
||||
let version = res
|
||||
.version
|
||||
.context("version not found")?
|
||||
.into_deploy_app_version()
|
||||
.context("version conversion failed")?;
|
||||
|
||||
Ok((app, version))
|
||||
}
|
||||
|
||||
/// Retrieve an app version by its global id.
|
||||
pub async fn get_app_version_by_id(
|
||||
client: &WasmerClient,
|
||||
version_id: String,
|
||||
) -> Result<DeployAppVersion, anyhow::Error> {
|
||||
client
|
||||
.run_graphql(types::GetDeployAppVersionById::build(
|
||||
types::GetDeployAppVersionByIdVars {
|
||||
version_id: version_id.into(),
|
||||
},
|
||||
))
|
||||
.await?
|
||||
.version
|
||||
.context("app not found")?
|
||||
.into_deploy_app_version()
|
||||
.context("app version conversion failed")
|
||||
}
|
||||
|
||||
pub async fn get_app_version_by_id_with_app(
|
||||
client: &WasmerClient,
|
||||
version_id: String,
|
||||
) -> Result<(DeployApp, DeployAppVersion), anyhow::Error> {
|
||||
let version = client
|
||||
.run_graphql(types::GetDeployAppVersionById::build(
|
||||
types::GetDeployAppVersionByIdVars {
|
||||
version_id: version_id.into(),
|
||||
},
|
||||
))
|
||||
.await?
|
||||
.version
|
||||
.context("app not found")?
|
||||
.into_deploy_app_version()
|
||||
.context("app version conversion failed")?;
|
||||
|
||||
let app_id = version
|
||||
.app
|
||||
.as_ref()
|
||||
.context("could not load app for version")?
|
||||
.id
|
||||
.clone();
|
||||
|
||||
let app = get_app_by_id(client, app_id.into_inner()).await?;
|
||||
|
||||
Ok((app, version))
|
||||
}
|
||||
|
||||
/// List all apps that are accessible by the current user.
|
||||
///
|
||||
/// NOTE: this will only include the first pages and does not provide pagination.
|
||||
pub async fn user_apps(client: &WasmerClient) -> Result<Vec<types::DeployApp>, anyhow::Error> {
|
||||
let user = client
|
||||
.run_graphql(types::GetCurrentUserWithApps::build(()))
|
||||
.await?
|
||||
.viewer
|
||||
.context("not logged in")?;
|
||||
|
||||
let apps = user
|
||||
.apps
|
||||
.edges
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|x| x.node)
|
||||
.collect();
|
||||
|
||||
Ok(apps)
|
||||
}
|
||||
|
||||
/// List all apps that are accessible by the current user.
|
||||
///
|
||||
/// NOTE: this does not currently do full pagination properly.
|
||||
// TODO(theduke): fix pagination
|
||||
pub async fn user_accessible_apps(
|
||||
client: &WasmerClient,
|
||||
) -> Result<Vec<types::DeployApp>, anyhow::Error> {
|
||||
let mut apps = Vec::new();
|
||||
|
||||
// Get user apps.
|
||||
|
||||
let user_apps = user_apps(client).await?;
|
||||
|
||||
apps.extend(user_apps);
|
||||
|
||||
// Get all aps in user-accessible namespaces.
|
||||
let namespace_res = client
|
||||
.run_graphql(types::GetCurrentUser::build(types::GetCurrentUserVars {
|
||||
namespace_role: None,
|
||||
}))
|
||||
.await?;
|
||||
let active_user = namespace_res.viewer.context("not logged in")?;
|
||||
let namespace_names = active_user
|
||||
.namespaces
|
||||
.edges
|
||||
.iter()
|
||||
.filter_map(|edge| edge.as_ref())
|
||||
.filter_map(|edge| edge.node.as_ref())
|
||||
.map(|node| node.name.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for namespace in namespace_names {
|
||||
let out = client
|
||||
.run_graphql(types::GetNamespaceApps::build(GetNamespaceAppsVars {
|
||||
name: namespace.to_string(),
|
||||
}))
|
||||
.await?;
|
||||
|
||||
if let Some(ns) = out.get_namespace {
|
||||
let ns_apps = ns.apps.edges.into_iter().flatten().filter_map(|x| x.node);
|
||||
apps.extend(ns_apps);
|
||||
}
|
||||
}
|
||||
Ok(apps)
|
||||
}
|
||||
|
||||
/// Get apps for a specific namespace.
|
||||
///
|
||||
/// NOTE: only retrieves the first page and does not do pagination.
|
||||
pub async fn namespace_apps(
|
||||
client: &WasmerClient,
|
||||
namespace: &str,
|
||||
) -> Result<Vec<types::DeployApp>, anyhow::Error> {
|
||||
let res = client
|
||||
.run_graphql(types::GetNamespaceApps::build(GetNamespaceAppsVars {
|
||||
name: namespace.to_string(),
|
||||
}))
|
||||
.await?;
|
||||
|
||||
let ns = res
|
||||
.get_namespace
|
||||
.with_context(|| format!("failed to get namespace '{}'", namespace))?;
|
||||
|
||||
let apps = ns
|
||||
.apps
|
||||
.edges
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|x| x.node)
|
||||
.collect();
|
||||
|
||||
Ok(apps)
|
||||
}
|
||||
|
||||
/// Publish a new app (version).
|
||||
pub async fn publish_deploy_app(
|
||||
client: &WasmerClient,
|
||||
vars: PublishDeployAppVars,
|
||||
) -> Result<DeployAppVersion, anyhow::Error> {
|
||||
let res = client
|
||||
.run_graphql_raw(types::PublishDeployApp::build(vars))
|
||||
.await?;
|
||||
|
||||
if let Some(app) = res
|
||||
.data
|
||||
.and_then(|d| d.publish_deploy_app)
|
||||
.map(|d| d.deploy_app_version)
|
||||
{
|
||||
Ok(app)
|
||||
} else {
|
||||
Err(GraphQLApiFailure::from_errors(
|
||||
"could not publish app",
|
||||
res.errors,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete an app.
|
||||
pub async fn delete_app(client: &WasmerClient, app_id: String) -> Result<(), anyhow::Error> {
|
||||
let res = client
|
||||
.run_graphql_strict(types::DeleteApp::build(types::DeleteAppVars {
|
||||
app_id: app_id.into(),
|
||||
}))
|
||||
.await?
|
||||
.delete_app
|
||||
.context("API did not return data for the delete_app mutation")?;
|
||||
|
||||
if !res.success {
|
||||
bail!("App deletion failed for an unknown reason");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get all namespaces accessible by the current user.
|
||||
pub async fn user_namespaces(
|
||||
client: &WasmerClient,
|
||||
) -> Result<Vec<types::Namespace>, anyhow::Error> {
|
||||
let user = client
|
||||
.run_graphql(types::GetCurrentUser::build(types::GetCurrentUserVars {
|
||||
namespace_role: None,
|
||||
}))
|
||||
.await?
|
||||
.viewer
|
||||
.context("not logged in")?;
|
||||
|
||||
let ns = user
|
||||
.namespaces
|
||||
.edges
|
||||
.into_iter()
|
||||
.flatten()
|
||||
// .filter_map(|x| x)
|
||||
.filter_map(|x| x.node)
|
||||
.collect();
|
||||
|
||||
Ok(ns)
|
||||
}
|
||||
|
||||
/// Retrieve a namespace by its name.
|
||||
pub async fn get_namespace(
|
||||
client: &WasmerClient,
|
||||
name: String,
|
||||
) -> Result<Option<types::Namespace>, anyhow::Error> {
|
||||
client
|
||||
.run_graphql(types::GetNamespace::build(types::GetNamespaceVars { name }))
|
||||
.await
|
||||
.map(|x| x.get_namespace)
|
||||
}
|
||||
|
||||
/// Create a new namespace.
|
||||
pub async fn create_namespace(
|
||||
client: &WasmerClient,
|
||||
vars: CreateNamespaceVars,
|
||||
) -> Result<types::Namespace, anyhow::Error> {
|
||||
client
|
||||
.run_graphql(types::CreateNamespace::build(vars))
|
||||
.await?
|
||||
.create_namespace
|
||||
.map(|x| x.namespace)
|
||||
.context("no namespace returned")
|
||||
}
|
||||
|
||||
/// Retrieve a package by its name.
|
||||
pub async fn get_package(
|
||||
client: &WasmerClient,
|
||||
name: String,
|
||||
) -> Result<Option<types::Package>, anyhow::Error> {
|
||||
client
|
||||
.run_graphql_strict(types::GetPackage::build(types::GetPackageVars { name }))
|
||||
.await
|
||||
.map(|x| x.get_package)
|
||||
}
|
||||
|
||||
/// Retrieve a package version by its name.
|
||||
pub async fn get_package_version(
|
||||
client: &WasmerClient,
|
||||
name: String,
|
||||
version: String,
|
||||
) -> Result<Option<types::PackageVersionWithPackage>, anyhow::Error> {
|
||||
client
|
||||
.run_graphql_strict(types::GetPackageVersion::build(
|
||||
types::GetPackageVersionVars { name, version },
|
||||
))
|
||||
.await
|
||||
.map(|x| x.get_package_version)
|
||||
}
|
||||
|
||||
/// Retrieve package versions for an app.
|
||||
pub async fn get_package_versions(
|
||||
client: &WasmerClient,
|
||||
vars: types::AllPackageVersionsVars,
|
||||
) -> Result<PackageVersionConnection, anyhow::Error> {
|
||||
let res = client
|
||||
.run_graphql(types::GetAllPackageVersions::build(vars))
|
||||
.await?;
|
||||
Ok(res.all_package_versions)
|
||||
}
|
||||
|
||||
/// Retrieve all versions of a package as a stream that auto-paginates.
|
||||
pub fn get_package_versions_stream(
|
||||
client: &WasmerClient,
|
||||
vars: types::AllPackageVersionsVars,
|
||||
) -> impl futures::Stream<Item = Result<Vec<types::PackageVersionWithPackage>, anyhow::Error>> + '_
|
||||
{
|
||||
futures::stream::try_unfold(
|
||||
Some(vars),
|
||||
move |vars: Option<types::AllPackageVersionsVars>| async move {
|
||||
let vars = match vars {
|
||||
Some(vars) => vars,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let page = get_package_versions(client, vars.clone()).await?;
|
||||
|
||||
let end_cursor = page.page_info.end_cursor;
|
||||
|
||||
let items = page
|
||||
.edges
|
||||
.into_iter()
|
||||
.filter_map(|x| x.and_then(|x| x.node))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let new_vars = end_cursor.map(|cursor| types::AllPackageVersionsVars {
|
||||
after: Some(cursor),
|
||||
..vars
|
||||
});
|
||||
|
||||
Ok(Some((items, new_vars)))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Generate a new Edge token.
|
||||
pub async fn generate_deploy_token_raw(
|
||||
client: &WasmerClient,
|
||||
app_version_id: String,
|
||||
) -> Result<String, anyhow::Error> {
|
||||
let res = client
|
||||
.run_graphql(types::GenerateDeployToken::build(
|
||||
types::GenerateDeployTokenVars { app_version_id },
|
||||
))
|
||||
.await?;
|
||||
|
||||
res.generate_deploy_token
|
||||
.map(|x| x.token)
|
||||
.context("no token returned")
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum GenerateTokenBy {
|
||||
Id(NetworkTokenV1),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum TokenKind {
|
||||
SSH,
|
||||
Network(GenerateTokenBy),
|
||||
}
|
||||
|
||||
pub async fn generate_deploy_config_token_raw(
|
||||
client: &WasmerClient,
|
||||
token_kind: TokenKind,
|
||||
) -> Result<String, anyhow::Error> {
|
||||
let res = client
|
||||
.run_graphql(types::GenerateDeployConfigToken::build(
|
||||
types::GenerateDeployConfigTokenVars {
|
||||
input: match token_kind {
|
||||
TokenKind::SSH => "{}".to_string(),
|
||||
TokenKind::Network(by) => match by {
|
||||
GenerateTokenBy::Id(token) => serde_json::to_string(&token)?,
|
||||
},
|
||||
},
|
||||
},
|
||||
))
|
||||
.await?;
|
||||
|
||||
res.generate_deploy_config_token
|
||||
.map(|x| x.token)
|
||||
.context("no token returned")
|
||||
}
|
||||
|
||||
/// Get pages of logs associated with an application that lie within the
|
||||
/// specified date range.
|
||||
// NOTE: this is not public due to severe usability issues.
|
||||
// The stream can loop forever due to re-fetching the same logs over and over.
|
||||
#[tracing::instrument(skip_all, level = "debug")]
|
||||
#[allow(clippy::let_with_type_underscore)]
|
||||
fn get_app_logs(
|
||||
client: &WasmerClient,
|
||||
name: String,
|
||||
owner: String,
|
||||
tag: Option<String>,
|
||||
start: OffsetDateTime,
|
||||
end: Option<OffsetDateTime>,
|
||||
watch: bool,
|
||||
) -> impl futures::Stream<Item = Result<Vec<Log>, anyhow::Error>> + '_ {
|
||||
// Note: the backend will limit responses to a certain number of log
|
||||
// messages, so we use try_unfold() to keep calling it until we stop getting
|
||||
// new log messages.
|
||||
let span = tracing::Span::current();
|
||||
|
||||
futures::stream::try_unfold(start, move |start| {
|
||||
let variables = types::GetDeployAppLogsVars {
|
||||
name: name.clone(),
|
||||
owner: owner.clone(),
|
||||
version: tag.clone(),
|
||||
// TODO: increase pagination size
|
||||
// See https://github.com/wasmerio/edge/issues/460
|
||||
// first: Some(500),
|
||||
first: Some(10),
|
||||
starting_from: unix_timestamp(start),
|
||||
until: end.map(unix_timestamp),
|
||||
};
|
||||
|
||||
let fut = async move {
|
||||
loop {
|
||||
let deploy_app_version = client
|
||||
.run_graphql(types::GetDeployAppLogs::build(variables.clone()))
|
||||
.await?
|
||||
.get_deploy_app_version
|
||||
.context("unknown package version")?;
|
||||
|
||||
let page: Vec<_> = deploy_app_version
|
||||
.logs
|
||||
.edges
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|edge| edge.node)
|
||||
.collect();
|
||||
|
||||
if page.is_empty() {
|
||||
if watch {
|
||||
/*
|
||||
TODO: the resolution of watch should be configurable
|
||||
TODO: should this be async?
|
||||
*/
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
continue;
|
||||
}
|
||||
|
||||
break Ok(None);
|
||||
} else {
|
||||
let last_message = page.last().expect("The page is non-empty");
|
||||
let timestamp = last_message.timestamp;
|
||||
// NOTE: adding 1 microsecond to the timestamp to avoid fetching
|
||||
// the last message again.
|
||||
let timestamp = OffsetDateTime::from_unix_timestamp_nanos(timestamp as i128)
|
||||
.with_context(|| {
|
||||
format!("Unable to interpret {timestamp} as a unix timestamp")
|
||||
})?;
|
||||
|
||||
// FIXME: We need a better way to tell the backend "give me the
|
||||
// next set of logs". Adding 1 nanosecond could theoretically
|
||||
// mean we miss messages if multiple log messages arrived at
|
||||
// the same nanosecond and the page ended midway.
|
||||
|
||||
let next_timestamp = timestamp + Duration::from_nanos(1_000);
|
||||
|
||||
break Ok(Some((page, next_timestamp)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fut.instrument(span.clone())
|
||||
})
|
||||
}
|
||||
|
||||
/// Get pages of logs associated with an application that lie within the
|
||||
/// specified date range.
|
||||
///
|
||||
/// In contrast to [`get_app_logs`], this function collects the stream into a
|
||||
/// final vector.
|
||||
#[tracing::instrument(skip_all, level = "debug")]
|
||||
#[allow(clippy::let_with_type_underscore)]
|
||||
pub async fn get_app_logs_paginated(
|
||||
client: &WasmerClient,
|
||||
name: String,
|
||||
owner: String,
|
||||
tag: Option<String>,
|
||||
start: OffsetDateTime,
|
||||
end: Option<OffsetDateTime>,
|
||||
watch: bool,
|
||||
) -> impl futures::Stream<Item = Result<Vec<Log>, anyhow::Error>> + '_ {
|
||||
let stream = get_app_logs(client, name, owner, tag, start, end, watch);
|
||||
|
||||
stream.map(|res| {
|
||||
let mut logs = Vec::new();
|
||||
let mut hasher = HashSet::new();
|
||||
let mut page = res?;
|
||||
|
||||
// Prevent duplicates.
|
||||
// TODO: don't clone the message, just hash it.
|
||||
page.retain(|log| hasher.insert((log.message.clone(), log.timestamp.round() as i128)));
|
||||
|
||||
logs.extend(page);
|
||||
|
||||
Ok(logs)
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert a [`OffsetDateTime`] to a unix timestamp that the WAPM backend
|
||||
/// understands.
|
||||
fn unix_timestamp(ts: OffsetDateTime) -> f64 {
|
||||
let nanos_per_second = 1_000_000_000;
|
||||
let timestamp = ts.unix_timestamp_nanos();
|
||||
let nanos = timestamp % nanos_per_second;
|
||||
let secs = timestamp / nanos_per_second;
|
||||
|
||||
(secs as f64) + (nanos as f64 / nanos_per_second as f64)
|
||||
}
|
||||
119
lib/backend-api/src/stream.rs
Normal file
119
lib/backend-api/src/stream.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use std::{collections::VecDeque, task::Poll};
|
||||
|
||||
use futures::{
|
||||
future::{BoxFuture, OptionFuture},
|
||||
Future,
|
||||
};
|
||||
|
||||
use super::WasmerClient;
|
||||
|
||||
type PaginationFuture<I, P> = BoxFuture<'static, Result<(Vec<I>, Option<P>), anyhow::Error>>;
|
||||
|
||||
pub trait PaginatedQuery {
|
||||
type Vars;
|
||||
type Paginator;
|
||||
type Item;
|
||||
|
||||
fn query(
|
||||
&self,
|
||||
client: WasmerClient,
|
||||
paginator: Option<Self::Paginator>,
|
||||
) -> PaginationFuture<Self::Item, Self::Paginator>;
|
||||
}
|
||||
|
||||
pin_project_lite::pin_project! {
|
||||
pub struct QueryStream<Q: PaginatedQuery> {
|
||||
query: Q,
|
||||
|
||||
client: WasmerClient,
|
||||
page: usize,
|
||||
paginator: Option<Q::Paginator>,
|
||||
finished: bool,
|
||||
items: VecDeque<Q::Item>,
|
||||
|
||||
#[pin]
|
||||
fut: OptionFuture<PaginationFuture<Q::Item, Q::Paginator>>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: PaginatedQuery> QueryStream<Q> {
|
||||
pub fn new(query: Q, client: WasmerClient) -> Self {
|
||||
Self {
|
||||
query,
|
||||
client,
|
||||
page: 0,
|
||||
finished: false,
|
||||
paginator: None,
|
||||
items: VecDeque::new(),
|
||||
fut: None.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: PaginatedQuery> futures::Stream for QueryStream<Q> {
|
||||
type Item = Result<Q::Item, anyhow::Error>;
|
||||
|
||||
fn poll_next(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
let mut this = self.project();
|
||||
|
||||
if let Some(item) = this.items.pop_front() {
|
||||
return Poll::Ready(Some(Ok(item)));
|
||||
}
|
||||
|
||||
match this.fut.as_mut().poll(cx) {
|
||||
Poll::Ready(None) => {}
|
||||
Poll::Ready(Some(Ok((items, paginator)))) => {
|
||||
*this.paginator = paginator;
|
||||
*this.page += 1;
|
||||
// *this.fut = None.into();
|
||||
this.items.extend(items);
|
||||
this.fut.set(None.into());
|
||||
|
||||
if let Some(item) = this.items.pop_front() {
|
||||
return Poll::Ready(Some(Ok(item)));
|
||||
}
|
||||
}
|
||||
Poll::Ready(Some(Err(err))) => {
|
||||
return Poll::Ready(Some(Err(err)));
|
||||
}
|
||||
Poll::Pending => {
|
||||
return Poll::Pending;
|
||||
}
|
||||
};
|
||||
|
||||
let pager = match this.paginator.take() {
|
||||
Some(p) => Some(p),
|
||||
None if *this.page == 0 => None,
|
||||
None => {
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
};
|
||||
|
||||
let f = this.query.query(this.client.clone(), pager);
|
||||
this.fut.set(Some(f).into());
|
||||
|
||||
match this.fut.as_mut().poll(cx) {
|
||||
Poll::Ready(None) => {
|
||||
unreachable!()
|
||||
}
|
||||
Poll::Ready(Some(Ok((items, paginator)))) => {
|
||||
*this.paginator = paginator;
|
||||
*this.page += 1;
|
||||
// *this.fut = None.into();
|
||||
this.items.extend(items);
|
||||
this.fut.set(None.into());
|
||||
|
||||
if let Some(item) = this.items.pop_front() {
|
||||
Poll::Ready(Some(Ok(item)))
|
||||
} else {
|
||||
Poll::Ready(None)
|
||||
}
|
||||
}
|
||||
Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err))),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
740
lib/backend-api/src/types.rs
Normal file
740
lib/backend-api/src/types.rs
Normal file
@@ -0,0 +1,740 @@
|
||||
pub use queries::*;
|
||||
|
||||
pub use cynic::Id;
|
||||
|
||||
#[cynic::schema_for_derives(file = r#"schema.graphql"#, module = "schema")]
|
||||
mod queries {
|
||||
use serde::Serialize;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use super::schema;
|
||||
|
||||
#[derive(cynic::Scalar, Debug, Clone)]
|
||||
pub struct DateTime(pub String);
|
||||
|
||||
impl TryFrom<OffsetDateTime> for DateTime {
|
||||
type Error = time::error::Format;
|
||||
|
||||
fn try_from(value: OffsetDateTime) -> Result<Self, Self::Error> {
|
||||
value
|
||||
.format(&time::format_description::well_known::Rfc3339)
|
||||
.map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<DateTime> for OffsetDateTime {
|
||||
type Error = time::error::Parse;
|
||||
|
||||
fn try_from(value: DateTime) -> Result<Self, Self::Error> {
|
||||
OffsetDateTime::parse(&value.0, &time::format_description::well_known::Rfc3339)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(cynic::Scalar, Debug, Clone)]
|
||||
pub struct JSONString(pub String);
|
||||
|
||||
#[derive(cynic::Enum, Clone, Copy, Debug)]
|
||||
pub enum GrapheneRole {
|
||||
Admin,
|
||||
Editor,
|
||||
Viewer,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetCurrentUserVars {
|
||||
pub namespace_role: Option<GrapheneRole>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetCurrentUserVars")]
|
||||
pub struct GetCurrentUser {
|
||||
pub viewer: Option<UserWithNamespaces>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
pub struct User {
|
||||
pub id: cynic::Id,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, Clone)]
|
||||
pub struct Package {
|
||||
pub id: cynic::Id,
|
||||
pub package_name: String,
|
||||
pub namespace: Option<String>,
|
||||
pub last_version: Option<PackageVersion>,
|
||||
pub private: bool,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, Clone)]
|
||||
pub struct PackageDistribution {
|
||||
pub pirita_sha256_hash: Option<String>,
|
||||
pub pirita_download_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, Clone)]
|
||||
pub struct PackageVersion {
|
||||
pub id: cynic::Id,
|
||||
pub version: String,
|
||||
pub created_at: DateTime,
|
||||
pub distribution: PackageDistribution,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, Clone)]
|
||||
#[cynic(graphql_type = "PackageVersion")]
|
||||
pub struct PackageVersionWithPackage {
|
||||
pub id: cynic::Id,
|
||||
pub version: String,
|
||||
pub created_at: DateTime,
|
||||
pub pirita_manifest: Option<JSONString>,
|
||||
pub distribution: PackageDistribution,
|
||||
|
||||
pub package: Package,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetPackageVars {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetPackageVars")]
|
||||
pub struct GetPackage {
|
||||
#[arguments(name: $name)]
|
||||
pub get_package: Option<Package>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetPackageVersionVars {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetPackageVersionVars")]
|
||||
pub struct GetPackageVersion {
|
||||
#[arguments(name: $name, version: $version)]
|
||||
pub get_package_version: Option<PackageVersionWithPackage>,
|
||||
}
|
||||
|
||||
#[derive(cynic::Enum, Clone, Copy, Debug)]
|
||||
pub enum PackageVersionSortBy {
|
||||
Newest,
|
||||
Oldest,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug, Clone, Default)]
|
||||
pub struct AllPackageVersionsVars {
|
||||
pub offset: Option<i32>,
|
||||
pub before: Option<String>,
|
||||
pub after: Option<String>,
|
||||
pub first: Option<i32>,
|
||||
pub last: Option<i32>,
|
||||
|
||||
pub created_after: Option<DateTime>,
|
||||
pub updated_after: Option<DateTime>,
|
||||
pub sort_by: Option<PackageVersionSortBy>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "AllPackageVersionsVars")]
|
||||
pub struct GetAllPackageVersions {
|
||||
#[arguments(
|
||||
first: $first,
|
||||
last: $last,
|
||||
after: $after,
|
||||
before: $before,
|
||||
offset: $offset,
|
||||
updatedAfter: $updated_after,
|
||||
createdAfter: $created_after,
|
||||
sortBy: $sort_by,
|
||||
)]
|
||||
pub all_package_versions: PackageVersionConnection,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
pub struct PackageVersionConnection {
|
||||
pub page_info: PageInfo,
|
||||
pub edges: Vec<Option<PackageVersionEdge>>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
pub struct PackageVersionEdge {
|
||||
pub node: Option<PackageVersionWithPackage>,
|
||||
pub cursor: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetPackageAndAppVars {
|
||||
pub package: String,
|
||||
pub app_owner: String,
|
||||
pub app_name: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetPackageAndAppVars")]
|
||||
pub struct GetPackageAndApp {
|
||||
#[arguments(name: $package)]
|
||||
pub get_package: Option<Package>,
|
||||
#[arguments(owner: $app_owner, name: $app_name)]
|
||||
pub get_deploy_app: Option<DeployApp>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query")]
|
||||
pub struct GetCurrentUserWithApps {
|
||||
pub viewer: Option<UserWithApps>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "User")]
|
||||
pub struct UserWithApps {
|
||||
pub id: cynic::Id,
|
||||
pub username: String,
|
||||
pub apps: DeployAppConnection,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Serialize, Debug, Clone)]
|
||||
pub struct Owner {
|
||||
pub global_name: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, Clone)]
|
||||
#[cynic(graphql_type = "User", variables = "GetCurrentUserVars")]
|
||||
pub struct UserWithNamespaces {
|
||||
pub id: cynic::Id,
|
||||
pub username: String,
|
||||
#[arguments(role: $namespace_role)]
|
||||
pub namespaces: NamespaceConnection,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetUserAppsVars {
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetUserAppsVars")]
|
||||
pub struct GetUserApps {
|
||||
#[arguments(username: $username)]
|
||||
pub get_user: Option<User>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetDeployAppVars {
|
||||
pub name: String,
|
||||
pub owner: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetDeployAppVars")]
|
||||
pub struct GetDeployApp {
|
||||
#[arguments(owner: $owner, name: $name)]
|
||||
pub get_deploy_app: Option<DeployApp>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug, Clone)]
|
||||
pub struct PaginationVars {
|
||||
pub offset: Option<i32>,
|
||||
pub before: Option<String>,
|
||||
pub after: Option<String>,
|
||||
pub first: Option<i32>,
|
||||
pub last: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(cynic::Enum, Clone, Copy, Debug)]
|
||||
pub enum DeployAppsSortBy {
|
||||
Newest,
|
||||
Oldest,
|
||||
MostActive,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug, Clone, Default)]
|
||||
pub struct GetDeployAppsVars {
|
||||
pub offset: Option<i32>,
|
||||
pub before: Option<String>,
|
||||
pub after: Option<String>,
|
||||
pub first: Option<i32>,
|
||||
pub last: Option<i32>,
|
||||
|
||||
pub updated_after: Option<DateTime>,
|
||||
pub sort_by: Option<DeployAppsSortBy>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetDeployAppsVars")]
|
||||
pub struct GetDeployApps {
|
||||
#[arguments(
|
||||
first: $first,
|
||||
last: $last,
|
||||
after: $after,
|
||||
before: $before,
|
||||
offset: $offset,
|
||||
updatedAfter: $updated_after,
|
||||
sortBy: $sort_by,
|
||||
)]
|
||||
pub get_deploy_apps: Option<DeployAppConnection>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetDeployAppByAliasVars {
|
||||
pub alias: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetDeployAppByAliasVars")]
|
||||
pub struct GetDeployAppByAlias {
|
||||
#[arguments(alias: $alias)]
|
||||
pub get_app_by_global_alias: Option<DeployApp>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetDeployAppAndVersionVars {
|
||||
pub name: String,
|
||||
pub owner: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetDeployAppAndVersionVars")]
|
||||
pub struct GetDeployAppAndVersion {
|
||||
#[arguments(owner: $owner, name: $name)]
|
||||
pub get_deploy_app: Option<DeployApp>,
|
||||
#[arguments(owner: $owner, name: $name, version: $version)]
|
||||
pub get_deploy_app_version: Option<DeployAppVersion>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetDeployAppVersionVars {
|
||||
pub name: String,
|
||||
pub owner: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetDeployAppVersionVars")]
|
||||
pub struct GetDeployAppVersion {
|
||||
#[arguments(owner: $owner, name: $name, version: $version)]
|
||||
pub get_deploy_app_version: Option<DeployAppVersion>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct CreateNamespaceVars {
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Mutation", variables = "CreateNamespaceVars")]
|
||||
pub struct CreateNamespace {
|
||||
#[arguments(input: {name: $name, description: $description})]
|
||||
pub create_namespace: Option<CreateNamespacePayload>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
pub struct CreateNamespacePayload {
|
||||
pub namespace: Namespace,
|
||||
}
|
||||
|
||||
#[derive(cynic::InputObject, Debug)]
|
||||
pub struct CreateNamespaceInput {
|
||||
pub name: String,
|
||||
pub display_name: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub avatar: Option<String>,
|
||||
pub client_mutation_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, Clone)]
|
||||
pub struct NamespaceEdge {
|
||||
pub node: Option<Namespace>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, Clone)]
|
||||
pub struct NamespaceConnection {
|
||||
pub edges: Vec<Option<NamespaceEdge>>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Serialize, Debug, Clone)]
|
||||
pub struct Namespace {
|
||||
pub id: cynic::Id,
|
||||
pub name: String,
|
||||
pub global_name: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Serialize, Debug, Clone)]
|
||||
pub struct DeployApp {
|
||||
pub id: cynic::Id,
|
||||
pub name: String,
|
||||
pub created_at: DateTime,
|
||||
pub description: Option<String>,
|
||||
pub active_version: DeployAppVersion,
|
||||
pub admin_url: String,
|
||||
pub owner: Owner,
|
||||
pub url: String,
|
||||
pub deleted: bool,
|
||||
pub aliases: AppAliasConnection,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Serialize, Debug, Clone)]
|
||||
pub struct AppAliasConnection {
|
||||
pub page_info: PageInfo,
|
||||
pub edges: Vec<Option<AppAliasEdge>>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Serialize, Debug, Clone)]
|
||||
pub struct AppAliasEdge {
|
||||
pub node: Option<AppAlias>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Serialize, Debug, Clone)]
|
||||
pub struct AppAlias {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug, Clone)]
|
||||
pub struct DeleteAppVars {
|
||||
pub app_id: cynic::Id,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Serialize, Debug, Clone)]
|
||||
pub struct DeleteAppPayload {
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Mutation", variables = "DeleteAppVars")]
|
||||
pub struct DeleteApp {
|
||||
#[arguments(input: { id: $app_id })]
|
||||
pub delete_app: Option<DeleteAppPayload>,
|
||||
}
|
||||
|
||||
#[derive(cynic::Enum, Clone, Copy, Debug)]
|
||||
pub enum DeployAppVersionsSortBy {
|
||||
Newest,
|
||||
Oldest,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug, Clone)]
|
||||
pub struct GetDeployAppVersionsVars {
|
||||
pub owner: String,
|
||||
pub name: String,
|
||||
|
||||
pub offset: Option<i32>,
|
||||
pub before: Option<String>,
|
||||
pub after: Option<String>,
|
||||
pub first: Option<i32>,
|
||||
pub last: Option<i32>,
|
||||
pub sort_by: Option<DeployAppVersionsSortBy>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, Clone)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetDeployAppVersionsVars")]
|
||||
pub struct GetDeployAppVersions {
|
||||
#[arguments(owner: $owner, name: $name)]
|
||||
pub get_deploy_app: Option<DeployAppVersions>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, Clone)]
|
||||
#[cynic(graphql_type = "DeployApp", variables = "GetDeployAppVersionsVars")]
|
||||
pub struct DeployAppVersions {
|
||||
#[arguments(
|
||||
first: $first,
|
||||
last: $last,
|
||||
before: $before,
|
||||
after: $after,
|
||||
offset: $offset,
|
||||
sortBy: $sort_by
|
||||
)]
|
||||
pub versions: DeployAppVersionConnection,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Serialize, Debug, Clone)]
|
||||
#[cynic(graphql_type = "DeployApp")]
|
||||
pub struct SparseDeployApp {
|
||||
pub id: cynic::Id,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Serialize, Debug, Clone)]
|
||||
pub struct DeployAppVersion {
|
||||
pub id: cynic::Id,
|
||||
pub created_at: DateTime,
|
||||
pub version: String,
|
||||
pub description: Option<String>,
|
||||
pub yaml_config: String,
|
||||
pub user_yaml_config: String,
|
||||
pub config: String,
|
||||
pub json_config: String,
|
||||
pub url: String,
|
||||
|
||||
pub app: Option<SparseDeployApp>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, Clone)]
|
||||
pub struct DeployAppVersionConnection {
|
||||
pub page_info: PageInfo,
|
||||
pub edges: Vec<Option<DeployAppVersionEdge>>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, Clone)]
|
||||
pub struct DeployAppVersionEdge {
|
||||
pub node: Option<DeployAppVersion>,
|
||||
pub cursor: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
pub struct DeployAppConnection {
|
||||
pub page_info: PageInfo,
|
||||
pub edges: Vec<Option<DeployAppEdge>>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
pub struct DeployAppEdge {
|
||||
pub node: Option<DeployApp>,
|
||||
pub cursor: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Serialize, Debug, Clone)]
|
||||
pub struct PageInfo {
|
||||
pub has_next_page: bool,
|
||||
pub end_cursor: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetNamespaceVars {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Serialize, Debug, Clone)]
|
||||
pub struct MarkAppVersionAsActivePayload {
|
||||
pub app: DeployApp,
|
||||
}
|
||||
|
||||
#[derive(cynic::InputObject, Debug)]
|
||||
pub struct MarkAppVersionAsActiveInput {
|
||||
pub app_version: cynic::Id,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct MarkAppVersionAsActiveVars {
|
||||
pub input: MarkAppVersionAsActiveInput,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Mutation", variables = "MarkAppVersionAsActiveVars")]
|
||||
pub struct MarkAppVersionAsActive {
|
||||
#[arguments(input: $input)]
|
||||
pub mark_app_version_as_active: Option<MarkAppVersionAsActivePayload>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetNamespaceVars")]
|
||||
pub struct GetNamespace {
|
||||
#[arguments(name: $name)]
|
||||
pub get_namespace: Option<Namespace>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetNamespaceAppsVars {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetNamespaceAppsVars")]
|
||||
pub struct GetNamespaceApps {
|
||||
#[arguments(name: $name)]
|
||||
pub get_namespace: Option<NamespaceWithApps>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Namespace")]
|
||||
pub struct NamespaceWithApps {
|
||||
pub id: cynic::Id,
|
||||
pub name: String,
|
||||
pub apps: DeployAppConnection,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct PublishDeployAppVars {
|
||||
pub config: String,
|
||||
pub name: cynic::Id,
|
||||
pub owner: Option<cynic::Id>,
|
||||
pub make_default: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Mutation", variables = "PublishDeployAppVars")]
|
||||
pub struct PublishDeployApp {
|
||||
#[arguments(input: { config: { yamlConfig: $config }, name: $name, owner: $owner, makeDefault: $make_default })]
|
||||
pub publish_deploy_app: Option<PublishDeployAppPayload>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
pub struct PublishDeployAppPayload {
|
||||
pub deploy_app_version: DeployAppVersion,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GenerateDeployTokenVars {
|
||||
pub app_version_id: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Mutation", variables = "GenerateDeployTokenVars")]
|
||||
pub struct GenerateDeployToken {
|
||||
#[arguments(input: { deployConfigVersionId: $app_version_id })]
|
||||
pub generate_deploy_token: Option<GenerateDeployTokenPayload>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
pub struct GenerateDeployTokenPayload {
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug, Clone)]
|
||||
pub struct GetDeployAppLogsVars {
|
||||
pub name: String,
|
||||
pub owner: String,
|
||||
/// The tag associated with a particular app version. Uses the active
|
||||
/// version if not provided.
|
||||
pub version: Option<String>,
|
||||
/// The lower bound for log messages, in nanoseconds since the Unix
|
||||
/// epoch.
|
||||
pub starting_from: f64,
|
||||
/// The upper bound for log messages, in nanoseconds since the Unix
|
||||
/// epoch.
|
||||
pub until: Option<f64>,
|
||||
pub first: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetDeployAppLogsVars")]
|
||||
pub struct GetDeployAppLogs {
|
||||
#[arguments(name: $name, owner: $owner, version: $version)]
|
||||
pub get_deploy_app_version: Option<DeployAppVersionLogs>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "DeployAppVersion", variables = "GetDeployAppLogsVars")]
|
||||
pub struct DeployAppVersionLogs {
|
||||
#[arguments(startingFrom: $starting_from, until: $until, first: $first)]
|
||||
pub logs: LogConnection,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
pub struct LogConnection {
|
||||
pub edges: Vec<Option<LogEdge>>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
pub struct LogEdge {
|
||||
pub node: Option<Log>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug, serde::Serialize, PartialEq)]
|
||||
pub struct Log {
|
||||
pub message: String,
|
||||
/// When the message was recorded, in nanoseconds since the Unix epoch.
|
||||
pub timestamp: f64,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GenerateDeployConfigTokenVars {
|
||||
pub input: String,
|
||||
}
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Mutation", variables = "GenerateDeployConfigTokenVars")]
|
||||
pub struct GenerateDeployConfigToken {
|
||||
#[arguments(input: { config: $input })]
|
||||
pub generate_deploy_config_token: Option<GenerateDeployConfigTokenPayload>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
pub struct GenerateDeployConfigTokenPayload {
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetNodeVars {
|
||||
pub id: cynic::Id,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetNodeVars")]
|
||||
pub struct GetNode {
|
||||
#[arguments(id: $id)]
|
||||
pub node: Option<Node>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetDeployAppByIdVars {
|
||||
pub app_id: cynic::Id,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetDeployAppByIdVars")]
|
||||
pub struct GetDeployAppById {
|
||||
#[arguments(id: $app_id)]
|
||||
#[cynic(rename = "node")]
|
||||
pub app: Option<Node>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetDeployAppAndVersionByIdVars {
|
||||
pub app_id: cynic::Id,
|
||||
pub version_id: cynic::Id,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetDeployAppAndVersionByIdVars")]
|
||||
pub struct GetDeployAppAndVersionById {
|
||||
#[arguments(id: $app_id)]
|
||||
#[cynic(rename = "node")]
|
||||
pub app: Option<Node>,
|
||||
#[arguments(id: $version_id)]
|
||||
#[cynic(rename = "node")]
|
||||
pub version: Option<Node>,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryVariables, Debug)]
|
||||
pub struct GetDeployAppVersionByIdVars {
|
||||
pub version_id: cynic::Id,
|
||||
}
|
||||
|
||||
#[derive(cynic::QueryFragment, Debug)]
|
||||
#[cynic(graphql_type = "Query", variables = "GetDeployAppVersionByIdVars")]
|
||||
pub struct GetDeployAppVersionById {
|
||||
#[arguments(id: $version_id)]
|
||||
#[cynic(rename = "node")]
|
||||
pub version: Option<Node>,
|
||||
}
|
||||
|
||||
#[derive(cynic::InlineFragments, Debug)]
|
||||
pub enum Node {
|
||||
DeployApp(Box<DeployApp>),
|
||||
DeployAppVersion(Box<DeployAppVersion>),
|
||||
#[cynic(fallback)]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn into_deploy_app(self) -> Option<DeployApp> {
|
||||
match self {
|
||||
Node::DeployApp(app) => Some(*app),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_deploy_app_version(self) -> Option<DeployAppVersion> {
|
||||
match self {
|
||||
Node::DeployAppVersion(version) => Some(*version),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case, non_camel_case_types)]
|
||||
mod schema {
|
||||
cynic::use_schema!(r#"schema.graphql"#);
|
||||
}
|
||||
Reference in New Issue
Block a user