From 06fd730a1c23af88eaffa1dde8acdea202247f91 Mon Sep 17 00:00:00 2001 From: kjuulh Date: Sat, 28 Jan 2023 13:33:30 +0100 Subject: [PATCH] can get schema --- src/cli_session.rs | 68 ++++++++------- src/engine.rs | 8 +- src/graphql/introspection_query.graphql | 99 ++++++++++++++++++++++ src/graphql/introspection_schema.graphql | 101 +++++++++++++++++++++++ src/schema.rs | 27 ++++++ src/session.rs | 79 ++++++++++++++---- 6 files changed, 333 insertions(+), 49 deletions(-) create mode 100644 src/graphql/introspection_query.graphql create mode 100644 src/graphql/introspection_schema.graphql diff --git a/src/cli_session.rs b/src/cli_session.rs index 50c57f4..94c9506 100644 --- a/src/cli_session.rs +++ b/src/cli_session.rs @@ -1,9 +1,11 @@ +use core::time; use std::{ fs::canonicalize, io::{BufRead, BufReader}, path::PathBuf, - process::Stdio, - sync::Arc, + process::{Child, Stdio}, + sync::{mpsc::sync_channel, Arc}, + thread::sleep, }; use crate::{config::Config, connect_params::ConnectParams}; @@ -20,7 +22,11 @@ impl CliSession { } } - pub fn connect(&self, config: &Config, cli_path: &PathBuf) -> eyre::Result { + pub fn connect( + &self, + config: &Config, + cli_path: &PathBuf, + ) -> eyre::Result<(ConnectParams, Child)> { self.inner.connect(config, cli_path) } } @@ -29,9 +35,13 @@ impl CliSession { struct InnerCliSession {} impl InnerCliSession { - pub fn connect(&self, config: &Config, cli_path: &PathBuf) -> eyre::Result { - let mut proc = self.start(config, cli_path)?; - let params = self.get_conn(&mut proc)?; + pub fn connect( + &self, + config: &Config, + cli_path: &PathBuf, + ) -> eyre::Result<(ConnectParams, Child)> { + let proc = self.start(config, cli_path)?; + let params = self.get_conn(proc)?; Ok(params) } @@ -62,7 +72,10 @@ impl InnerCliSession { return Ok(proc); } - fn get_conn(&self, proc: &mut std::process::Child) -> eyre::Result { + fn get_conn( + &self, + mut proc: std::process::Child, + ) -> eyre::Result<(ConnectParams, std::process::Child)> { let stdout = proc .stdout .take() @@ -73,31 +86,28 @@ impl InnerCliSession { .take() .ok_or(eyre::anyhow!("could not acquire stderr from child process"))?; - let mut conn: Option = None; + let (sender, receiver) = sync_channel(1); - std::thread::scope(|s| { - s.spawn(|| { - let stdout_bufr = BufReader::new(stdout); - let mut res_conn: Option = None; - for line in stdout_bufr.lines() { - let out = line.unwrap(); - let conn: ConnectParams = serde_json::from_str(&out).unwrap(); - res_conn = Some(conn); - break; + std::thread::spawn(move || { + let stdout_bufr = BufReader::new(stdout); + for line in stdout_bufr.lines() { + let out = line.unwrap(); + if let Ok(conn) = serde_json::from_str::(&out) { + sender.send(conn).unwrap(); } - - conn = res_conn; - }); - - //s.spawn(|| { - // let stderr_bufr = BufReader::new(stderr); - // for line in stderr_bufr.lines() { - // let out = line.unwrap(); - // panic!("could not start dagger session: {}", out) - // } - //}); + } }); - Ok(conn.ok_or(eyre::anyhow!("could not connect to dagger"))?) + std::thread::spawn(|| { + let stderr_bufr = BufReader::new(stderr); + for line in stderr_bufr.lines() { + let out = line.unwrap(); + panic!("could not start dagger session: {}", out) + } + }); + + let conn = receiver.recv()?; + + Ok((conn, proc)) } } diff --git a/src/engine.rs b/src/engine.rs index 2401b98..d1eedbc 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,3 +1,5 @@ +use std::process::Child; + use crate::{ cli_session::CliSession, config::Config, connect_params::ConnectParams, downloader::Downloader, }; @@ -9,7 +11,7 @@ impl Engine { Self {} } - fn from_cli(&self, cfg: &Config) -> eyre::Result { + fn from_cli(&self, cfg: &Config) -> eyre::Result<(ConnectParams, Child)> { let cli = Downloader::new("0.3.10".into())?.get_cli()?; let cli_session = CliSession::new(); @@ -17,7 +19,7 @@ impl Engine { Ok(cli_session.connect(cfg, &cli)?) } - pub fn start(&self, cfg: &Config) -> eyre::Result { + pub fn start(&self, cfg: &Config) -> eyre::Result<(ConnectParams, Child)> { // TODO: Add from existing session as well self.from_cli(cfg) } @@ -36,7 +38,7 @@ mod tests { let params = engine.start(&Config::new(None, None, None, None)).unwrap(); assert_ne!( - params, + params.0, ConnectParams { port: 123, session_token: "123".into() diff --git a/src/graphql/introspection_query.graphql b/src/graphql/introspection_query.graphql new file mode 100644 index 0000000..d17da75 --- /dev/null +++ b/src/graphql/introspection_query.graphql @@ -0,0 +1,99 @@ +query IntrospectionQuery { + __schema { + queryType { + name + } + mutationType { + name + } + subscriptionType { + name + } + types { + ...FullType + } + directives { + name + description + locations + args { + ...InputValue + } + } + } +} + +fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + description + args { + ...InputValue + } + type { + ...TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ...InputValue + } + interfaces { + ...TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ...TypeRef + } +} + +fragment InputValue on __InputValue { + name + description + type { + ...TypeRef + } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } +} diff --git a/src/graphql/introspection_schema.graphql b/src/graphql/introspection_schema.graphql new file mode 100644 index 0000000..29f5587 --- /dev/null +++ b/src/graphql/introspection_schema.graphql @@ -0,0 +1,101 @@ +schema { + query: Query +} + +type Query { + __schema: __Schema +} + +type __Schema { + types: [__Type!]! + queryType: __Type! + mutationType: __Type + subscriptionType: __Type + directives: [__Directive!]! +} + +type __Type { + kind: __TypeKind! + name: String + description: String + + # OBJECT and INTERFACE only + fields(includeDeprecated: Boolean = false): [__Field!] + + # OBJECT only + interfaces: [__Type!] + + # INTERFACE and UNION only + possibleTypes: [__Type!] + + # ENUM only + enumValues(includeDeprecated: Boolean = false): [__EnumValue!] + + # INPUT_OBJECT only + inputFields: [__InputValue!] + + # NON_NULL and LIST only + ofType: __Type +} + +type __Field { + name: String! + description: String + args: [__InputValue!]! + type: __Type! + isDeprecated: Boolean! + deprecationReason: String +} + +type __InputValue { + name: String! + description: String + type: __Type! + defaultValue: String +} + +type __EnumValue { + name: String! + description: String + isDeprecated: Boolean! + deprecationReason: String +} + +enum __TypeKind { + SCALAR + OBJECT + INTERFACE + UNION + ENUM + INPUT_OBJECT + LIST + NON_NULL +} + +type __Directive { + name: String! + description: String + locations: [__DirectiveLocation!]! + args: [__InputValue!]! +} + +enum __DirectiveLocation { + QUERY + MUTATION + SUBSCRIPTION + FIELD + FRAGMENT_DEFINITION + FRAGMENT_SPREAD + INLINE_FRAGMENT + SCHEMA + SCALAR + OBJECT + FIELD_DEFINITION + ARGUMENT_DEFINITION + INTERFACE + UNION + ENUM + ENUM_VALUE + INPUT_OBJECT + INPUT_FIELD_DEFINITION +} diff --git a/src/schema.rs b/src/schema.rs index e69de29..653d31a 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -0,0 +1,27 @@ +use core::time; +use std::thread::sleep; + +use graphql_introspection_query::introspection_response::IntrospectionResponse; + +use crate::{config::Config, engine::Engine, session::Session}; + +pub fn get_schema() -> eyre::Result { + //TODO: Implement cotext for proc + let cfg = Config::new(None, None, None, None); + let (conn, proc) = Engine::new().start(&cfg)?; + let session = Session::new(); + let req_builder = session.start(cfg, &conn)?; + let schema = session.schema(req_builder)?; + + Ok(schema) +} + +#[cfg(test)] +mod tests { + use super::get_schema; + + #[test] + fn can_get_schema() { + let _ = get_schema().unwrap(); + } +} diff --git a/src/session.rs b/src/session.rs index 4676499..60d61c7 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,34 +1,79 @@ -use graphql_client::reqwest::post_graphql_blocking; +use graphql_client::GraphQLQuery; use graphql_introspection_query::introspection_response::IntrospectionResponse; -use reqwest::blocking::Client; +use reqwest::{ + blocking::{Client, RequestBuilder}, + header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE}, +}; use crate::{config::Config, connect_params::ConnectParams}; -pub struct Session {} +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql/introspection_schema.graphql", + query_path = "src/graphql/introspection_query.graphql", + responsive_path = "Serialize", + variable_derive = "Deserialize" +)] +struct IntrospectionQuery; + +pub struct Session; impl Session { pub fn new() -> Self { Self {} } - pub fn start(&self, cfg: Config, conn: &ConnectParams) -> eyre::Result { + pub fn start(&self, cfg: Config, conn: &ConnectParams) -> eyre::Result { let client = Client::builder() .user_agent("graphql-rust/0.10.0") - .default_headers( - std::iter::once(( - reqwest::header::AUTHORIZATION, - reqwest::header::HeaderValue::from_str(&format!( - "Bearer {}", - conn.session_token - )) - .unwrap(), - )) - .collect(), - ) + .connection_verbose(true) + //.danger_accept_invalid_certs(true) .build()?; - let schema = post_graphql_blocking::(&client, conn.url(), vec![]); + let req_builder = client + .post(conn.url()) + .headers(construct_headers()) + .basic_auth::(conn.session_token.to_string(), None); - Ok(client) + Ok(req_builder) + } + + pub fn schema(&self, req_builder: RequestBuilder) -> eyre::Result { + let request_body: graphql_client::QueryBody<()> = graphql_client::QueryBody { + variables: (), + query: introspection_query::QUERY, + operation_name: introspection_query::OPERATION_NAME, + }; + + let res = req_builder.json(&request_body).send()?; + + if res.status().is_success() { + // do nothing + } else if res.status().is_server_error() { + return Err(eyre::anyhow!("server error!")); + } else { + let status = res.status(); + let error_message = match res.text() { + Ok(msg) => match serde_json::from_str::(&msg) { + Ok(json) => { + format!("HTTP {}\n{}", status, serde_json::to_string_pretty(&json)?) + } + Err(_) => format!("HTTP {}: {}", status, msg), + }, + Err(_) => format!("HTTP {}", status), + }; + return Err(eyre::anyhow!(error_message)); + } + + let json: IntrospectionResponse = res.json()?; + + Ok(json) } } + +fn construct_headers() -> HeaderMap { + let mut headers = HeaderMap::new(); + headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + headers.insert(ACCEPT, HeaderValue::from_static("application/json")); + headers +}