Skip to content

Commit

Permalink
Nested schemas (#10)
Browse files Browse the repository at this point in the history
* Fix a lot of things related to nested schemas

* Update documentation

Fixes #7

* Update travis config

* do-key-values

* Tremendous refactoring

* Finish implementing one-field-of fields

* DOCS

* Fix omission in function renaming
  • Loading branch information
fisxoj authored Aug 10, 2019
1 parent ac900ff commit d9e4d6d
Show file tree
Hide file tree
Showing 26 changed files with 1,365 additions and 856 deletions.
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ env:
- ROSWELL_INSTALL_DIR=$HOME/.roswell
- COVERAGE_EXCLUDE=t
matrix:
- LISP=sbcl-bin COVERAGE=1
- LISP=sbcl-bin COVERALLS=true
- LISP=ccl-bin
- LISP=ecl
- LISP=allegro
Expand All @@ -29,8 +29,8 @@ matrix:
install:
# Install Roswell
- curl -L https://raw.githubusercontent.com/snmsts/roswell/release/scripts/install-for-ci.sh | sh
- ros install fisxoj/rove
- git clone https://github.com/fisxoj/validate ~/lisp/validate
- ros install fukamachi/rove
- ros install fisxoj/validate

cache:
directories:
Expand Down
108 changes: 105 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
:Source: `https://github.com/fisxoj/sanity-clause <https://github.com/fisxoj/sanity-clause>`_
:Docs: `https://fisxoj.github.io/sanity-clause/ <https://fisxoj.github.io/sanity-clause/>`_

There's no such thing as Sanity Clause
..
There's no such thing as Sanity Clause.

-- Groucho Marx


Expand All @@ -29,7 +32,7 @@ You can load these sorts of schemas from a file by writing them as sexps with ke
and then loading them using :function:`sanity-clause.loadable-schema:load` to load them.


Finally, you can also define class-based schemas using :class:`sanity-clause.metaclass:validated-metaclass` like::
Finally, you can also define class-based schemas using :class:`sanity-clause:validated-metaclass` like::

(defclass person ()
((favorite-dog :type symbol
Expand All @@ -43,6 +46,105 @@ Finally, you can also define class-based schemas using :class:`sanity-clause.met
(potato :type string
:initarg :potato
:required t))
(:metaclass sanity-clause.metaclass:validated-metaclass))
(:metaclass sanity-clause:validated-metaclass))

which will validate thier initargs when you instantiate them (**BUT NOT WHEN YOU SET SLOTS**). Hopefully, that will be added eventually, perhaps as an optional feature.


~~~~~~~
Example
~~~~~~~

``v2-info.json``::

{
"title": "Swagger Sample App",
"description": "This is a sample server Petstore server.",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0.1"
}


``example.lisp``::

;; load required libraries
(ql:quickload '(jonathan sanity-clause))

(defclass contact-object ()
((name :type string
:initarg :name
:documentation "The identifying name of the contact person/organization.")
(url :type string
:field-type :uri
:initarg :url
:documentation "The URL pointing to the contact information. MUST be in the format of a URL.")
(email :type string
:field-type :email
:initarg :email
:documentation "The email address of the contact person/organization. MUST be in the format of an email address."))
(:metaclass sanity-clause:validated-metaclass))


(defclass license-object ()
((name :type string
:initarg :name
:documentation "The license name used for the API.")
(url :type string
:field-type :uri
:initarg :url
:documentation "A URL to the license used for the API. MUST be in the format of a URL."))
(:metaclass sanity-clause:validated-metaclass))


(defclass info-object ()
((title :type string
:data-key "title"
:initarg :title
:required t
:documentation "The title of the application.")
(description :type string
:initarg :description
:documentation "A short description of the application. GFM syntax can be used for rich text representation.")
(terms-of-service :type string
:data-key "termsOfService"
:initarg :terms-of-service
:documentation "The Terms of Service for the API.")
(contact :type contact-object
:field-type :nested
:element-type contact-object
:initarg :contact
:documentation "The contact information for the exposed API.")
(license :type license-object
:field-type :nested
:element-type license-object
:initarg :license
:documentation "The license information for the exposed API.")
(version :type string
:initarg :version
:documentation "Provides the version of the application API (not to be confused with the specification version)."
:required t))
(:metaclass sanity-clause:validated-metaclass))

;;; Deserialize the json from the file into instances of these classes

(let ((v2-info (alexandria:read-file-into-string "v2-info.json")))
(sanity-clause:load (find-class 'info-object) (jojo:parse v2-info :as :alist)))

;; => #<INFO-OBJECT {10045F9C93}>

(slot-value * 'license)

;; => #<LICENSE-OBJECT {1006600BE3}>

(slot-value * 'name)

;; => "Apache 2.0"
125 changes: 112 additions & 13 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<table class="docinfo" frame="void" rules="none"><col class="docinfo-name" />
<col class="docinfo-content" />
<tbody valign="top">
<tr><th class="docinfo-name">Author</th><td>Matt Novenstern</td></tr><tr><th class="docinfo-name">Version</th><td>0.4.0</td></tr><tr class="field"><th colspan="1" class="docinfo-name">license:</th><td class="field-body">LLGPLv3+</td>
<tr><th class="docinfo-name">Author</th><td>Matt Novenstern</td></tr><tr><th class="docinfo-name">Version</th><td>0.5.0</td></tr><tr class="field"><th colspan="1" class="docinfo-name">license:</th><td class="field-body">LLGPLv3+</td>
</tr>
<tr class="field"><th colspan="1" class="docinfo-name">homepage:</th><td class="field-body"><a class="reference" href="https://fisxoj.github.io/sanity-clause/"><span>https://fisxoj.github.io/sanity-clause/</span></a></td>
</tr>
Expand All @@ -24,13 +24,14 @@
<tbody valign="top">
<tr class="field"><th colspan="1" class="field-name">Source:</th><td class="field-body"><a class="reference" href="https://github.com/fisxoj/sanity-clause"><span>https://github.com/fisxoj/sanity-clause</span></a></td>
</tr>
<tr class="field"><th colspan="1" class="field-name">Docs:</th><td class="field-body"><p class="first"><a class="reference" href="https://fisxoj.github.io/sanity-clause/"><span>https://fisxoj.github.io/sanity-clause/</span></a></p>
<p class="last">There's no such thing as Sanity Clause -- Groucho Marx</p>
</td>
<tr class="field"><th colspan="1" class="field-name">Docs:</th><td class="field-body"><a class="reference" href="https://fisxoj.github.io/sanity-clause/"><span>https://fisxoj.github.io/sanity-clause/</span></a></td>
</tr>
</tbody>
</table>
<p>Sanity clause is a data validation/contract library. You might use it for configuration data, validating an api response, or documents from a datastore. In a dynamically typed langauge, it helps you define clearly defined areas of doubt and uncertainty. We should love our users, but we should never blindly trust their inputs.</p>
<!-- -->
<blockquote> <p>There's no such thing as Sanity Clause.</p>
<p class="attribution">&mdash; Groucho Marx </p>
</blockquote> <p>Sanity clause is a data validation/contract library. You might use it for configuration data, validating an api response, or documents from a datastore. In a dynamically typed langauge, it helps you define clearly defined areas of doubt and uncertainty. We should love our users, but we should never blindly trust their inputs.</p>
<p>To make use of it, you define schemas, which can be property lists with symbols for keys and instances of <a class="reference ref-class" href="sanity-clause.field.html#class__field"><span class="ref-class">sanity-clause.field:field</span></a> subclasses that dictate the type of values you expect as well as the shape of the property list to be returned after deserializing and validating data. For example:</p>
<pre class="literal-block">
(list :name (make-field :string) :age (make-field :integer))
Expand All @@ -40,8 +41,8 @@

(:key (:string :validator (:not-empty) :default "potato")
:key2 (:integer :validator ((:int :min 0)) :default 2))
</pre><p>and then loading them using <a class="reference ref-function" href="sanity-clause.loadable-schema.html#function__load"><span class="ref-function">sanity-clause.loadable-schema:load</span></a> to load them.</p>
<p>Finally, you can also define class-based schemas using <a class="reference ref-class" href="sanity-clause.metaclass.html#class__validated-metaclass"><span class="ref-class">sanity-clause.metaclass:validated-metaclass</span></a> like:</p>
</pre><p>and then loading them using <a class="reference ref-function" href="http://www.lispworks.com/reference/HyperSpec/Body/f_load.htm"><span class="ref-function">common-lisp:load</span></a> to load them.</p>
<p>Finally, you can also define class-based schemas using <a class="reference ref-class" href="sanity-clause.schema.html#class__validated-metaclass"><span class="ref-class">sanity-clause.schema:validated-metaclass</span></a> like:</p>
<pre class="literal-block">
(defclass person ()
((favorite-dog :type symbol
Expand All @@ -55,27 +56,125 @@
(potato :type string
:initarg :potato
:required t))
(:metaclass sanity-clause.metaclass:validated-metaclass))
(:metaclass sanity-clause:validated-metaclass))
</pre><p>which will validate thier initargs when you instantiate them (<strong><span>BUT NOT WHEN YOU SET SLOTS</span></strong>). Hopefully, that will be added eventually, perhaps as an optional feature.</p>
<div id="packages" class="section"><h2><a name="packages">Packages</a></h2>
<ul>
<div id="example" class="section"><h2><a name="example">Example</a></h2>
<p><tt class="docutils literal">
<span class="pre">v2-info.json</span></tt>:</p>
<pre class="literal-block">
{
"title": "Swagger Sample App",
"description": "This is a sample server Petstore server.",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "1.0.1"
}

</pre><p><tt class="docutils literal">
<span class="pre">example.lisp</span></tt>:</p>
<pre class="literal-block">
;; load required libraries
(ql:quickload '(jonathan sanity-clause))

(defclass contact-object ()
((name :type string
:initarg :name
:documentation "The identifying name of the contact person/organization.")
(url :type string
:field-type :uri
:initarg :url
:documentation "The URL pointing to the contact information. MUST be in the format of a URL.")
(email :type string
:field-type :email
:initarg :email
:documentation "The email address of the contact person/organization. MUST be in the format of an email address."))
(:metaclass sanity-clause:validated-metaclass))


(defclass license-object ()
((name :type string
:initarg :name
:documentation "The license name used for the API.")
(url :type string
:field-type :uri
:initarg :url
:documentation "A URL to the license used for the API. MUST be in the format of a URL."))
(:metaclass sanity-clause:validated-metaclass))


(defclass info-object ()
((title :type string
:data-key "title"
:initarg :title
:required t
:documentation "The title of the application.")
(description :type string
:initarg :description
:documentation "A short description of the application. GFM syntax can be used for rich text representation.")
(terms-of-service :type string
:data-key "termsOfService"
:initarg :terms-of-service
:documentation "The Terms of Service for the API.")
(contact :type contact-object
:field-type :nested
:element-type contact-object
:initarg :contact
:documentation "The contact information for the exposed API.")
(license :type license-object
:field-type :nested
:element-type license-object
:initarg :license
:documentation "The license information for the exposed API.")
(version :type string
:initarg :version
:documentation "Provides the version of the application API (not to be confused with the specification version)."
:required t))
(:metaclass sanity-clause:validated-metaclass))

;;; Deserialize the json from the file into instances of these classes

(let ((v2-info (alexandria:read-file-into-string "v2-info.json")))
(sanity-clause:load (find-class 'info-object) (jojo:parse v2-info :as :alist)))

;; =&gt; #&lt;INFO-OBJECT {10045F9C93}&gt;

(slot-value * 'license)

;; =&gt; #&lt;LICENSE-OBJECT {1006600BE3}&gt;

(slot-value * 'name)

;; =&gt; "Apache 2.0"


</pre><div class="section"><p class="sidebar-title first">Packages</p>
<ul class="last">
<li><p class="first last"><a class="reference" href="sanity-clause.util.html"><span>sanity-clause.util</span></a></p>
</li>
<li><p class="first last"><a class="reference" href="sanity-clause.validator.html"><span>sanity-clause.validator</span></a></p>
</li>
<li><p class="first last"><a class="reference" href="sanity-clause.protocol.html"><span>sanity-clause.protocol</span></a></p>
</li>
<li><p class="first last"><a class="reference" href="sanity-clause.field.html"><span>sanity-clause.field</span></a></p>
</li>
<li><p class="first last"><a class="reference" href="sanity-clause.loadable-schema.html"><span>sanity-clause.loadable-schema</span></a></p>
</li>
<li><p class="first last"><a class="reference" href="sanity-clause.schema.html"><span>sanity-clause.schema</span></a></p>
</li>
<li><p class="first last"><a class="reference" href="sanity-clause.metaclass.html"><span>sanity-clause.metaclass</span></a></p>
</li>
<li><p class="first last"><a class="reference" href="sanity-clause.html"><span>sanity-clause</span></a></p>
</li>
</ul>
</div>
<hr class ="docutils footer" /><div class="footer">Generated on : Fri, 05 Jul 2019 23:24:51 .
</div>
<hr class ="docutils footer" /><div class="footer">Generated on : Sat, 10 Aug 2019 19:19:54 .
</div>
</div>
</body>
Expand Down
Loading

0 comments on commit d9e4d6d

Please sign in to comment.