1use crate::{
2 allow_cors_with_origin, check, generate_action_templates, HttpReply, HttpRequest,
3 ProcessActionStruct, ToServiceSchema, WithActionStruct,
4};
5use async_graphql::{
6 http::{receive_body, GraphiQLSource},
7 EmptyMutation, EmptySubscription,
8};
9use futures::executor::block_on;
10
11const SIMPLE_UI: &[u8] = br#"<html><div id="root" class="ui container"></div><script src="/common/SimpleUI.mjs" type="module"></script></html>"#;
12
13pub fn serve_simple_ui<Wrapper: WithActionStruct + ToServiceSchema>(
25 request: &HttpRequest,
26) -> Option<HttpReply> {
27 None.or_else(|| serve_simple_index(request))
28 .or_else(|| serve_action_templates::<Wrapper>(request))
29 .or_else(|| serve_pack_action::<Wrapper>(request))
30}
31
32pub fn serve_simple_index(request: &HttpRequest) -> Option<HttpReply> {
43 if request.method == "GET" && (request.target == "/" || request.target == "/index.html") {
44 Some(HttpReply {
45 status: 200,
46 contentType: "text/html".into(),
47 body: SIMPLE_UI.to_vec().into(),
48 headers: allow_cors_with_origin("*"),
49 })
50 } else {
51 None
52 }
53}
54
55pub fn serve_action_templates<Wrapper: ToServiceSchema>(
66 request: &HttpRequest,
67) -> Option<HttpReply> {
68 if request.method == "GET" && request.target == "/action_templates" {
69 Some(HttpReply {
70 status: 200,
71 contentType: "text/html".into(),
72 body: generate_action_templates::<Wrapper>().into(),
73 headers: allow_cors_with_origin("*"),
74 })
75 } else {
76 None
77 }
78}
79
80pub fn serve_pack_action<Wrapper: WithActionStruct>(request: &HttpRequest) -> Option<HttpReply> {
92 struct PackAction<'a>(&'a [u8]);
93
94 impl<'a> ProcessActionStruct for PackAction<'a> {
95 type Output = HttpReply;
96
97 fn process<
98 Return: serde::Serialize + serde::de::DeserializeOwned,
99 ArgStruct: fracpack::Pack + serde::Serialize + serde::de::DeserializeOwned,
100 >(
101 self,
102 ) -> Self::Output {
103 let arg_struct_result = serde_json::from_slice::<ArgStruct>(self.0);
104 if let Err(err) = &arg_struct_result {
105 check(false, &format!("err parsing action args json {}", err));
106 }
107 HttpReply {
108 status: 200,
109 contentType: "application/octet-stream".into(),
110 body: arg_struct_result.unwrap().packed().into(),
111 headers: allow_cors_with_origin("*"),
112 }
113 }
114 }
115
116 if request.method == "POST" && request.target.starts_with("/pack_action/") {
117 Wrapper::with_action_struct(&request.target[13..], PackAction(&request.body))
118 } else {
119 None
120 }
121}
122
123pub fn serve_graphql<Query: async_graphql::ObjectType + 'static>(
133 request: &HttpRequest,
134 query: Query,
135) -> Option<HttpReply> {
136 let (base, args) = if let Some((b, q)) = request.target.split_once('?') {
137 (b, q)
138 } else {
139 (request.target.as_ref(), "")
140 };
141 if base != "/graphql" || request.method != "GET" && request.method != "POST" {
142 return None;
143 }
144 block_on(async move {
145 let schema = async_graphql::Schema::new(query, EmptyMutation, EmptySubscription);
146 if let Some(request) = args.strip_prefix("?query=") {
147 let res = schema.execute(request).await;
148 Some(HttpReply {
149 status: 200,
150 contentType: "application/json".into(),
151 body: serde_json::to_vec(&res).unwrap().into(),
152 headers: allow_cors_with_origin("*"),
153 })
154 } else if request.method == "GET" {
155 Some(HttpReply {
156 status: 200,
157 contentType: "text".into(), body: schema.sdl().into_bytes().into(),
159 headers: allow_cors_with_origin("*"),
160 })
161 } else if request.contentType == "application/graphql" {
162 let res = schema
163 .execute(std::str::from_utf8(&request.body.0).unwrap())
164 .await;
165 Some(HttpReply {
166 status: 200,
167 contentType: "application/json".into(),
168 body: serde_json::to_vec(&res).unwrap().into(),
169 headers: allow_cors_with_origin("*"),
170 })
171 } else {
172 let request_result = receive_body(
173 Some(&request.contentType),
174 request.body.as_ref(),
175 Default::default(),
176 )
177 .await;
178
179 if let Err(err) = &request_result {
180 check(false, &format!("err parsing graphql query {}", err));
181 }
182
183 let res = schema.execute(request_result.unwrap()).await;
184 Some(HttpReply {
185 status: 200,
186 contentType: "application/json".into(),
187 body: serde_json::to_vec(&res).unwrap().into(),
188 headers: allow_cors_with_origin("*"),
189 })
190 }
191 })
192}
193
194pub fn serve_graphiql(request: &HttpRequest) -> Option<HttpReply> {
201 if request.method == "GET"
202 && (request.target == "/graphiql.html" || request.target == "/graphiql")
203 {
204 Some(HttpReply {
205 status: 200,
206 contentType: "text/html".into(),
207 body: GraphiQLSource::build().endpoint("/graphql").finish().into(),
208 headers: allow_cors_with_origin("*"),
209 })
210 } else {
211 None
212 }
213}