Repository: permitio/cedar-agent (language: rust)

Issue:
unexpected HTML response
just playing with this, I ran into a surprising HTML response -- not surprising in its error, but rather that it wasn't JSON

```
% curl -X PUT -H "Content-Type: application/json" -d @./examples/policies.json http://localhost:8180/v1/policies/viewers-policy
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>422 Unprocessable Entity</title>
</head>
<body align="center">
    <div role="main" align="center">
        <h1>422: Unprocessable Entity</h1>
        <p>The request was well-formed but was unable to be followed due to semantic errors.</p>
        <hr />
    </div>
    <div role="contentinfo" align="center">
        <small>Rocket</small>
    </div>
</body>
</html>
%         
```


Discussion / hints:
Thanks @srenatus !
@omer9564 will take a look into it.
Hey @srenatus 

The [example/policies.json](https://github.com/permitio/cedar-agent/blob/94d70fef56eb5113f9d6c63b7554e6db206673b4/examples/policies.json) file has list of policies.
The `PUT /v1/policies/{policy_id}` is supposed to have only one policy in the body, this is because you can't have multiple `permit` / `forbid` rules with the same id. 
If you'd like to create multiple policies you can use the `PUT /v1/policies` API, this will replace your existing policies with the ones in the request body.
You can check out the [README](https://github.com/permitio/cedar-agent/blob/94d70fef56eb5113f9d6c63b7554e6db206673b4/README.md), it has examples usage instructions.

It is still an issue that the response is in HTML format, and we'll work on a fix, 
Thanks for pointing this out :)  
Thanks for your help. I'm afraid I wasn't expecting anything but an error of some sort -- I was intentionally throwing this at the wrong endpoint to see what would happen 🙃 It's just the HTML that surprised me. (Sorry for not having made this clear.)

Fix patch:
diff --git a/src/common.rs b/src/common.rs
index db2ee9c..bd99e3b 100644
--- a/src/common.rs
+++ b/src/common.rs
@@ -1,6 +1,11 @@
 use std::fmt;
 use std::fmt::{Display, Formatter};
+use std::str::FromStr;
 
+use async_trait::async_trait;
+use rocket::fairing::{Fairing, Info, Kind};
+use rocket::http::{ContentType, Header};
+use rocket::{Data, Request};
 use thiserror::Error;
 
 #[derive(Debug, Error)]
@@ -11,3 +16,61 @@ impl Display for EmptyError {
         return Ok(());
     }
 }
+
+pub(crate) struct DefaultContentType(ContentType);
+
+impl DefaultContentType {
+    /// Set a default content type for incoming messages
+    pub fn new(t: ContentType) -> Self {
+        Self(t)
+    }
+}
+
+impl DefaultContentType {
+    fn is_matching_wildcard(&self, accept_headers: Vec<&str>) -> bool {
+        for accept_header in accept_headers {
+            let header = ContentType::from_str(accept_header);
+            match header {
+                Ok(header) => {
+                    if header.top() == "*" && header.sub() == "*" {
+                        // Accepts all content types should be transformed
+                        // to the default content type
+                        return true;
+                    }
+                    if header.top() == self.0.top() && header.sub() == "*" {
+                        // Accepts all subtypes of the default content type
+                        return true;
+                    }
+                    if header.top() == "*" && header.sub() == self.0.sub() {
+                        // Accepts all types of the default content type
+                        return true;
+                    }
+                }
+                Err(_) => {
+                    // Convert invalid headers to the default content type
+                    return true;
+                }
+            };
+        }
+        return false;
+    }
+}
+
+#[async_trait]
+impl Fairing for DefaultContentType {
+    fn info(&self) -> Info {
+        Info {
+            name: "DefaultContentType",
+            kind: Kind::Request,
+        }
+    }
+
+    async fn on_request(&self, req: &mut Request<'_>, _data: &mut Data<'_>) {
+        let accept_header = req.headers().get_one("Accept");
+        if accept_header.is_none()
+            || self.is_matching_wildcard(accept_header.unwrap().split(", ").collect())
+        {
+            req.replace_header(Header::new("Accept", format!("{}", self.0)));
+        }
+    }
+}
diff --git a/src/main.rs b/src/main.rs
index 9edd2fb..8706223 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,6 +4,7 @@ extern crate rocket;
 use std::borrow::Borrow;
 
 use rocket::catchers;
+use rocket::http::ContentType;
 use rocket_okapi::settings::UrlObject;
 use rocket_okapi::{openapi_get_routes, rapidoc::*, swagger_ui::*};
 
@@ -27,6 +28,7 @@ async fn main() {
     logger::init(&config);
     let server_config: rocket::figment::Figment = config.borrow().into();
     let launch_result = rocket::custom(server_config)
+        .attach(common::DefaultContentType::new(ContentType::JSON))
         .manage(config)
         .manage(Box::new(MemoryPolicyStore::new()) as Box<dyn PolicyStore>)
         .manage(Box::new(MemoryDataStore::new()) as Box<dyn DataStore>)


Test patch: