|
| 1 | +//! # Apollo Studio Extension for Registering Schema |
| 2 | +use async_graphql::{ObjectType, Schema, SubscriptionType}; |
| 3 | +use reqwest::Client; |
| 4 | +use sha2::{Digest, Sha256}; |
| 5 | +use uuid::Uuid; |
| 6 | + |
| 7 | +const SCHEMA_URL: &str = "https://schema-reporting.api.apollographql.com/api/graphql"; |
| 8 | +const TARGET_LOG: &str = "apollo-studio-extension-register"; |
| 9 | +const VERSION: &str = env!("CARGO_PKG_VERSION"); |
| 10 | +const RUNTIME_VERSION: &str = "Rust - No runtime version provided yet"; |
| 11 | + |
| 12 | +/** |
| 13 | + * Compute the SHA256 of a Schema |
| 14 | + * Usefull for Apollo Studio |
| 15 | + */ |
| 16 | +pub fn sha<Q: ObjectType + 'static, M: ObjectType + 'static, S: SubscriptionType + 'static>( |
| 17 | + schema: Schema<Q, M, S>, |
| 18 | +) -> String { |
| 19 | + let mut hasher = Sha256::new(); |
| 20 | + let schema_sdl = schema.sdl(); |
| 21 | + let schema_bytes = schema_sdl.as_bytes(); |
| 22 | + hasher.update(schema_bytes); |
| 23 | + let sha_from_schema = Sha256::digest(schema_bytes); |
| 24 | + format!("{:x}", sha_from_schema) |
| 25 | +} |
| 26 | + |
| 27 | +/// Register your schema to Apollo Studio |
| 28 | +/// |
| 29 | +/// * `authorization_token` - Token to send schema to apollo Studio. |
| 30 | +/// * `schema` - async_graphql generated schema. |
| 31 | +/// * `server_id` - An ID that's unique for each instance of your edge server. Unlike bootId, this value should persist across an instance's restarts. In a Kubernetes cluster, this might be the pod name, whereas the container can restart. |
| 32 | +/// * `variant` - The name of the graph variant to register the schema to. The default value is current. |
| 33 | +/// * `user_version` - An arbitrary string you can set to distinguish data sent by different versions of your edge server. For example, this can be the SHA of the Git commit for your deployed server code. We plan to make this value visible in Apollo Studio. |
| 34 | +/// * `platform` - The infrastructure environment that your edge server is running in (localhost, kubernetes/deployment, aws lambda, google cloud run, google cloud function, AWS ECS, etc.) |
| 35 | +#[instrument(err, skip(schema))] |
| 36 | +pub async fn register< |
| 37 | + Q: ObjectType + 'static, |
| 38 | + M: ObjectType + 'static, |
| 39 | + S: SubscriptionType + 'static, |
| 40 | +>( |
| 41 | + authorization_token: &str, |
| 42 | + schema: Schema<Q, M, S>, |
| 43 | + server_id: &str, |
| 44 | + variant: &str, |
| 45 | + user_version: &str, |
| 46 | + platform: &str, |
| 47 | +) -> anyhow::Result<()> { |
| 48 | + info!( |
| 49 | + target: TARGET_LOG, |
| 50 | + message = "Apollo Studio - Register Schema" |
| 51 | + ); |
| 52 | + let client = Client::new(); |
| 53 | + let schema_sdl = schema.sdl(); |
| 54 | + let sha_from_schema = sha(schema); |
| 55 | + let boot_id = Uuid::new_v4(); |
| 56 | + |
| 57 | + let mutation = format!( |
| 58 | + r#" |
| 59 | + mutation($schema: String!) {{ |
| 60 | + me {{ |
| 61 | + ... on ServiceMutation {{ |
| 62 | + reportServerInfo( |
| 63 | + info: {{ |
| 64 | + bootId: "{:?}" |
| 65 | + serverId: "{}" |
| 66 | + executableSchemaId: "{}" |
| 67 | + graphVariant: "{}" |
| 68 | + platform: "{}" |
| 69 | + libraryVersion: "{}" |
| 70 | + runtimeVersion: "{}" |
| 71 | + userVersion: "{}" |
| 72 | + }} |
| 73 | + executableSchema: $schema |
| 74 | + ) {{ |
| 75 | + __typename |
| 76 | + ... on ReportServerInfoError {{ |
| 77 | + code |
| 78 | + message |
| 79 | + }} |
| 80 | + inSeconds |
| 81 | + withExecutableSchema |
| 82 | + }} |
| 83 | + }} |
| 84 | + }} |
| 85 | + }} |
| 86 | + "#, |
| 87 | + boot_id, |
| 88 | + server_id, |
| 89 | + sha_from_schema, |
| 90 | + variant, |
| 91 | + platform, |
| 92 | + format!("async-studio-extension {}", VERSION), |
| 93 | + RUNTIME_VERSION, |
| 94 | + user_version |
| 95 | + ); |
| 96 | + |
| 97 | + let result = client |
| 98 | + .post(SCHEMA_URL) |
| 99 | + .json(&serde_json::json!({ |
| 100 | + "query": mutation, |
| 101 | + "variables": { |
| 102 | + "schema": schema_sdl, |
| 103 | + }, |
| 104 | + })) |
| 105 | + .header("content-type", "application/json") |
| 106 | + .header("X-Api-Key", authorization_token) |
| 107 | + .send() |
| 108 | + .await; |
| 109 | + |
| 110 | + match result { |
| 111 | + Ok(data) => { |
| 112 | + info!( |
| 113 | + target: TARGET_LOG, |
| 114 | + message = "Schema correctly registered", |
| 115 | + response = &tracing::field::debug(&data) |
| 116 | + ); |
| 117 | + let text = data.text().await; |
| 118 | + debug!(target: TARGET_LOG, data = ?text); |
| 119 | + Ok(()) |
| 120 | + } |
| 121 | + Err(err) => { |
| 122 | + let status_code = err.status(); |
| 123 | + error!(target: TARGET_LOG, status = ?status_code, error = ?err); |
| 124 | + Err(anyhow::anyhow!(err)) |
| 125 | + } |
| 126 | + } |
| 127 | +} |
0 commit comments