RJMetrics / sweet-liberty

A library for building database-backed RESTful services using Clojure
Apache License 2.0
104 stars 6 forks source link

Return 415 Unsupported Media Type when appropriate #9

Open cmerrick opened 9 years ago

cmerrick commented 9 years ago

Currently throws a gnarly stack trace that just confuses the issue. A 415 response with a message body describing acceptable formats will save people time debugging.

karstendick commented 9 years ago

Could you give an example of a request that shows this problem?

cmerrick commented 9 years ago

Yessir. Running https://github.com/RJMetrics/sweet-liberty-example/ with the instructions in the README. Works well for a GET:

› curl http://localhost:3000/dogs                      
[
  {
    "breed": "poodle",
    "name": "Fido",
    "id": 1
  },
  {
    "breed": "corgi",
    "name": "Lacy",
    "id": 2
  },
  {
    "breed": "chihuahua",
    "name": "Rex",
    "id": 3
  },
  {
    "breed": "dalmation",
    "name": "Spot",
    "id": 4
  },
  {
    "breed": "chihuahua",
    "name": "Taco",
    "id": 5
  },
  {
    "breed": "corgi",
    "name": "Brody",
    "id": 6
  }
]

When I go to POST some JSON:

› curl -d '{"name": "Roxy", "breed": "Springer Spaniel"}' http://localhost:3000/dogs                                        
{
  "stack-trace": [
    "org.hsqldb.jdbc.Util.sqlException(Unknown Source)",
    "org.hsqldb.jdbc.Util.sqlException(Unknown Source)",
    "org.hsqldb.jdbc.JDBCPreparedStatement.<init>(Unknown Source)",
    "org.hsqldb.jdbc.JDBCConnection.prepareStatement(Unknown Source)",
    "clojure.java.jdbc$prepare_statement$fn__2846.invoke(jdbc.clj:411)",
    "clojure.java.jdbc$prepare_statement.doInvoke(jdbc.clj:407)",
    "clojure.lang.RestFn.invoke(RestFn.java:464)",
    "clojure.java.jdbc$db_do_prepared_return_keys.invoke(jdbc.clj:650)",
    "clojure.java.jdbc$multi_insert_helper$fn__2957.invoke(jdbc.clj:853)",
    "clojure.core$map$fn__4245.invoke(core.clj:2559)",
    "clojure.lang.LazySeq.sval(LazySeq.java:40)",
    "clojure.lang.LazySeq.seq(LazySeq.java:49)",
    "clojure.lang.RT.seq(RT.java:484)",
    "clojure.core$seq.invoke(core.clj:133)",
    "clojure.core$dorun.invoke(core.clj:2855)",
    "clojure.core$doall.invoke(core.clj:2871)",
    "clojure.java.jdbc$multi_insert_helper.invoke(jdbc.clj:852)",
    "clojure.java.jdbc$insert_helper$fn__2960.invoke(jdbc.clj:863)",
    "clojure.java.jdbc$db_transaction_STAR_.doInvoke(jdbc.clj:546)",
    "clojure.lang.RestFn.invoke(RestFn.java:425)",
    "clojure.java.jdbc$insert_helper.invoke(jdbc.clj:863)",
    "clojure.java.jdbc$insert_BANG_.doInvoke(jdbc.clj:955)",
    "clojure.lang.RestFn.invoke(RestFn.java:442)",
    "com.rjmetrics.sweet_liberty.db$insert_entity_into_storage.invoke(db.clj:64)",
    "com.rjmetrics.sweet_liberty.handlers$make_create_entity_fn$fn__3928$fn__3931.invoke(handlers.clj:148)",
    "com.rjmetrics.sweet_liberty.handlers$run_with_conditions.invoke(handlers.clj:39)",
    "com.rjmetrics.sweet_liberty.handlers$make_create_entity_fn$fn__3928.invoke(handlers.clj:145)",
    "liberator.core$decide.invoke(core.clj:98)",
    "liberator.core$post_BANG_.invoke(core.clj:264)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$can_post_to_missing_QMARK_.invoke(core.clj:266)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$post_to_missing_QMARK_.invoke(core.clj:268)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$existed_QMARK_.invoke(core.clj:285)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$method_put_QMARK_.invoke(core.clj:301)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$if_match_star_exists_for_missing_QMARK_.invoke(core.clj:305)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$exists_QMARK_.invoke(core.clj:406)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$processable_QMARK_.invoke(core.clj:409)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$accept_encoding_exists_QMARK_.invoke(core.clj:428)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$accept_charset_exists_QMARK_.invoke(core.clj:441)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$accept_language_exists_QMARK_.invoke(core.clj:455)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$media_type_available_QMARK_.invoke(core.clj:465)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$accept_exists_QMARK_.invoke(core.clj:468)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$is_options_QMARK_.invoke(core.clj:485)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$valid_entity_length_QMARK_.invoke(core.clj:488)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$known_content_type_QMARK_.invoke(core.clj:491)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$valid_content_header_QMARK_.invoke(core.clj:493)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$allowed_QMARK_.invoke(core.clj:496)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$authorized_QMARK_.invoke(core.clj:499)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$malformed_QMARK_.invoke(core.clj:502)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$method_allowed_QMARK_.invoke(core.clj:505)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$uri_too_long_QMARK_.invoke(core.clj:508)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$known_method_QMARK_.invoke(core.clj:511)",
    "liberator.core$decide.invoke(core.clj:103)",
    "liberator.core$service_available_QMARK_.invoke(core.clj:514)",
    "liberator.core$run_resource.invoke(core.clj:598)",
    "liberator.core$resource$fn__4775.invoke(core.clj:616)",
    "compojure.response$eval1314$fn__1315.invoke(response.clj:27)",
    "compojure.response$eval1275$fn__1276$G__1266__1283.invoke(response.clj:10)",
    "compojure.core$make_route$fn__1450.invoke(core.clj:94)",
    "compojure.core$if_route$fn__1434.invoke(core.clj:40)",
    "compojure.core$if_method$fn__1427.invoke(core.clj:25)",
    "compojure.core$routing$fn__1456.invoke(core.clj:107)",
    "clojure.core$some.invoke(core.clj:2515)",
    "compojure.core$routing.doInvoke(core.clj:107)",
    "clojure.lang.RestFn.applyTo(RestFn.java:139)",
    "clojure.core$apply.invoke(core.clj:626)",
    "compojure.core$routes$fn__1460.invoke(core.clj:112)",
    "clojure.lang.Var.invoke(Var.java:379)",
    "compojure.core$routing$fn__1456.invoke(core.clj:107)",
    "clojure.core$some.invoke(core.clj:2515)",
    "compojure.core$routing.doInvoke(core.clj:107)",
    "clojure.lang.RestFn.applyTo(RestFn.java:139)",
    "clojure.core$apply.invoke(core.clj:626)",
    "compojure.core$routes$fn__1460.invoke(core.clj:112)",
    "ring.middleware.params$wrap_params$fn__1722.invoke(params.clj:58)",
    "noir.util.middleware$wrap_request_map$fn__7814.invoke(middleware.clj:44)",
    "ring.middleware.keyword_params$wrap_keyword_params$fn__5514.invoke(keyword_params.clj:32)",
    "ring.middleware.nested_params$wrap_nested_params$fn__5563.invoke(nested_params.clj:70)",
    "ring.middleware.params$wrap_params$fn__1722.invoke(params.clj:58)",
    "hiccup.middleware$wrap_base_url$fn__6804.invoke(middleware.clj:12)",
    "ring.middleware.format_params$wrap_format_params$fn__7117.invoke(format_params.clj:102)",
    "ring.middleware.format_params$wrap_format_params$fn__7117.invoke(format_params.clj:102)",
    "ring.middleware.format_response$wrap_format_response$fn__7751.invoke(format_response.clj:113)",
    "ring.middleware.multipart_params$wrap_multipart_params$fn__5607.invoke(multipart_params.clj:107)",
    "noir.validation$wrap_noir_validation$fn__6854.invoke(validation.clj:153)",
    "noir.cookies$noir_cookies$fn__6900.invoke(cookies.clj:72)",
    "ring.middleware.cookies$wrap_cookies$fn__6634.invoke(cookies.clj:171)",
    "noir.session$noir_flash$fn__6945.invoke(session.clj:142)",
    "ring.middleware.flash$wrap_flash$fn__6779.invoke(flash.clj:31)",
    "noir.session$noir_session$fn__6935.invoke(session.clj:97)",
    "ring.middleware.session$wrap_session$fn__6759.invoke(session.clj:85)",
    "clojure.lang.Var.invoke(Var.java:379)",
    "ring.middleware.refresh$wrap_with_script$fn__1799.invoke(refresh.clj:81)",
    "compojure.core$routing$fn__1456.invoke(core.clj:107)",
    "clojure.core$some.invoke(core.clj:2515)",
    "compojure.core$routing.doInvoke(core.clj:107)",
    "clojure.lang.RestFn.applyTo(RestFn.java:139)",
    "clojure.core$apply.invoke(core.clj:626)",
    "compojure.core$routes$fn__1460.invoke(core.clj:112)",
    "ring.middleware.params$wrap_params$fn__1722.invoke(params.clj:58)",
    "ring.middleware.reload$wrap_reload$fn__1117.invoke(reload.clj:18)",
    "ring.middleware.stacktrace$wrap_stacktrace_log$fn__884.invoke(stacktrace.clj:17)",
    "ring.middleware.stacktrace$wrap_stacktrace_web$fn__946.invoke(stacktrace.clj:80)",
    "ring.adapter.jetty$proxy_handler$fn__96.invoke(jetty.clj:18)",
    "ring.adapter.jetty.proxy$org.eclipse.jetty.server.handler.AbstractHandler$ff19274a.handle(Unknown Source)",
    "org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:116)",
    "org.eclipse.jetty.server.Server.handle(Server.java:363)",
    "org.eclipse.jetty.server.AbstractHttpConnection.handleRequest(AbstractHttpConnection.java:483)",
    "org.eclipse.jetty.server.AbstractHttpConnection.content(AbstractHttpConnection.java:931)",
    "org.eclipse.jetty.server.AbstractHttpConnection$RequestHandler.content(AbstractHttpConnection.java:992)",
    "org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:856)",
    "org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:240)",
    "org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:82)",
    "org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:628)",
    "org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:52)",
    "org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608)",
    "org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543)",
    "java.lang.Thread.run(Thread.java:744)"
  ],
  "exception-message": "unexpected token: )"
}

Setting the Content-Type fixes it:

› curl -H "Content-Type: application/json" -d '{"name": "Roxy", "breed": "Springer Spaniel"}' http://localhost:3000/dogs    
{"breed":"Springer Spaniel","name":"Roxy","id":7}