├── .travis.yml ├── .gitignore ├── CHANGELOG.md ├── project.clj ├── test └── io │ └── clojure │ ├── liberator_transit │ └── generators.clj │ └── liberator_transit_test.clj ├── src └── io │ └── clojure │ └── liberator_transit.clj ├── README.md └── LICENSE /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | lein: lein2 3 | jdk: 4 | - openjdk7 5 | - oraclejdk7 6 | - oraclejdk8 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # transit-liberator changes 2 | 3 | ## 0.3.1 4 | 5 | * Fix bug when `Accept` header is missing #6, #12 6 | * Update dependencies 7 | 8 | ## 0.3.0 9 | 10 | * Add support for write handlers 11 | 12 | ## 0.2.0 13 | 14 | * Add support for specifying options to transit-liberator 15 | * Fix problem with charset parameter being added to the returned Content-Type 16 | header 17 | * Add `as-response` 18 | 19 | ## 0.1.0 20 | 21 | * Initial release 22 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject io.clojure/liberator-transit "0.3.2-SNAPSHOT" 2 | :description "Library to add Transit encoding support to Liberator" 3 | :url "https://github.com/sattvik/liberator-transit" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.8.0" :scope "provided"] 7 | [com.cognitect/transit-clj "0.8.290"] 8 | [liberator "0.14.1"]] 9 | :profiles {:dev {:dependencies [[com.gfredericks/test.chuck "0.2.7"] 10 | [compojure "1.5.1"] 11 | [org.clojure/test.check "0.9.0"] 12 | [ring-mock "0.1.5"]]}}) 13 | -------------------------------------------------------------------------------- /test/io/clojure/liberator_transit/generators.clj: -------------------------------------------------------------------------------- 1 | ; Copyright © 2014 Daniel Solano Gómez 2 | ; All rights reserved. 3 | ; 4 | ; The use and distribution terms for this software are covered by the Eclipse 5 | ; Public License 1.0 which can be 6 | ; found in the file LICENSE at the root of this distribution. By using this 7 | ; software in any fashion, you are agreeing to be bound by the terms of this 8 | ; license. You must not remove this notice, or any other, from this software. 9 | 10 | (ns io.clojure.liberator-transit.generators 11 | (:require [clojure.string :as str] 12 | [clojure.test.check.generators :as gen] 13 | [com.gfredericks.test.chuck.generators :as gen']) 14 | (:import [java.util Date])) 15 | 16 | (def nil-generator 17 | (gen/return nil)) 18 | 19 | (def float-generator 20 | (gen/fmap (fn [d] 21 | (cond 22 | (.isNaN d) 23 | Float/NaN 24 | 25 | (= Double/POSITIVE_INFINITY d) 26 | Float/POSITIVE_INFINITY 27 | 28 | (= Double/NEGATIVE_INFINITY d) 29 | Float/NEGATIVE_INFINITY 30 | 31 | :default 32 | (unchecked-float d))) 33 | gen/double)) 34 | 35 | (def big-decimal-generator 36 | (gen/fmap 37 | #(BigDecimal. %) 38 | (gen'/string-from-regex #"\d+\.\d+"))) 39 | 40 | (def big-int-generator 41 | (gen/fmap 42 | #(BigInteger. %) 43 | (gen'/string-from-regex #"\d+"))) 44 | 45 | (def time-generator 46 | (gen/fmap 47 | (fn [offset] 48 | (Date. (+ (System/currentTimeMillis) offset))) 49 | gen/int)) 50 | 51 | ; TODO: really generate URIs 52 | (def uri-generator 53 | (gen/elements [(java.net.URI. "/foo/bar/baz") 54 | (java.net.URI. "http://example.com") 55 | (java.net.URI. "https://user:foo@example.com/baz/dldl/lsjdlj?q=42") 56 | (java.net.URI. "https://user:foo@example.com/baz/dldl/lsjdlj?q=42#ldjd") 57 | (java.net.URI. "urn:bar") 58 | (java.net.URI. "file:foo") 59 | (java.net.URI. "file:/foo") 60 | (java.net.URI. "http://localhost:9000/check") 61 | (java.net.URI. "http://[::1]:9000/check")])) 62 | 63 | (def simple-type-generator 64 | (gen/frequency [[10 gen/simple-type] 65 | [1 nil-generator] 66 | [1 gen/double] 67 | [1 float-generator] 68 | [1 big-decimal-generator] 69 | [1 big-int-generator] 70 | [1 time-generator] 71 | [1 gen/uuid] 72 | [1 uri-generator]])) 73 | 74 | (defn container-generator 75 | [inner-type] 76 | (gen/one-of [(gen/vector inner-type) 77 | (gen/list inner-type) 78 | (gen/map inner-type inner-type) 79 | (gen/set inner-type)])) 80 | 81 | (def content-generator 82 | (gen/recursive-gen container-generator simple-type-generator)) 83 | 84 | (def sequence-generator 85 | (gen/one-of [(gen/vector content-generator) 86 | (gen/list content-generator)])) 87 | 88 | (def map-generator 89 | (gen/map simple-type-generator content-generator)) 90 | -------------------------------------------------------------------------------- /src/io/clojure/liberator_transit.clj: -------------------------------------------------------------------------------- 1 | ; Copyright © 2014 Daniel Solano Gómez 2 | ; All rights reserved. 3 | ; 4 | ; The use and distribution terms for this software are covered by the Eclipse 5 | ; Public License 1.0 which can be 6 | ; found in the file LICENSE at the root of this distribution. By using this 7 | ; software in any fashion, you are agreeing to be bound by the terms of this 8 | ; license. You must not remove this notice, or any other, from this software. 9 | 10 | (ns io.clojure.liberator-transit 11 | "Main namespace for liberator-transit. Just adds new methods to the 12 | multimethods that liberator uses to render maps and sequences." 13 | {:author "Daniel Solano Gómez"} 14 | (:require [liberator.representation :as liberator :refer [render-map-generic render-seq-generic]] 15 | [cognitect.transit :as transit]) 16 | (:import [java.io ByteArrayInputStream ByteArrayOutputStream])) 17 | 18 | (def default-options 19 | "The default options for liberator-transit. 20 | 21 | * `:allow-json-verbose?`: if false, do not ever produce verbose JSON 22 | output 23 | * `:json-verbose-is-default?`: if true, produce verbose JSON output by 24 | default. 25 | * `:initial-buffer-size`: The initial buffer size to use when generating the 26 | output. Note that the buffer will automatically grow as needed. It 27 | probably only makes sense to change this if you are serialising very large 28 | objects." 29 | {:allow-json-verbose? true 30 | :json-verbose-is-default? false 31 | :initial-buffer-size 4096}) 32 | 33 | (defn ^:private requested-verbose? 34 | "Returns true if the givn Ring request contains an \"Accept\" header that 35 | contains the string \"verbose\"." 36 | [request] 37 | (some-> request 38 | (get-in [:headers "accept"]) 39 | (.indexOf "verbose") 40 | (pos?))) 41 | 42 | (defn ^:private json-type 43 | "Returns which JSON type should be produced by liberator-transit depending on 44 | the options passed in through the context and in the request headers. The 45 | options are stored in the context map under the `:liberator-transit` key. 46 | The determination is done as follows: 47 | 48 | 1. If `:allow-json-verbose?` option is set to a false value, return`:json`. 49 | 2. If the request contains \"verbose\" as part of the \"Accept\" header, then 50 | return `:json-verbose`. 51 | 3. If `:json-verbose-is-default?` option is set to a true value, 52 | return`:json-verbose`. 53 | 4. If none of the above apply, return `:json`." 54 | [{:keys [liberator-transit request]}] 55 | (let [{:keys [allow-json-verbose? 56 | json-verbose-is-default?]} (merge default-options 57 | liberator-transit)] 58 | (cond 59 | (not allow-json-verbose?) :json 60 | (requested-verbose? request) :json-verbose 61 | json-verbose-is-default? :json-verbose 62 | :default :json))) 63 | 64 | ;; If we return an input stream, Liberator will attempt to add a charset 65 | ;; parameter to the Content-Type header returned to the client. For a transit 66 | ;; response, this is undesireable. As a result, the TransitResponse type will 67 | ;; ensure that does not occur. 68 | (defrecord TransitResponse [bytes] 69 | liberator.representation.Representation 70 | (as-response [_ context] 71 | {:body bytes 72 | :headers {"Content-Type" (get-in context [:representation :media-type])}})) 73 | 74 | (defn ^:private render-as-transit 75 | "Renders the given `data` to an input stream. Liberator will pass this 76 | stream to Ring, which will write the contents into the response. `type` 77 | must be a supported transit-clj writer type, e.g. `:json`." 78 | [{options :liberator-transit} data type] 79 | (let [initial-size (get options :initial-buffer-size (:initial-buffer-size default-options)) 80 | buffer (ByteArrayOutputStream. initial-size) 81 | writer (transit/writer buffer type (select-keys options [:handlers]))] 82 | (transit/write writer data) 83 | (->TransitResponse (ByteArrayInputStream. (.toByteArray buffer))))) 84 | 85 | ;; Renders a map using the JSON transit encoding. If the original "Accept" 86 | ;; header included "verbose", i.e. `application/transit+json;verbose`, then 87 | ;; this will write verbose JSON output. Otherwise, this will produce the 88 | ;; default JSON encoding. 89 | (defmethod render-map-generic "application/transit+json" 90 | [data context] 91 | (render-as-transit context data (json-type context))) 92 | 93 | ;; Renders a map using the MessagePack transit encoding. 94 | (defmethod render-map-generic "application/transit+msgpack" 95 | [data context] 96 | (render-as-transit context data :msgpack)) 97 | 98 | ;; Renders a sequence using the JSON transit encoding. If the original 99 | ;; "Accept" header included "verbose", i.e. `application/transit+json;verbose`, 100 | ;; then this will write verbose JSON output. Otherwise, this will produce the 101 | ;; default JSON encoding. 102 | (defmethod render-seq-generic "application/transit+json" 103 | [data context] 104 | (render-as-transit context data (json-type context))) 105 | 106 | ;; Renders a sequence using the MessagePack transit encoding. 107 | (defmethod render-seq-generic "application/transit+msgpack" 108 | [data context] 109 | (render-as-transit context data :msgpack)) 110 | 111 | (defn as-response 112 | "This convenience function allows you to specify options to liberator-transit 113 | in one handy place. As an alternative to specifying liberator-transit options 114 | elsewere in your resource, you can call this function as the value of 115 | `:as-response` key, as in: 116 | 117 | (defresource foo 118 | ; … 119 | :as-response (as-response {:allow-json-verbose? false}) 120 | 121 | Note that if you do specify liberator-transit options elsewhere in your 122 | resource, they will override the options set here. 123 | 124 | Additionally, if you already have an `as response` function you would like to 125 | use, can wrap it with this one. Otherwsie, this will just use Liberator’s 126 | default implementation." 127 | ([options] 128 | (as-response options liberator/as-response)) 129 | ([options as-response-fn] 130 | (fn [data {context-options :liberator-transit :as context}] 131 | (as-response-fn data 132 | (assoc context 133 | :liberator-transit 134 | (merge options context-options)))))) 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # liberator-transit 2 | 3 | liberator-transit is a Clojure library designed to add support to 4 | [Liberator][liberator] for encoding sequences and maps into [Transit][transit]. 5 | It supports JSON, verbose JSON, and MessagePack encodings. 6 | 7 | It is a fairly simple library, but it’s handy to have. 8 | 9 | [![Build Status](https://travis-ci.org/sattvik/liberator-transit.svg?branch=master)][build-status] 10 | [![Clojars Project](https://img.shields.io/clojars/v/io.clojure/liberator-transit.svg)](https://clojars.org/io.clojure/liberator-transit) 11 | 12 | [liberator]: http://clojure-liberator.github.io/liberator/ 13 | [transit]: https://github.com/cognitect/transit-format 14 | [build-status]: https://travis-ci.org/sattvik/liberator-transit (Travis CI build status) 15 | 16 | 17 | ## Installation 18 | 19 | To install, just add the following to your project :dependencies: 20 | 21 | ```clojure 22 | [io.clojure/liberator-transit "0.3.1"] 23 | ``` 24 | 25 | 26 | ## Usage 27 | 28 | ### Loading liberator-transit 29 | 30 | To load support for Transit into Liberator, just require the 31 | `io.clojure.liberator-transit` namespace: 32 | 33 | ```clojure 34 | (ns my.liberator.app 35 | (:require [io.clojure.liberator-transit])) 36 | ``` 37 | 38 | 39 | ### Supporing Transit in a resource 40 | 41 | The only other thing you need to do is to let Liberator know you would like to 42 | support the Transit MIME types: `application/transit+json` and 43 | `application/transit+msgpack`, as in: 44 | 45 | 46 | ```clojure 47 | (defresource my-awesom-resource 48 | :available-media-types ["application/transit+json" 49 | "application/transit+msgpack" 50 | "application/json"] 51 | :handle-ok ["this" "is" "awesome"]) 52 | ``` 53 | 54 | That’s it. If your `handle-ok` returns a sequence or a map, it will now be 55 | encoded into Transit/JSON or Transit/MessagePack if your client requests it. 56 | 57 | 58 | ### Getting verbose JSON 59 | 60 | Both varieties of Transit/JSON use the same MIME type. By default, 61 | liberator-transit uses the non-verbose JSON format. In order to get verbose 62 | JSON output, a client needs to include "verbose" as part of the "Accept" 63 | header. For example: 64 | 65 | ``` 66 | curl -H "Accept: application/transit+json;verbose" \ 67 | http://localhost:3000/hello 68 | ``` 69 | 70 | You can completely disable verbose JSON output by setting the 71 | `:allow-json-verbose?` option to a false value. Additionally, by setting 72 | `"json-verbose-is-default?` to a true value, JSON responses will be verbose by 73 | default. See the next section about setting options for more information. 74 | 75 | ### Setting options 76 | 77 | You can set various options to modify the behaviour of `liberator-transit`. 78 | The supported options include: 79 | 80 | * `:allow-json-verbose?`: if false, do not ever produce verbose JSON output. 81 | * `:json-verbose-is-default?`: if true, produce verbose JSON output by default. 82 | * `:initial-buffer-size`: The initial buffer size to use when generating the 83 | output. Note that the buffer will automatically grow as needed. It probably 84 | only makes sense to change this if you are serialising very large objects. 85 | * `:handlers`: a map of write handlers that will be passed directly to transit 86 | 87 | liberator-transit looks for its options in the Liberator context under they key 88 | `:liberator-transit`. As such you can set options anywhere in your resource 89 | where you can modify the context. For example, the following sample resource 90 | will modify the context as part of the `exists?` decision: 91 | 92 | ```clojure 93 | (defresource hello-resource [name] 94 | :exists? {:liberator-transit {:allow-json-verbose? false}} 95 | :available-media-types ["application/transit+json"] 96 | :handle-ok {:message (str "Hello, " name \!)}) 97 | ``` 98 | 99 | Additionally, you can set options by specifying a `as-response` function. 100 | transit-liberator provides `io.clojure.transit-liberator/as-response`, 101 | available in two different arities. The unary form simply takes an options map 102 | and invokes Liberator’s default behaviour. The following is equivalent to the 103 | previous example: 104 | 105 | ```clojure 106 | (defresource hello-resource [name] 107 | :available-media-types ["application/transit+json"] 108 | :handle-ok {:message (str "Hello, " name \!)} 109 | :as-response (transit-liberator/as-response 110 | {:allow-json-verbose? false})) 111 | ``` 112 | 113 | Additionally, in the case you already have a custom `as-response` function you 114 | would like to use, you can wrap it using transit-liberator’s `as-response`: 115 | 116 | ```clojure 117 | (defresource hello-resource [name] 118 | :available-media-types ["application/transit+json"] 119 | :handle-ok {:message (str "Hello, " name \!)} 120 | :as-response (transit-liberator/as-response 121 | {:allow-json-verbose? false} 122 | my-as-response)) 123 | ``` 124 | 125 | Note that any options specified elsewhere in your resource definition will 126 | override any options set as part of the `as-response` call. 127 | 128 | ## Contributors 129 | 130 | All contributors to liberator-transit by first commit: 131 | 132 | * Daniel Solano Gómez (sattvik) 133 | * Rafael Khan (rafkhan) 134 | * Alexander Kiel (alexanderkiel) 135 | * Steve Miner (miner) 136 | 137 | Additionally, a special thanks to the following people for pointing out an 138 | error in my README, which I ignored for far too long: 139 | 140 | * Rafael Khan (rafkhan) 141 | * Lucas Bradstreet (lbradstreet) 142 | * Tero Parviainen (teropa) 143 | * Kristian Frøhlich Hansen (kfh) 144 | * David Whittington (djwhitt) 145 | * Mike Janger (inchingforward) 146 | * Brian Jenkins (bonkydog) 147 | * Dan Kersten (danielytics) 148 | * spieden 149 | * itkach 150 | 151 | ## To-do 152 | 153 | I am not sure what might be desired by user of the library, but a few ideas I 154 | have include: 155 | 156 | * Write a proper URI generator for the tests 157 | * Write a Transit link generator for the tests 158 | * Should liberator-transit modify the content type or add additional headers to 159 | let the consumer know it is producing verbose JSON? 160 | 161 | ## License 162 | 163 | Copyright © 2014-2016 Daniel Solano Gómez 164 | All rights reserved. 165 | 166 | The use and distribution terms for this software are covered by the Eclipse 167 | Public License 1.0 which can 168 | be found in the file LICENSE at the root of this distribution. By using this 169 | software in any fashion, you are agreeing to be bound by the terms of this 170 | license. You must not remove this notice, or any other, from this software. 171 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor tocontrol, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /test/io/clojure/liberator_transit_test.clj: -------------------------------------------------------------------------------- 1 | ; Copyright © 2014 Daniel Solano Gómez 2 | ; All rights reserved. 3 | ; 4 | ; The use and distribution terms for this software are covered by the Eclipse 5 | ; Public License 1.0 which can be 6 | ; found in the file LICENSE at the root of this distribution. By using this 7 | ; software in any fashion, you are agreeing to be bound by the terms of this 8 | ; license. You must not remove this notice, or any other, from this software. 9 | 10 | (ns io.clojure.liberator-transit-test 11 | (:require [clojure.java.io :as io] 12 | [clojure.test :refer :all] 13 | [clojure.test.check.clojure-test :refer [defspec]] 14 | [clojure.test.check.properties :as prop] 15 | [cognitect.transit :as transit] 16 | [io.clojure.liberator-transit :as lt] 17 | [io.clojure.liberator-transit.generators :as gen] 18 | [liberator.core :as liberator :refer [defresource]] 19 | [liberator.dev :refer [wrap-trace]] 20 | [liberator.representation :as rep] 21 | [ring.mock.request :as mock])) 22 | 23 | (defresource test-resource [value] 24 | :available-media-types ["application/transit+json" "application/transit+msgpack"] 25 | :handle-ok value) 26 | 27 | (defn make-request 28 | ([] (make-request nil)) 29 | ([type] 30 | (cond-> (mock/request :get "/") 31 | type (mock/header "Accept" type)))) 32 | 33 | (defn json-request 34 | ([] 35 | (make-request "application/transit+json")) 36 | ([_] 37 | (make-request "application/transit+json;verbose"))) 38 | 39 | (defn msgpack-request [] 40 | (make-request "application/transit+msgpack")) 41 | 42 | (defn to-string 43 | [{:keys [body]}] 44 | (slurp body)) 45 | 46 | (defn to-bytes 47 | [{:keys [body]}] 48 | (let [out (java.io.ByteArrayOutputStream. 4096)] 49 | (io/copy body out) 50 | (into [] (map #(bit-and % 0x0ff) (.toByteArray out))))) 51 | 52 | (defn jsonify 53 | ([v] 54 | (jsonify v false)) 55 | ([v verbose?] 56 | (let [out (java.io.ByteArrayOutputStream. 4096) 57 | writer (transit/writer out (if verbose? :json-verbose :json))] 58 | (transit/write writer v) 59 | (slurp (java.io.ByteArrayInputStream. (.toByteArray out)))))) 60 | 61 | (defn packify [v] 62 | (let [out (java.io.ByteArrayOutputStream. 4096) 63 | writer (transit/writer out :msgpack)] 64 | (transit/write writer v) 65 | (into [] (map #(bit-and % 0x0ff) (.toByteArray out))))) 66 | 67 | (deftest fixed-sequences 68 | (testing "Fixed sequeces" 69 | (testing "JSON encoding" 70 | (are [json data] (= json (to-string ((test-resource data) (json-request)))) 71 | "[]" [] 72 | "[null]" [nil] 73 | "[true,false]" [true false] 74 | "[42,3.1415926]" [42 3.1415926] 75 | "[\"~:foo\",\"bar\",\"~$baz\"]" [:foo "bar" 'baz] 76 | "[\"~#list\",[[\"^0\",[]],[]]]" '(() []) 77 | "[\"~#list\",[1,2,3,4,5]]" (range 1 6) 78 | "[\"~:foo\",\"~:bar\",\"^0\",\"^1\",\"^0\",\"^1\"]" [:foo :bar :foo :bar :foo :bar])) 79 | (testing "JSON-verbose encoding" 80 | (are [json data] (= json (to-string ((test-resource data) (json-request :verbose)))) 81 | "[]" [] 82 | "[null]" [nil] 83 | "[true,false]" [true false] 84 | "[42,3.1415926]" [42 3.1415926] 85 | "[\"~:foo\",\"bar\",\"~$baz\"]" [:foo "bar" 'baz] 86 | "{\"~#list\":[{\"~#list\":[]},[]]}" '(() []) 87 | "{\"~#list\":[1,2,3,4,5]}" (range 1 6) 88 | "[\"~:foo\",\"~:bar\",\"~:foo\",\"~:bar\",\"~:foo\",\"~:bar\"]" [:foo :bar :foo :bar :foo :bar])) 89 | (testing "messagepack-verbose encoding" 90 | (are [msgpack data] (= msgpack (to-bytes ((test-resource data) (msgpack-request)))) 91 | [0x90] [] 92 | [0x91 0xc0] [nil] 93 | [0x92 0xc3 0xc2] [true false] 94 | [0x92 0x2a 0xcb 0x40 0x09 0x21 0xfb 0x4d 0x12 0xd8 0x4a] [42 3.1415926] 95 | [0x93 0xa5 0x7e 0x3a 0x66 0x6f 0x6f 0xa3 0x62 0x61 0x72 0xa5 0x7e 0x24 0x62 0x61 0x7a] [:foo "bar" 'baz] 96 | [0x92 0xa6 0x7e 0x23 0x6c 0x69 0x73 0x74 0x92 0x92 0xa2 0x5e 0x30 0x90 0x90] '(() []) 97 | [0x92 0xa6 0x7e 0x23 0x6c 0x69 0x73 0x74 0x95 0x01 0x02 0x03 0x04 0x05] (range 1 6) 98 | [0x96 0xa5 0x7e 0x3a 0x66 0x6f 0x6f 0xa5 0x7e 0x3a 0x62 0x61 0x72 0xa2 0x5e 0x30 0xa2 0x5e 0x31 0xa2 0x5e 0x30 0xa2 0x5e 0x31] [:foo :bar :foo :bar :foo :bar])))) 99 | 100 | (deftest fixed-maps 101 | (testing "Fixed sequeces" 102 | (testing "JSON encoding" 103 | (are [json data] (= json (to-string ((test-resource data) (json-request)))) 104 | "[\"^ \"]" {} 105 | "[\"^ \",\"foo\",1]" {"foo" 1} 106 | "[\"^ \",\"~:a\",[\"^ \"],\"~:b\",42]" {:a {} :b 42} 107 | )) 108 | (testing "JSON-verbose encoding" 109 | (are [json data] (= json (to-string ((test-resource data) (json-request :verbose)))) 110 | "{}" {} 111 | "{\"foo\":1}" {"foo" 1} 112 | "{\"~:a\":{},\"~:b\":42}" {:a {} :b 42} 113 | )) 114 | (testing "messagepack encoding" 115 | (are [msgpack data] (= msgpack (to-bytes ((test-resource data) (msgpack-request)))) 116 | [0x80] {} 117 | [0x81 0xa3 0x66 0x6f 0x6f 0x01] {"foo" 1} 118 | [0x82 0xa3 0x7e 0x3a 0x61 0x80 0xa3 0x7e 0x3a 0x62 0x2a] {:a {} :b 42})))) 119 | 120 | (defspec generated-sequences 121 | {:max-size 50} 122 | (prop/for-all [v gen/sequence-generator] 123 | (and (= (jsonify v) (to-string ((test-resource v) (json-request)))) 124 | (= (jsonify v :verbose) (to-string ((test-resource v) (json-request :verbose)))) 125 | (= (packify v) (to-bytes ((test-resource v) (msgpack-request))))))) 126 | 127 | (defspec generated-maps 128 | {:max-size 50} 129 | (prop/for-all [v gen/map-generator] 130 | (and (= (jsonify v) (to-string ((test-resource v) (json-request)))) 131 | (= (jsonify v :verbose) (to-string ((test-resource v) (json-request :verbose)))) 132 | (= (packify v) (to-bytes ((test-resource v) (msgpack-request))))))) 133 | 134 | (defmethod assert-expr 'invalid-buffer-error? 135 | [msg form] 136 | (let [ex-body "java.lang.String cannot be cast to java.lang.Number"] 137 | `(let [response# ~(second form) 138 | status# (:status response#) 139 | body# (:body response#)] 140 | (cond 141 | (not= 500 status#) 142 | (do-report {:type :fail 143 | :message ~msg 144 | :expected '(~'= 500 (:status ~(second form))) 145 | :actual status#}) 146 | (not= ~ex-body body#) 147 | (do-report {:type :fail 148 | :message ~msg 149 | :expected '(~'= ~ex-body (:body ~(second form))) 150 | :actual body#}) 151 | :default 152 | (do-report {:type :pass 153 | :message ~msg 154 | :expected '~form 155 | :actual response#}))))) 156 | 157 | (deftest liberator-transit-options 158 | (testing "allow-json-verbose?" 159 | (let [resource (fn [v] 160 | (liberator/resource 161 | :exists? {:liberator-transit {:allow-json-verbose? false}} 162 | :available-media-types ["application/transit+json"] 163 | :handle-ok (fn [context] v)))] 164 | (are [json data] (= json 165 | (to-string ((resource data) (json-request))) 166 | (to-string ((resource data) (json-request :verbose)))) 167 | "[\"~:foo\",\"^0\"]" [:foo :foo] 168 | "[\"^ \",\"foo\",1]" {"foo" 1}))) 169 | (testing "json-verbose-is-default?" 170 | (let [resource (fn [v] 171 | (liberator/resource 172 | :exists? {:liberator-transit {:json-verbose-is-default? true}} 173 | :available-media-types ["application/transit+json"] 174 | :handle-ok v))] 175 | (are [json data] (= json 176 | (to-string ((resource data) (json-request))) 177 | (to-string ((resource data) (json-request :verbose)))) 178 | "[\"~:foo\",\"~:foo\"]" [:foo :foo] 179 | "{\"foo\":1}" {"foo" 1}))) 180 | ; The test to see if the initial-buffer-size can be set is accomplished by 181 | ; setting something that is clearly invalid and will throw an exception. 182 | (testing "initial-buffer-size" 183 | (let [resource (fn [v] 184 | (liberator/resource 185 | :exists? {:liberator-transit {:initial-buffer-size "forty-two"}} 186 | :available-media-types ["application/transit+json" 187 | "application/transit+msgpack"] 188 | :handle-ok v 189 | :handle-exception (fn [{:keys [exception]}] 190 | (.getMessage exception))))] 191 | (are [data request] (invalid-buffer-error? ((resource data) request)) 192 | ; test sequences 193 | [:foo :bar {:foo 42 :bar "42"}] (json-request) 194 | [:foo :bar {:foo 42 :bar "42"}] (json-request :verbose) 195 | [:foo :bar {:foo 42 :bar "42"}] (msgpack-request) 196 | ; test maps 197 | {:foo 42 :bar ["42" :foo :bar]} (json-request) 198 | {:foo 42 :bar ["42" :foo :bar]} (json-request :verbose) 199 | {:foo 42 :bar ["42" :foo :bar]} (msgpack-request))))) 200 | 201 | (deftest response-headers 202 | (are [media-type data request] (= {"Content-Type" media-type 203 | "Vary" "Accept"} 204 | (:headers ((test-resource data) request))) 205 | ; sequences 206 | "application/transit+json" [] (json-request) 207 | "application/transit+json" [] (json-request :verbose) 208 | "application/transit+json" [] (make-request) 209 | "application/transit+json" [] (make-request "*/*") 210 | "application/transit+msgpack" [] (msgpack-request) 211 | ; maps 212 | "application/transit+json" {} (json-request) 213 | "application/transit+json" {} (json-request :verbose) 214 | "application/transit+json" {} (make-request) 215 | "application/transit+json" {} (make-request "*/*") 216 | "application/transit+msgpack" {} (msgpack-request))) 217 | 218 | (deftest as-response 219 | (testing "With options" 220 | (let [resource (fn [v] 221 | (liberator/resource 222 | :available-media-types ["application/transit+json"] 223 | :handle-ok v 224 | :as-response (lt/as-response {:json-verbose-is-default? true})))] 225 | (are [json data] (= json 226 | (to-string ((resource data) (json-request))) 227 | (to-string ((resource data) (json-request :verbose)))) 228 | "[\"~:foo\",\"~:foo\"]" [:foo :foo] 229 | "{\"foo\":1}" {"foo" 1}))) 230 | (testing "Will not override options" 231 | (let [resource (fn [v] 232 | (liberator/resource 233 | :exists? {:liberator-transit {:allow-json-verbose? false}} 234 | :available-media-types ["application/transit+json"] 235 | :handle-ok v 236 | :as-response (lt/as-response {:json-verbose-is-default? true})))] 237 | (are [json data] (= json 238 | (to-string ((resource data) (json-request))) 239 | (to-string ((resource data) (json-request :verbose)))) 240 | "[\"~:foo\",\"^0\"]" [:foo :foo] 241 | "[\"^ \",\"foo\",1]" {"foo" 1}))) 242 | (testing "As a decorator" 243 | (let [resource (fn [v] 244 | (liberator/resource 245 | :available-media-types ["application/transit+json" 246 | "application/transit+msgpack"] 247 | :handle-ok v 248 | :as-response (lt/as-response {:json-verbose-is-default? true} 249 | (fn [d c] 250 | (if (= d [:foo]) 251 | (rep/as-response [:bar] c) 252 | (rep/as-response d c))))))] 253 | (are [json data] (= json 254 | (to-string ((resource data) (json-request))) 255 | (to-string ((resource data) (json-request :verbose)))) 256 | "[\"~:foo\",\"~:foo\"]" [:foo :foo] 257 | "{\"foo\":1}" {"foo" 1} 258 | "[\"~:bar\"]" [:foo]) 259 | (are [msgpack data] (= msgpack (to-bytes ((resource data) (msgpack-request)))) 260 | [0x92 0xa5 0x7e 0x3a 0x66 0x6f 0x6f 0xa2 0x5e 0x30] [:foo :foo] 261 | [0x81 0xa3 0x66 0x6f 0x6f 0x01] {"foo" 1} 262 | [0x91 0xa5 0x7e 0x3a 0x62 0x61 0x72] [:foo])))) 263 | 264 | (defrecord Point [x y]) 265 | (defrecord Circle [centre radius]) 266 | 267 | (deftest handlers 268 | (let [round-trip (fn [type] 269 | (fn [{in :body}] 270 | (let [reader (transit/reader in type {:handlers (transit/record-read-handlers Point Circle)})] 271 | (transit/read reader))))] 272 | (testing "With no handler" 273 | (let [resource (fn [v] 274 | (liberator/resource 275 | :available-media-types ["application/transit+json" 276 | "application/transit+msgpack"] 277 | :handle-ok v))] 278 | (are [out in] (= out ((round-trip :json) ((resource in) (json-request)))) 279 | [{:x 42 :y 73}] [(Point. 42 73)] 280 | {:p {:x 42 :y 73}} {:p (Point. 42 73)} 281 | [{:radius 1 :centre {:x 0 :y 0}}] [(Circle. (Point. 0 0) 1)]) 282 | (are [out in] (= out ((round-trip :json-verbose) ((resource in) (json-request :verbose)))) 283 | [{:x 42 :y 73}] [(Point. 42 73)] 284 | {:p {:x 42 :y 73}} {:p (Point. 42 73)} 285 | [{:radius 1 :centre {:x 0 :y 0}}] [(Circle. (Point. 0 0) 1)]) 286 | (are [out in] (= out ((round-trip :msgpack) ((resource in) (msgpack-request)))) 287 | [{:x 42 :y 73}] [(Point. 42 73)] 288 | {:p {:x 42 :y 73}} {:p (Point. 42 73)} 289 | [{:radius 1 :centre {:x 0 :y 0}}] [(Circle. (Point. 0 0) 1)]))) 290 | (testing "With point handler" 291 | (let [resource (fn [v] 292 | (liberator/resource 293 | :available-media-types ["application/transit+json" 294 | "application/transit+msgpack"] 295 | :handle-ok v 296 | :as-response (lt/as-response {:handlers (transit/record-write-handlers Point)})))] 297 | (are [out in] (= out ((round-trip :json) ((resource in) (json-request)))) 298 | [(Point. 42 73)] [(Point. 42 73)] 299 | {:p (Point. 42 73)} {:p (Point. 42 73)} 300 | [{:radius 1 :centre (Point. 0 0)}] [(Circle. (Point. 0 0) 1)]) 301 | (are [out in] (= out ((round-trip :json-verbose) ((resource in) (json-request :verbose)))) 302 | [(Point. 42 73)] [(Point. 42 73)] 303 | {:p (Point. 42 73)} {:p (Point. 42 73)} 304 | [{:radius 1 :centre (Point. 0 0)}] [(Circle. (Point. 0 0) 1)]) 305 | (are [out in] (= out ((round-trip :msgpack) ((resource in) (msgpack-request)))) 306 | [(Point. 42 73)] [(Point. 42 73)] 307 | {:p (Point. 42 73)} {:p (Point. 42 73)} 308 | [{:radius 1 :centre (Point. 0 0)}] [(Circle. (Point. 0 0) 1)]))) 309 | (testing "With point and circle handlers" 310 | (let [resource (fn [v] 311 | (liberator/resource 312 | :available-media-types ["application/transit+json" 313 | "application/transit+msgpack"] 314 | :handle-ok v 315 | :as-response (lt/as-response {:handlers (transit/record-write-handlers Circle Point)})))] 316 | (are [out in] (= out ((round-trip :json) ((resource in) (json-request)))) 317 | [(Point. 42 73)] [(Point. 42 73)] 318 | {:p (Point. 42 73)} {:p (Point. 42 73)} 319 | [(Circle. (Point. 0 0) 1)] [(Circle. (Point. 0 0) 1)]) 320 | (are [out in] (= out ((round-trip :json-verbose) ((resource in) (json-request :verbose)))) 321 | [(Point. 42 73)] [(Point. 42 73)] 322 | {:p (Point. 42 73)} {:p (Point. 42 73)} 323 | [(Circle. (Point. 0 0) 1)] [(Circle. (Point. 0 0) 1)]) 324 | (are [out in] (= out ((round-trip :msgpack) ((resource in) (msgpack-request)))) 325 | [(Point. 42 73)] [(Point. 42 73)] 326 | {:p (Point. 42 73)} {:p (Point. 42 73)} 327 | [(Circle. (Point. 0 0) 1)] [(Circle. (Point. 0 0) 1)]))))) 328 | --------------------------------------------------------------------------------