2020-04-02 02:21:04 +00:00
|
|
|
#[macro_use]
|
|
|
|
extern crate thiserror;
|
|
|
|
|
2020-03-29 14:31:45 +00:00
|
|
|
use actix_rt;
|
|
|
|
use actix_web::{guard, web, App, HttpResponse, HttpServer};
|
|
|
|
use async_graphql::http::{graphiql_source, playground_source, GQLRequest, GQLResponse};
|
2020-04-02 04:57:53 +00:00
|
|
|
use async_graphql::ErrorExtensions;
|
2020-03-29 14:31:45 +00:00
|
|
|
use async_graphql::*;
|
2020-04-01 08:53:49 +00:00
|
|
|
use futures::TryFutureExt;
|
2020-03-29 14:31:45 +00:00
|
|
|
use serde_json::json;
|
|
|
|
|
2020-04-02 02:21:04 +00:00
|
|
|
#[derive(Debug, Error)]
|
2020-03-29 14:31:45 +00:00
|
|
|
pub enum MyError {
|
2020-04-02 02:21:04 +00:00
|
|
|
#[error("Could not find resource")]
|
2020-03-29 14:31:45 +00:00
|
|
|
NotFound,
|
2020-04-02 02:21:04 +00:00
|
|
|
|
|
|
|
#[error("ServerError")]
|
2020-03-29 14:31:45 +00:00
|
|
|
ServerError(String),
|
2020-04-02 02:21:04 +00:00
|
|
|
|
|
|
|
#[error("No Extensions")]
|
2020-03-29 14:31:45 +00:00
|
|
|
ErrorWithoutExtensions,
|
|
|
|
}
|
|
|
|
|
2020-04-02 04:57:53 +00:00
|
|
|
impl ErrorExtensions for MyError {
|
|
|
|
// lets define our base extensions
|
|
|
|
fn extend(&self) -> FieldError {
|
|
|
|
let extensions = match self {
|
2020-04-02 02:21:04 +00:00
|
|
|
MyError::NotFound => json!({"code": "NOT_FOUND"}),
|
|
|
|
MyError::ServerError(reason) => json!({ "reason": reason }),
|
|
|
|
MyError::ErrorWithoutExtensions => {
|
|
|
|
json!("This will be ignored since it does not represent an object.")
|
2020-03-29 14:31:45 +00:00
|
|
|
}
|
2020-04-02 04:57:53 +00:00
|
|
|
};
|
2020-03-29 14:31:45 +00:00
|
|
|
|
2020-04-02 04:57:53 +00:00
|
|
|
FieldError(format!("{}", self), Some(extensions))
|
|
|
|
}
|
2020-03-29 14:31:45 +00:00
|
|
|
}
|
|
|
|
|
2020-04-02 02:21:04 +00:00
|
|
|
struct QueryRoot;
|
2020-03-29 14:31:45 +00:00
|
|
|
|
|
|
|
#[Object]
|
|
|
|
impl QueryRoot {
|
2020-04-02 04:57:53 +00:00
|
|
|
// It works on foreign types without extensions as before
|
|
|
|
#[field]
|
|
|
|
async fn parse_without_extensions(&self) -> FieldResult<i32> {
|
|
|
|
Ok("234a".parse()?)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Foreign types can be extended
|
2020-03-29 14:31:45 +00:00
|
|
|
#[field]
|
2020-04-02 04:57:53 +00:00
|
|
|
async fn parse_with_extensions(&self) -> FieldResult<i32> {
|
|
|
|
Ok("234a"
|
|
|
|
.parse()
|
|
|
|
.map_err(|e: std::num::ParseIntError| e.extend_with(|_| json!({"code": 404})))?)
|
2020-03-29 14:31:45 +00:00
|
|
|
}
|
|
|
|
|
2020-04-02 04:57:53 +00:00
|
|
|
// THIS does unfortunately NOT work because ErrorExtensions is implemented for &E and not E.
|
|
|
|
// Which is necessary for the overwrite by the user.
|
|
|
|
|
|
|
|
//#[field]
|
|
|
|
// async fn parse_with_extensions_result(&self) -> FieldResult<i32> {
|
|
|
|
// Ok("234a".parse().extend_err(|_| json!({"code": 404}))?)
|
|
|
|
// }
|
|
|
|
|
|
|
|
// Using our own types we can implement some base extensions
|
2020-03-29 14:31:45 +00:00
|
|
|
#[field]
|
2020-04-02 04:57:53 +00:00
|
|
|
async fn extend(&self) -> FieldResult<i32> {
|
|
|
|
Err(MyError::NotFound.extend())?
|
2020-03-29 14:31:45 +00:00
|
|
|
}
|
|
|
|
|
2020-04-02 04:57:53 +00:00
|
|
|
// Or on the result
|
2020-03-29 14:31:45 +00:00
|
|
|
#[field]
|
2020-04-02 04:57:53 +00:00
|
|
|
async fn extend_result(&self) -> FieldResult<i32> {
|
|
|
|
Err(MyError::NotFound).extend()?
|
2020-03-29 14:31:45 +00:00
|
|
|
}
|
|
|
|
|
2020-04-02 04:57:53 +00:00
|
|
|
// Base extensions can be further extended
|
2020-03-29 14:31:45 +00:00
|
|
|
#[field]
|
2020-04-02 04:57:53 +00:00
|
|
|
async fn more_extensions(&self) -> FieldResult<String> {
|
|
|
|
// resolves to extensions: { "code": "NOT_FOUND", "reason": "my reason" }
|
|
|
|
Err(MyError::NotFound.extend_with(|_e| json!({"reason": "my reason"})))?
|
2020-03-29 14:31:45 +00:00
|
|
|
}
|
2020-04-02 02:21:04 +00:00
|
|
|
|
2020-04-02 04:57:53 +00:00
|
|
|
// works with results as well
|
2020-04-02 02:21:04 +00:00
|
|
|
#[field]
|
2020-04-02 04:57:53 +00:00
|
|
|
async fn more_extensions_on_result(&self) -> FieldResult<String> {
|
|
|
|
// resolves to extensions: { "code": "NOT_FOUND", "reason": "my reason" }
|
|
|
|
Err(MyError::NotFound).extend_err(|_e| json!({"reason": "my reason"}))?
|
|
|
|
}
|
|
|
|
|
|
|
|
// extend_with is chainable
|
|
|
|
#[field]
|
|
|
|
async fn chainable_extensions(&self) -> FieldResult<String> {
|
|
|
|
let err = MyError::NotFound
|
|
|
|
.extend_with(|_| json!({"ext1": 1}))
|
|
|
|
.extend_with(|_| json!({"ext2": 2}))
|
|
|
|
.extend_with(|_| json!({"ext3": 3}));
|
|
|
|
Err(err)?
|
|
|
|
}
|
|
|
|
|
|
|
|
// extend_with overwrites keys which are already present
|
|
|
|
#[field]
|
|
|
|
async fn overwrite(&self) -> FieldResult<String> {
|
|
|
|
Err(MyError::NotFound.extend_with(|_| json!({"code": "overwritten"})))?
|
2020-04-02 02:21:04 +00:00
|
|
|
}
|
2020-03-29 14:31:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn index(
|
|
|
|
s: web::Data<Schema<QueryRoot, EmptyMutation, EmptySubscription>>,
|
|
|
|
req: web::Json<GQLRequest>,
|
|
|
|
) -> web::Json<GQLResponse> {
|
2020-04-01 08:53:49 +00:00
|
|
|
web::Json(GQLResponse(
|
2020-04-02 04:53:53 +00:00
|
|
|
futures::future::ready(req.into_inner().into_query_builder(&s))
|
2020-04-01 08:53:49 +00:00
|
|
|
.and_then(|builder| builder.execute())
|
|
|
|
.await,
|
|
|
|
))
|
2020-03-29 14:31:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async fn gql_playgound() -> HttpResponse {
|
|
|
|
HttpResponse::Ok()
|
|
|
|
.content_type("text/html; charset=utf-8")
|
|
|
|
.body(playground_source("/", None))
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn gql_graphiql() -> HttpResponse {
|
|
|
|
HttpResponse::Ok()
|
|
|
|
.content_type("text/html; charset=utf-8")
|
|
|
|
.body(graphiql_source("/"))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[actix_rt::main]
|
|
|
|
async fn main() -> std::io::Result<()> {
|
2020-04-02 04:57:53 +00:00
|
|
|
println!("Playground: http://localhost:8000");
|
|
|
|
println!("Graphiql: http://localhost:8000/graphiql");
|
|
|
|
|
2020-03-29 14:31:45 +00:00
|
|
|
HttpServer::new(move || {
|
|
|
|
App::new()
|
2020-04-02 02:21:04 +00:00
|
|
|
.data(Schema::new(QueryRoot, EmptyMutation, EmptySubscription))
|
2020-03-29 14:31:45 +00:00
|
|
|
.service(web::resource("/").guard(guard::Post()).to(index))
|
|
|
|
.service(web::resource("/").guard(guard::Get()).to(gql_playgound))
|
|
|
|
.service(
|
|
|
|
web::resource("/graphiql")
|
|
|
|
.guard(guard::Get())
|
|
|
|
.to(gql_graphiql),
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.bind("127.0.0.1:8000")?
|
|
|
|
.run()
|
|
|
|
.await
|
|
|
|
}
|