├── .gitignore ├── CHANGELOG.md ├── README.md ├── project.clj ├── resources ├── clojure.transit ├── clojurescript.transit └── test-output │ └── clojure.transit ├── src ├── clj │ └── outpace │ │ ├── schema_transit.clj │ │ └── schema_transit │ │ └── macros.clj └── cljs │ └── outpace │ └── schema_transit.cljs └── test ├── clj └── outpace │ ├── schema_transit │ └── generate_test_output.clj │ └── schema_transit_test.clj └── cljs └── outpace ├── schema_transit └── generate_test_output.cljs └── schema_transit_test.cljs /.gitignore: -------------------------------------------------------------------------------- 1 | /.repl/ 2 | /target/ 3 | /classes/ 4 | /checkouts/ 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.2 2 | * Update for schema 0.3.7 compatibility. Most importantly, the 3 | schema.core/Regex schema changed from being the js/RegExp class to being a 4 | reification of the Schema protocol, and this broke the serialization for 5 | schema.core/Regex. 6 | 7 | ## 0.2.1 8 | * Move clojure and clojurescript into dev dependencies. 9 | 10 | ## 0.2.0 11 | * Clojurescript implementation of transit serialization for schema. 12 | 13 | ## 0.1.0 14 | * Initial open source release 15 | * Clojure implementation of transit serialization for schema. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # schema-transit 2 | 3 | schema-transit is a library that glues together Prismatic's 4 | [Schema](http://github.com/prismatic/schema) library and Cognitect's 5 | [Transit](http://github.com/cognitect/transit-clj) library. 6 | 7 | Using schema-transit you can turn a schema into data. Once it is data you can 8 | send it over the wire, store it in a database, etc. 9 | 10 | The latest version of schema-transit is: 11 | 12 | [![Clojars Project](http://clojars.org/com.outpace/schema-transit/latest-version.svg)](http://clojars.org/com.outpace/schema-transit) 13 | 14 | ## Usage 15 | 16 | The schema-transit cross-platform read/write handlers are defined as 17 | `outpace.schema-transit/cross-platform-read-handlers` and 18 | `outpace.schema-transit/cross-platform-write-handlers`, respectively. 19 | 20 | The platform specific read/write handlers are defined as 21 | `outpace.schema-transit/read-handlers` and 22 | `outpace.schema-transit/write-handlers`, respectively 23 | 24 | You can use these like you would any other transit handlers: 25 | 26 | ```clojure 27 | (require '[cognitect.transit :as t]) 28 | (require '[outpace.schema-transit :as st]) 29 | 30 | (defn transit-write [obj out] 31 | (t/write (t/writer out 32 | :json-verbose 33 | {:handlers st/write-handlers}) 34 | obj)) 35 | 36 | (defn transit-read [in] 37 | (t/read (t/reader in 38 | :json-verbose 39 | {:handlers st/read-handlers}))) 40 | ``` 41 | 42 | ## Approach 43 | 44 | Schemas can be divided into two sets: cross-platform and platform specific. 45 | 46 | The cross-platform schemas can be serialized, deserialized, and transmitted 47 | between Clojure and ClojureScript. The following leaf schemas are currently 48 | considered cross-platform: s/Any, s/Int, s/Keyword, s/Symbol, s/String, s/Bool, 49 | s/Num, s/Regex, s/Inst, s/Uuid. The following composite schemas are 50 | cross-platform if their components are also cross-platform: s/eq, s/maybe, 51 | s/named, s/either, s/both, maps, sets, vectors, s/required-key, s/optional-key, 52 | s/map-entry, s/one, function schema, and s/pair. 53 | 54 | The platform specific schemas include the cross-platform schemas, and additional 55 | schemas that can safely be serialized/deserialized but cannot be transmitted 56 | between Clojure and ClojureScript. Clojure's platform specific schemas are 57 | primitive schemas, a compiled regular expression, and a Java Class. In the case 58 | of a Java class it is serialized to its fully qualified class name. When 59 | deserialized a Java class must exist and have already been initialized; 60 | schema-transit finds classes using Class/forName but in a way that will *not* 61 | initialize a class. On the ClojureScript side, the only platform specific 62 | schema is a regular expression. 63 | 64 | ## License 65 | 66 | Copyright © Outpace Systems, Inc. 67 | 68 | Released under the Apache License, Version 2.0 69 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject com.outpace/schema-transit "0.2.4-SNAPSHOT" 2 | :description "A library for serializing Prismatic Schema definitions with 3 | Transit." 4 | :url "http://github.com/outpace/schema-transit" 5 | :license {:name "Apache License, Version 2.0" 6 | :url "http://www.apache.org/licenses/LICENSE-2.0.html"} 7 | :deploy-repositories [["releases" :clojars]] 8 | :dependencies [[com.cognitect/transit-clj "0.8.259"] 9 | [com.cognitect/transit-cljs "0.8.192"] 10 | [prismatic/schema "0.3.7"]] 11 | :plugins [[com.cemerick/clojurescript.test "0.3.3"] 12 | [lein-cljsbuild "1.0.3"]] 13 | :source-paths ["src/clj" "src/cljs"] 14 | :test-paths ["test/clj"] 15 | :cljsbuild {:test-commands {"unit" ["node" :node-runner 16 | "this.literal_js_was_evaluated=true" 17 | "target/unit-test.js"]} 18 | :builds 19 | {:generate {:source-paths ["src/clj" "src/cljs" "test/cljs"] 20 | :compiler {:output-to "target/generate.js" 21 | :target :nodejs 22 | :optimizations :simple}} 23 | :dev {:source-paths ["src/clj" "src/cljs"] 24 | :compiler {:output-to "target/main.js" 25 | :target :nodejs 26 | :optimizations :simple}} 27 | :test {:source-paths ["src/clj" "test/clj" 28 | "src/cljs" "test/cljs"] 29 | :compiler {:output-to "target/unit-test.js" 30 | :optimizations :simple}}}} 31 | :profiles {:dev {:dependencies [[com.cemerick/piggieback "0.1.3"] 32 | [org.clojure/clojure "1.6.0"] 33 | [org.clojure/clojurescript "0.0-2371"]] 34 | :repl-options {:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}}}) 35 | -------------------------------------------------------------------------------- /resources/clojure.transit: -------------------------------------------------------------------------------- 1 | {"~#cmap":["~:enum",{"~#schema.core.EnumSchema":{"~:vs":{"~#set":["second","first"]}}},"~:num",{"~#outpace.schema-transit.Num":"outpace.schema-transit.Num"},"~:fn",{"~#schema.core.FnSchema":{"~:output-schema":{"~#outpace.schema-transit.String":"outpace.schema-transit.String"},"~:input-schemas":{"~#list":[[{"~#outpace.schema-transit.String":"outpace.schema-transit.String"}]]}}},"~:pred",{"~#outpace.schema-transit.Int":"outpace.schema-transit.Int"},"~:isa",{"~#schema.core.Isa":{"~:parent":"~:foo/bar","~:h":null}},"~:inst",{"~#outpace.schema-transit.Inst":"outpace.schema-transit.Inst"},"~:eq",{"~#schema.core.EqSchema":{"~:v":"foo"}},{"~#schema.core.OptionalKey":{"~:k":"~:maybe"}},{"~#schema.core.Maybe":{"~:schema":{"~#outpace.schema-transit.Num":"outpace.schema-transit.Num"}}},"~:int",{"~#outpace.schema-transit.Int":"outpace.schema-transit.Int"},"~:pair",[{"~#schema.core.One":{"~:name":"string","~:schema":{"~#outpace.schema-transit.String":"outpace.schema-transit.String"},"~:optional?":false}},{"~#schema.core.One":{"~:schema":{"~#outpace.schema-transit.Int":"outpace.schema-transit.Int"},"~:name":"int","~:optional?":false}}],"~:symbol",{"~#outpace.schema-transit.Symbol":"outpace.schema-transit.Symbol"},{"~#schema.core.RequiredKey":{"~:k":"str"}},{"~#outpace.schema-transit.String":"outpace.schema-transit.String"},"~:keyword",{"~#outpace.schema-transit.Keyword":"outpace.schema-transit.Keyword"},"~:both",{"~#schema.core.Both":{"~:schemas":{"~#list":[{"~#outpace.schema-transit.String":"outpace.schema-transit.String"},{"~#outpace.schema-transit.Int":"outpace.schema-transit.Int"}]}}},"~:map-entry",{"~#schema.core.MapEntry":{"~:kspec":{"~#outpace.schema-transit.String":"outpace.schema-transit.String"},"~:val-schema":{"~#outpace.schema-transit.Int":"outpace.schema-transit.Int"}}},"~:regex",{"~#outpace.schema-transit.Regex":"outpace.schema-transit.Regex"},"~:named",{"~#schema.core.NamedSchema":{"~:name":"string","~:schema":{"~#outpace.schema-transit.String":"outpace.schema-transit.String"}}},"~:bool",{"~#outpace.schema-transit.Bool":"outpace.schema-transit.Bool"},"~:uuid",{"~#outpace.schema-transit.Uuid":"outpace.schema-transit.Uuid"},"~:seq",[{"~#schema.core.One":{"~:name":"string","~:schema":{"~#outpace.schema-transit.String":"outpace.schema-transit.String"},"~:optional?":false}},{"~#schema.core.One":{"~:optional?":true,"~:schema":{"~#outpace.schema-transit.Int":"outpace.schema-transit.Int"},"~:name":"int"}}],{"~#schema.core.AnythingSchema":{"~:_":null}},{"~#schema.core.AnythingSchema":{"~:_":null}},"~:either",{"~#schema.core.Either":{"~:schemas":{"~#list":[{"~#outpace.schema-transit.String":"outpace.schema-transit.String"},{"~#outpace.schema-transit.Int":"outpace.schema-transit.Int"}]}}}]} 2 | -------------------------------------------------------------------------------- /resources/clojurescript.transit: -------------------------------------------------------------------------------- 1 | {"~#cmap":["~:enum",{"~#schema.core.EnumSchema":{"~:vs":{"~#set":["first","second"]}}},"~:num",{"~#outpace.schema-transit.Num":"outpace.schema-transit.Num"},"~:fn",{"~#schema.core.FnSchema":{"~:output-schema":{"~#outpace.schema-transit.String":"outpace.schema-transit.String"},"~:input-schemas":{"~#list":[[{"~#outpace.schema-transit.String":"outpace.schema-transit.String"}]]}}},"~:pred",{"~#outpace.schema-transit.Int":"outpace.schema-transit.Int"},"~:isa",{"~#schema.core.Isa":{"~:h":null,"~:parent":"~:foo/bar"}},"~:inst",{"~#outpace.schema-transit.Inst":"outpace.schema-transit.Inst"},"~:eq",{"~#schema.core.EqSchema":{"~:v":"foo"}},"~:int",{"~#outpace.schema-transit.Int":"outpace.schema-transit.Int"},{"~#schema.core.OptionalKey":{"~:k":"~:maybe"}},{"~#schema.core.Maybe":{"~:schema":{"~#outpace.schema-transit.Num":"outpace.schema-transit.Num"}}},{"~#schema.core.AnythingSchema":{"~:_":null}},{"~#schema.core.AnythingSchema":{"~:_":null}},"~:pair",[{"~#schema.core.One":{"~:schema":{"~#outpace.schema-transit.String":"outpace.schema-transit.String"},"~:optional?":false,"~:name":"string"}},{"~#schema.core.One":{"~:schema":{"~#outpace.schema-transit.Int":"outpace.schema-transit.Int"},"~:optional?":false,"~:name":"int"}}],{"~#schema.core.RequiredKey":{"~:k":"str"}},{"~#outpace.schema-transit.String":"outpace.schema-transit.String"},"~:symbol",{"~#outpace.schema-transit.Symbol":"outpace.schema-transit.Symbol"},"~:keyword",{"~#outpace.schema-transit.Keyword":"outpace.schema-transit.Keyword"},"~:both",{"~#schema.core.Both":{"~:schemas":{"~#list":[{"~#outpace.schema-transit.String":"outpace.schema-transit.String"},{"~#outpace.schema-transit.Int":"outpace.schema-transit.Int"}]}}},"~:map-entry",{"~#schema.core.MapEntry":{"~:kspec":{"~#outpace.schema-transit.String":"outpace.schema-transit.String"},"~:val-schema":{"~#outpace.schema-transit.Int":"outpace.schema-transit.Int"}}},"~:regex",{"~#outpace.schema-transit.Regex":"outpace.schema-transit.Regex"},"~:named",{"~#schema.core.NamedSchema":{"~:schema":{"~#outpace.schema-transit.String":"outpace.schema-transit.String"},"~:name":"string"}},"~:bool",{"~#outpace.schema-transit.Bool":"outpace.schema-transit.Bool"},"~:uuid",{"~#outpace.schema-transit.Uuid":"outpace.schema-transit.Uuid"},"~:seq",[{"~#schema.core.One":{"~:schema":{"~#outpace.schema-transit.String":"outpace.schema-transit.String"},"~:optional?":false,"~:name":"string"}},{"~#schema.core.One":{"~:schema":{"~#outpace.schema-transit.Int":"outpace.schema-transit.Int"},"~:optional?":true,"~:name":"int"}}],"~:either",{"~#schema.core.Either":{"~:schemas":{"~#list":[{"~#outpace.schema-transit.String":"outpace.schema-transit.String"},{"~#outpace.schema-transit.Int":"outpace.schema-transit.Int"}]}}}]} 2 | -------------------------------------------------------------------------------- /resources/test-output/clojure.transit: -------------------------------------------------------------------------------- 1 | ["~#cmap",["~:a",["~#outpace.schema-transit.Class","java.lang.String"],"~:b",["~#schema.core.Maybe",["^ ","~:schema",["^1","java.lang.Number"]]],["~#schema.core.AnythingSchema",["^ ","~:_",null]],["^4",["^ ","~:_",null]]]] 2 | -------------------------------------------------------------------------------- /src/clj/outpace/schema_transit.clj: -------------------------------------------------------------------------------- 1 | (ns outpace.schema-transit 2 | (:require [cognitect.transit :refer [read-handler 3 | record-read-handlers 4 | record-write-handlers 5 | write-handler]] 6 | [outpace.schema-transit.macros] 7 | [schema.core :as s]) 8 | (:import (clojure.lang RT) 9 | (java.util Date UUID) 10 | (java.util.regex Pattern))) 11 | 12 | (defn tag [name] 13 | (str "outpace.schema-transit." name)) 14 | 15 | (def cross-platform-records 16 | [schema.core.AnythingSchema 17 | schema.core.EqSchema 18 | schema.core.EnumSchema 19 | schema.core.Predicate 20 | schema.core.Maybe 21 | schema.core.NamedSchema 22 | schema.core.Either 23 | schema.core.Both 24 | schema.core.RequiredKey 25 | schema.core.OptionalKey 26 | schema.core.MapEntry 27 | schema.core.One 28 | schema.core.FnSchema 29 | schema.core.Isa]) 30 | 31 | (def platform-specific-records 32 | [schema.core.Record]) 33 | 34 | (defmacro assoc-primitive-write [m primitive] 35 | `(assoc ~m 36 | (class ~(symbol primitive)) 37 | (write-handler (tag ~(str primitive)) (tag ~(str primitive))))) 38 | 39 | (defmacro assoc-primitive-read [m primitive] 40 | `(assoc ~m 41 | (tag ~(str primitive)) 42 | (read-handler (constantly ~(symbol primitive))))) 43 | 44 | (def primitives '[double float long int short char byte boolean 45 | doubles floats longs ints shorts chars bytes booleans]) 46 | 47 | (defmacro assoc-primitives [m op] 48 | (list* '-> m 49 | (for [primitive primitives] 50 | (list op primitive)))) 51 | 52 | (defn pred-tag [obj] 53 | (condp = (:p? obj) 54 | integer? (tag "Int") 55 | keyword? (tag "Keyword") 56 | symbol? (tag "Symbol") 57 | string? (tag "String") 58 | nil)) 59 | 60 | (defn java-type-tag [obj] 61 | (condp = obj 62 | java.lang.String (tag "String") 63 | java.lang.Boolean (tag "Bool") 64 | java.lang.Number (tag "Num") 65 | java.util.regex.Pattern (tag "Regex") 66 | java.util.Date (tag "Inst") 67 | java.util.UUID (tag "Uuid") 68 | nil)) 69 | 70 | (def 71 | ^{:doc "Transit write handlers for the subset of schemas that can be 72 | transmitted between Clojure and ClojureScript."} 73 | cross-platform-write-handlers 74 | (-> (apply record-write-handlers cross-platform-records) 75 | (assoc schema.core.Predicate (write-handler pred-tag pred-tag) 76 | Class (write-handler java-type-tag java-type-tag)))) 77 | 78 | (def 79 | ^{:doc "Transit write handlers for the set of schemas that can be 80 | serialized with Transit from a Clojure process."} 81 | write-handlers 82 | (-> (merge cross-platform-write-handlers 83 | (apply record-write-handlers platform-specific-records)) 84 | (assoc-primitives assoc-primitive-write) 85 | (assoc Pattern (write-handler (tag "Pattern") str) 86 | Class (write-handler #(or (java-type-tag %) (tag "Class")) 87 | #(or (java-type-tag %) (.getName ^Class %)))))) 88 | 89 | (def 90 | ^{:doc "Transit read handlers for the subset of schemas that can be 91 | transmitted between Clojure and ClojureScript."} 92 | cross-platform-read-handlers 93 | (-> (apply record-read-handlers cross-platform-records) 94 | (assoc (tag "Int") (read-handler (constantly s/Int)) 95 | (tag "Keyword") (read-handler (constantly s/Keyword)) 96 | (tag "Symbol") (read-handler (constantly s/Symbol)) 97 | (tag "String") (read-handler (constantly s/Str)) 98 | (tag "Bool") (read-handler (constantly s/Bool)) 99 | (tag "Num") (read-handler (constantly s/Num)) 100 | (tag "Regex") (read-handler (constantly s/Regex)) 101 | (tag "Inst") (read-handler (constantly s/Inst)) 102 | (tag "Uuid") (read-handler (constantly s/Uuid))))) 103 | 104 | (def 105 | ^{:doc "Transit read handlers for the set of schemas that can be 106 | deserialized with Transit by a Clojure process."} 107 | read-handlers 108 | (-> (merge cross-platform-read-handlers 109 | (apply record-read-handlers platform-specific-records)) 110 | (assoc-primitives assoc-primitive-read) 111 | (assoc (tag "Pattern") (read-handler #(Pattern/compile %)) 112 | (tag "Class") (read-handler #(Class/forName % false (RT/baseLoader)))))) 113 | -------------------------------------------------------------------------------- /src/clj/outpace/schema_transit/macros.clj: -------------------------------------------------------------------------------- 1 | (ns outpace.schema-transit.macros) 2 | 3 | (defmacro schema-tag [name] 4 | `(str "schema.core." ~name)) 5 | 6 | (defmacro record-write-handlers [& records] 7 | `(-> {} 8 | ~@(for [record records] 9 | `(assoc ~(symbol (str "schema.core/" record)) 10 | (cognitect.transit/write-handler 11 | (fn [x#] 12 | ~(schema-tag (str record))) 13 | (fn [x#] 14 | (into {} x#))))))) 15 | 16 | (defmacro record-read-handlers [& records] 17 | `(-> {} 18 | ~@(for [record records] 19 | `(assoc ~(schema-tag (str record)) 20 | (cognitect.transit/read-handler 21 | (fn [x#] 22 | (~(symbol (str "schema.core/map->" record ".")) x#))))))) 23 | -------------------------------------------------------------------------------- /src/cljs/outpace/schema_transit.cljs: -------------------------------------------------------------------------------- 1 | (ns outpace.schema-transit 2 | (:require-macros [outpace.schema-transit.macros :refer [record-read-handlers record-write-handlers]]) 3 | (:require [cognitect.transit :refer [read-handler write-handler]] 4 | [schema.core :as s])) 5 | 6 | (defn tag [name] 7 | (str "outpace.schema-transit." name)) 8 | 9 | (defn tag-fn [name] 10 | (fn [_] 11 | (tag name))) 12 | 13 | (defn pred-tag [obj] 14 | (condp = (:p? obj) 15 | integer? (tag "Int") 16 | keyword? (tag "Keyword") 17 | symbol? (tag "Symbol") 18 | string? (tag "String") 19 | nil)) 20 | 21 | (def js-type-tag 22 | {s/Bool (tag "Bool") 23 | s/Num (tag "Num") 24 | s/Regex (tag "Regex") 25 | s/Inst (tag "Inst") 26 | s/Uuid (tag "Uuid")}) 27 | 28 | (def 29 | ^{:doc "Transit write handlers for the subset of schemas that can be 30 | transmitted between Clojure and ClojureScript."} 31 | cross-platform-write-handlers 32 | (assoc (record-write-handlers AnythingSchema EqSchema EnumSchema Predicate 33 | Maybe NamedSchema Either Both RequiredKey 34 | OptionalKey MapEntry One FnSchema Isa) 35 | schema.core.Predicate (write-handler pred-tag pred-tag) 36 | js/Function (write-handler js-type-tag js-type-tag) 37 | (type s/Regex) (write-handler js-type-tag js-type-tag))) 38 | 39 | (def 40 | ^{:doc "Transit write handlers for the set of schemas that can be 41 | serialized with Transit from a ClojureScript process."} 42 | write-handlers 43 | (assoc cross-platform-write-handlers 44 | js/RegExp (write-handler (tag-fn "RegexInstance") 45 | (fn [obj] 46 | (let [obj (str obj) 47 | len (count obj)] 48 | (subs obj 1 (dec len))))))) 49 | 50 | (def 51 | ^{:doc "Transit read handlers for the subset of schemas that can be 52 | transmitted between Clojure and ClojureScript."} 53 | cross-platform-read-handlers 54 | (assoc (record-read-handlers AnythingSchema EqSchema EnumSchema Predicate 55 | Maybe NamedSchema Either Both RequiredKey 56 | OptionalKey MapEntry One FnSchema Isa) 57 | (tag "Int") (read-handler (constantly s/Int)) 58 | (tag "Keyword") (read-handler (constantly s/Keyword)) 59 | (tag "Symbol") (read-handler (constantly s/Symbol)) 60 | (tag "String") (read-handler (constantly s/Str)) 61 | (tag "Bool") (read-handler (constantly s/Bool)) 62 | (tag "Num") (read-handler (constantly s/Num)) 63 | (tag "Regex") (read-handler (constantly s/Regex)) 64 | (tag "Inst") (read-handler (constantly s/Inst)) 65 | (tag "Uuid") (read-handler (constantly s/Uuid)))) 66 | 67 | (def 68 | ^{:doc "Transit read handlers for the set of schemas that can be 69 | deserialized with Transit by a ClojureScript process."} 70 | read-handlers 71 | (assoc cross-platform-read-handlers 72 | (tag "RegexInstance") (read-handler re-pattern))) 73 | -------------------------------------------------------------------------------- /test/clj/outpace/schema_transit/generate_test_output.clj: -------------------------------------------------------------------------------- 1 | (ns outpace.schema-transit.generate-test-output 2 | (:require [cognitect.transit :as tr] 3 | [outpace.schema-transit :as st] 4 | [schema.core :as s]) 5 | (:import (java.io ByteArrayOutputStream))) 6 | 7 | (defn -main [] 8 | (let [stream (ByteArrayOutputStream.) 9 | writer (tr/writer stream :json-verbose {:handlers st/cross-platform-write-handlers})] 10 | (tr/write writer {:int s/Int 11 | :keyword s/Keyword 12 | :symbol s/Symbol 13 | (s/required-key "str") s/Str 14 | (s/optional-key :maybe) (s/maybe s/Num) 15 | :eq (s/eq "foo") 16 | :enum (s/enum "first" "second") 17 | :pred (s/pred integer?) 18 | :named (s/named s/Str "string") 19 | :either (s/either s/Str s/Int) 20 | :both (s/both s/Str s/Int) 21 | :map-entry (s/map-entry s/Str s/Int) 22 | :seq [(s/one s/Str "string") (s/optional s/Int "int")] 23 | :pair (s/pair s/Str "string" s/Int "int") 24 | :isa (s/isa :foo/bar) 25 | :bool s/Bool 26 | :num s/Num 27 | :regex s/Regex 28 | :inst s/Inst 29 | :uuid s/Uuid 30 | :fn (s/make-fn-schema s/Str [[s/Str]]) 31 | s/Any s/Any}) 32 | (println (.toString stream)))) 33 | -------------------------------------------------------------------------------- /test/clj/outpace/schema_transit_test.clj: -------------------------------------------------------------------------------- 1 | (ns outpace.schema-transit-test 2 | (:require [clojure.java.io :as io] 3 | [clojure.test :refer :all] 4 | [cognitect.transit :as t] 5 | [outpace.schema-transit :as st] 6 | [schema.core :as s]) 7 | (:import (java.io ByteArrayInputStream ByteArrayOutputStream) 8 | (java.util.regex Pattern))) 9 | 10 | (defn transit-write [obj] 11 | (let [out (ByteArrayOutputStream.)] 12 | (t/write (t/writer out 13 | :json-verbose 14 | {:handlers st/write-handlers}) 15 | obj) 16 | (.toByteArray out))) 17 | 18 | (defn transit-read 19 | ([in] 20 | (transit-read in st/read-handlers)) 21 | ([in read-handlers] 22 | (t/read (t/reader in 23 | :json-verbose 24 | {:handlers read-handlers})))) 25 | 26 | (defn roundtrip [obj] 27 | (transit-read (ByteArrayInputStream. (transit-write obj)))) 28 | 29 | (defn roundtrip? [obj] 30 | (let [t (roundtrip obj)] 31 | (is (= obj t) t))) 32 | 33 | (deftest test-cross-platform-schemas 34 | (testing "leaf schemas" 35 | (is (roundtrip? s/Any)) 36 | (is (roundtrip? (s/eq "foo"))) 37 | (derive ::child ::parent) 38 | (is (roundtrip? (s/isa ::parent))) 39 | (is (roundtrip? (s/enum 'foo 'bar))) 40 | (is (roundtrip? (s/pred integer? 'integer?))) 41 | (is (roundtrip? (s/pred keyword? 'keyword?))) 42 | (is (roundtrip? (s/pred symbol? 'symbol?))) 43 | (is (= (roundtrip (s/pred string?)) s/Str)) 44 | (is (thrown? Exception (transit-write (s/pred odd? 'odd?) 45 | (ByteArrayOutputStream.)))) 46 | (is (roundtrip? s/Int)) 47 | (is (roundtrip? s/Keyword)) 48 | (is (roundtrip? s/Symbol)) 49 | (is (roundtrip? s/Str)) 50 | (is (roundtrip? s/Bool)) 51 | (is (roundtrip? s/Num)) 52 | (is (roundtrip? s/Regex)) 53 | (is (roundtrip? s/Inst)) 54 | (is (roundtrip? s/Uuid))) 55 | (testing "composite schemas" 56 | (is (roundtrip? (s/maybe s/Str))) 57 | (is (roundtrip? (s/named s/Str "foo"))) 58 | (is (roundtrip? (s/either s/Str s/Int))) 59 | (is (roundtrip? (s/both s/Str s/Int)))) 60 | (testing "map schemas" 61 | (is (roundtrip? (s/required-key "foo"))) 62 | (is (roundtrip? (s/optional-key :foo))) 63 | (is (roundtrip? (s/map-entry :foo s/Int)))) 64 | (testing "sequence schemas" 65 | (is (roundtrip? (s/one s/Str "string"))) 66 | (is (roundtrip? (s/optional s/Str "string"))) 67 | (is (roundtrip? (s/pair s/Int "x" s/Int "y")))) 68 | (testing "function schema" 69 | (is (roundtrip? (s/make-fn-schema s/Str [[s/Str]])))) 70 | (testing "actual output" 71 | (is (= {:int s/Int 72 | :keyword s/Keyword 73 | :symbol s/Symbol 74 | (s/required-key "str") s/Str 75 | (s/optional-key :maybe) (s/maybe s/Num) 76 | :eq (s/eq "foo") 77 | :enum (s/enum "first" "second") 78 | :pred (s/pred integer? 'integer?) 79 | :named (s/named s/Str "string") 80 | :either (s/either s/Str s/Int) 81 | :both (s/both s/Str s/Int) 82 | :map-entry (s/map-entry s/Str s/Int) 83 | :seq [(s/one s/Str "string") (s/optional s/Int "int")] 84 | :pair (s/pair s/Str "string" s/Int "int") 85 | :isa (s/isa :foo/bar) 86 | :bool s/Bool 87 | :num s/Num 88 | :regex s/Regex 89 | :inst s/Inst 90 | :uuid s/Uuid 91 | :fn (s/make-fn-schema s/Str [[s/Str]]) 92 | s/Any s/Any} 93 | (transit-read (io/input-stream (io/resource "clojurescript.transit")) 94 | st/cross-platform-read-handlers))))) 95 | 96 | (deftest test-record-schemas 97 | (is (roundtrip? (s/record schema.core.Record {:klass Class :schema Class})))) 98 | 99 | (deftest test-platform-specific-schemas 100 | (testing "primitive schemas" 101 | (is (roundtrip? double)) 102 | (is (roundtrip? float)) 103 | (is (roundtrip? long)) 104 | (is (roundtrip? int)) 105 | (is (roundtrip? short)) 106 | (is (roundtrip? char)) 107 | (is (roundtrip? byte)) 108 | (is (roundtrip? boolean))) 109 | (testing "primitive array schemas" 110 | (is (roundtrip? doubles)) 111 | (is (roundtrip? floats)) 112 | (is (roundtrip? longs)) 113 | (is (roundtrip? ints)) 114 | (is (roundtrip? shorts)) 115 | (is (roundtrip? chars)) 116 | (is (roundtrip? bytes)) 117 | (is (roundtrip? booleans))) 118 | (testing "class schema" 119 | (is (roundtrip? Exception))) 120 | (testing "pattern instance" 121 | (let [result (roundtrip #"foo")] 122 | (is (instance? Pattern result)) 123 | (is (= "foo" (str result)))))) 124 | -------------------------------------------------------------------------------- /test/cljs/outpace/schema_transit/generate_test_output.cljs: -------------------------------------------------------------------------------- 1 | (ns outpace.schema-transit.generate-test-output 2 | (:require [cljs.nodejs :as nodejs] 3 | [cognitect.transit :as tr] 4 | [outpace.schema-transit :as st] 5 | [schema.core :as s])) 6 | 7 | (defn -main [] 8 | (let [w (tr/writer :json-verbose 9 | {:handlers st/cross-platform-write-handlers})] 10 | (println (tr/write w 11 | {:int s/Int 12 | :keyword s/Keyword 13 | :symbol s/Symbol 14 | (s/required-key "str") s/Str 15 | (s/optional-key :maybe) (s/maybe s/Num) 16 | :eq (s/eq "foo") 17 | :enum (s/enum "first" "second") 18 | :pred (s/pred integer?) 19 | :named (s/named s/Str "string") 20 | :either (s/either s/Str s/Int) 21 | :both (s/both s/Str s/Int) 22 | :map-entry (s/map-entry s/Str s/Int) 23 | :seq [(s/one s/Str "string") (s/optional s/Int "int")] 24 | :pair (s/pair s/Str "string" s/Int "int") 25 | :isa (s/isa :foo/bar) 26 | :bool s/Bool 27 | :num s/Num 28 | :regex s/Regex 29 | :inst s/Inst 30 | :uuid s/Uuid 31 | :fn (s/make-fn-schema s/Str [[s/Str]]) 32 | s/Any s/Any})))) 33 | 34 | (nodejs/enable-util-print!) 35 | (set! *main-cli-fn* -main) 36 | -------------------------------------------------------------------------------- /test/cljs/outpace/schema_transit_test.cljs: -------------------------------------------------------------------------------- 1 | (ns outpace.schema-transit-test 2 | (:require-macros [cemerick.cljs.test :refer [deftest is testing]]) 3 | (:require [cemerick.cljs.test] 4 | [cljs.nodejs :as nodejs] 5 | [cognitect.transit :as t] 6 | [outpace.schema-transit :as st] 7 | [schema.core :as s])) 8 | 9 | (defn transit-write [obj] 10 | (t/write (t/writer :json-verbose 11 | {:handlers st/write-handlers}) 12 | obj)) 13 | 14 | (defn transit-read 15 | ([in] 16 | (transit-read in st/read-handlers)) 17 | ([in read-handlers] 18 | (t/read (t/reader :json-verbose 19 | {:handlers read-handlers}) 20 | in))) 21 | 22 | (defn roundtrip [obj] 23 | (transit-read (transit-write obj))) 24 | 25 | (defn roundtrip? [obj] 26 | (let [t (roundtrip obj)] 27 | (is (= obj t) t))) 28 | 29 | (deftest test-cross-platform-schemas 30 | (testing "leaf schemas" 31 | (is (roundtrip? s/Any)) 32 | (is (roundtrip? (s/eq "foo"))) 33 | (derive ::child ::parent) 34 | (is (roundtrip? (s/isa ::parent))) 35 | (is (roundtrip? (s/enum 'foo 'bar))) 36 | (is (roundtrip? (s/pred integer? 'integer?))) 37 | (is (roundtrip? (s/pred keyword? 'keyword?))) 38 | (is (roundtrip? (s/pred symbol? 'symbol?))) 39 | (is (= (roundtrip (s/pred string?)) s/Str)) 40 | (is (thrown? js/Error (transit-write (s/pred odd? 'odd?)))) 41 | (is (roundtrip? s/Int)) 42 | (is (roundtrip? s/Keyword)) 43 | (is (roundtrip? s/Symbol)) 44 | (is (roundtrip? s/Str)) 45 | (is (roundtrip? s/Bool)) 46 | (is (roundtrip? s/Num)) 47 | (is (roundtrip? s/Regex)) 48 | (is (roundtrip? s/Inst)) 49 | (is (roundtrip? s/Uuid))) 50 | (testing "composite schemas" 51 | (is (roundtrip? (s/maybe s/Str))) 52 | (is (roundtrip? (s/named s/Str "foo"))) 53 | (is (roundtrip? (s/either s/Str s/Int))) 54 | (is (roundtrip? (s/both s/Str s/Int)))) 55 | (testing "map schemas" 56 | (is (roundtrip? (s/required-key "foo"))) 57 | (is (roundtrip? (s/optional-key :foo))) 58 | (is (roundtrip? (s/map-entry :foo s/Int)))) 59 | (testing "sequence schemas" 60 | (is (roundtrip? (s/one s/Str "string"))) 61 | (is (roundtrip? (s/optional s/Str "string"))) 62 | (is (roundtrip? (s/pair s/Int "x" s/Int "y")))) 63 | (testing "function schema" 64 | (is (roundtrip? (s/make-fn-schema s/Str [[s/Str]])))) 65 | (testing "actual output" 66 | (let [fs (nodejs/require "fs")] 67 | (is (= {:int s/Int 68 | :keyword s/Keyword 69 | :symbol s/Symbol 70 | (s/required-key "str") s/Str 71 | (s/optional-key :maybe) (s/maybe s/Num) 72 | :eq (s/eq "foo") 73 | :enum (s/enum "first" "second") 74 | :pred (s/pred integer? 'integer?) 75 | :named (s/named s/Str "string") 76 | :either (s/either s/Str s/Int) 77 | :both (s/both s/Str s/Int) 78 | :map-entry (s/map-entry s/Str s/Int) 79 | :seq [(s/one s/Str "string") (s/optional s/Int "int")] 80 | :pair (s/pair s/Str "string" s/Int "int") 81 | :isa (s/isa :foo/bar) 82 | :bool s/Bool 83 | :num s/Num 84 | :regex s/Regex 85 | :inst s/Inst 86 | :uuid s/Uuid 87 | :fn (s/make-fn-schema s/Str [[s/Str]]) 88 | s/Any s/Any} 89 | (transit-read (.readFileSync fs "resources/clojure.transit" "utf-8") 90 | st/cross-platform-read-handlers)))))) 91 | 92 | (deftest test-platform-specific-schemas 93 | (let [result (roundtrip #"foo")] 94 | (is (instance? js/RegExp result)) 95 | (is (s/validate result "foo" )))) 96 | --------------------------------------------------------------------------------