A metosin/malli schema implementation for clj-commons/ring-buffer, providing validation, generation, and transformation capabilities for ring buffers in Clojure and ClojureScript.
Alpha
There is decent test coverage, but this hasn't yet been tested in production. Use at your own risk. Issues and PRs are welcome!
Latest version: 0.1.36 (tag v0.1.36)
xyz.psyclyx/malli-amalloy-ring-buffer {:mvn/version "0.1.36"}
[xyz.psyclyx/malli-amalloy-ring-buffer "0.1.36"]
Snippets in this section assume the following requires:
(require
'[amalloy.ring-buffer :as rb]
'[malli.core :as m]
'[malli.error :as me]
'[malli.generator :as mg]
'[malli.registry :as mr]
'[malli.transform :as mt]
'[psyclyx.malli.amalloy.ring-buffer :as malli.rb])
Make the schema accessible as :amalloy/ring-buffer by adding it to the default registry:
(mr/set-default-registry!
(mr/composite-registry
(m/default-schemas)
malli.rb/registry))
(m/validate [:amalloy/ring-buffer :int]
(into (rb/ring-buffer 3) [1 2 3])) ; => true
(m/validate [:amalloy/ring-buffer :int]
[1 2 3]) ; => false
Ring buffer capacity can be constrained using schema properties:
:capacity - exact capacity requirement:min-capacity - minimum capacity bound:max-capacity - maximum capacity boundNote that :capacity cannot be combined with min/max bounds.
(m/validate [:amalloy/ring-buffer {:capacity 5} :int]
(rb/ring-buffer 5)) ; => true
(m/validate [:amalloy/ring-buffer {:capacity 5} :int]
(rb/ring-buffer 10)) ; => false
(m/validate [:amalloy/ring-buffer {:min-capacity 5} :int]
(rb/ring-buffer 5)) ; => true
(m/validate [:amalloy/ring-buffer {:min-capacity 5} :int]
(rb/ring-buffer 3)) ; => false
(m/validate [:amalloy/ring-buffer {:max-capacity 5} :int]
(rb/ring-buffer 3)) ; => true
(m/validate [:amalloy/ring-buffer {:max-capacity 5} :int]
(rb/ring-buffer 10)) ; => false
Explanations can be humanized to meaningful error messages in English. Localization PRs are welcome!
(->> [1 2 3]
(m/explain [:amalloy/ring-buffer :int])
me/humanize) ; => ["should be a ring buffer"]
(->> (into (rb/ring-buffer 3) [1 2 3])
(m/explain [:amalloy/ring-buffer {:capacity 5} :int])
me/humanize) ; => ["should have capacity 5"]
(->> (into (rb/ring-buffer 3) [1 2 3])
(m/explain [:amalloy/ring-buffer {:min-capacity 4} :int])
me/humanize) ; => ["should have capacity >= 4"]
(->> (into (rb/ring-buffer 3) [1 2 3])
(m/explain [:amalloy/ring-buffer {:min-capacity 1 :max-capacity 2} :int])
me/humanize) ; => ["should have capacity between 1 and 2"]
(->> (into (rb/ring-buffer 3) [1 2 3])
(m/explain [:amalloy/ring-buffer :string])
(me/humanize)) ; => [["should be a string"] ["should be a string"] ["should be a string"]]
Ring buffers can be generated from schemas.
(mg/generate [:amalloy/ring-buffer :int]) ; => #amalloy/ring-buffer [59 (-32326 -19097 1154648 94 -1 -67 -72204790)]
Capacity constraints are respected.
(mg/generate [:amalloy/ring-buffer {:capacity 3} :int]) ; => #amalloy/ring-buffer [3 (3838072 503345 7160549)]
(mg/generate [:amalloy/ring-buffer {:min-capacity 3} :int]) ; => #amalloy/ring-buffer [11 (1707179 -1 217 -3 -814 -71 644 -2 -561 0 118606)]
:amalloy/ring-buffer schemas support encoding/decoding with transformers.
Ships with ring-buffer-transformer, which transforms between ring buffers and sequentials.
(m/encode [:amalloy/ring-buffer :int]
(into (rb/ring-buffer 5) [1 2 3])
(malli.rb/ring-buffer-transformer)) ; => [1 2 3]
(m/decode [:amalloy/ring-buffer :int]
[1 2 3]
(malli.rb/ring-buffer-transformer)) ; => #amalloy/ring-buffer [3 (1 2 3)]
When decoding collections without an exact :capacity, it will be inferred from the sequential being decoded and any capacity bounds.
(m/decode [:amalloy/ring-buffer {:min-capacity 3} :int]
[1 2 3 4 5]
(malli.rb/ring-buffer-transformer)) ; => #amalloy/ring-buffer [5 (1 2 3 4 5)]
When decoding sequences shorter than the required capacity, the ring buffer is created with the specified capacity but only partially filled:
(m/decode [:amalloy/ring-buffer {:min-capacity 3} :int]
[1 2]
(malli.rb/ring-buffer-transformer)) ; => #amalloy/ring-buffer [3 (1 2)]
This applies to both :capacity and :min-capacity constraints.
When the sequence being decoded exceeds :capacity or
:max-capacity, decoding will fail by default. This is to prevent
seemingly successful decodes from unintentionally dropping data.
(m/decode [:amalloy/ring-buffer {:capacity 3} :int]
(range 5)
(malli.rb/ring-buffer-transformer)) ; => (0 1 2 3 4)
(m/decode [:amalloy/ring-buffer {:max-capacity 3} :int]
(range 5)
(malli.rb/ring-buffer-transformer)) ; => (0 1 2 3 4)
This behavior can be disabled with :overflow.
(m/decode [:amalloy/ring-buffer {:max-capacity 3} :int]
(range 5)
(malli.rb/ring-buffer-transformer {:overflow true})) ; => #amalloy/ring-buffer [3 (2 3 4)]
Of course, malli transformers become much more useful when composed and used to transform larger structures.
(m/encode
[:map [:foo/xs [:amalloy/ring-buffer :int]]]
{:foo/xs (into (rb/ring-buffer 5) [1 2 3])}
(mt/transformer
(mt/key-transformer {:encode name})
(malli.rb/ring-buffer-transformer)
(mt/string-transformer))) ; => {"xs" ["1" "2" "3"]}
(m/decode
[:map [:foo/xs [:amalloy/ring-buffer {:capacity 5} :int]]]
{"xs" ["1" "2" "3"]}
(mt/transformer
(mt/key-transformer {:decode {"xs" :foo/xs}})
(malli.rb/ring-buffer-transformer)
(mt/string-transformer))) ; => {:foo/xs #amalloy/ring-buffer [5 (1 2 3)]}
There's a .dir-locals.el that should make CLJ and CLJS repls work
OOTB on Emacs + CIDER. Just cider-jack-in-clj or
cider-jack-in-cljs from a clojure-mode buffer.
For other editors, you'll need to make sure your editor is including
the :libs and :dev aliases, and is injecting whatever additional
middleware it needs. The :cider and :nrepl aliases may be useful
here. When in doubt, consult your editor's documentation.
As an alternative, starting an nREPL with cider support from the CLI is likely sufficient to enable connecting from your editor.
clj -M:libs:dev:nrepl:cider
This starts an nREPL that's usable from the CLI and accepts connections from many editors.
To switch to a CLJS repl, require repl and evaluate (repl/start-cljs).
The ClojureScript test runner requires an npm dependency, which can be installed with
npm i
clj -M:libs:test
clj -M:libs:test --focus :clj
clj -M:libs:test --focus :cljs
clj -T:build clean
clj -T:build jar
Requires a previously-built jar.
clj -T:build install
clj -T:build ci
CLOJARS_USERNAME and CLOJARS_TOKEN must be set.
clj -T:build ci && clj clj -T:build deploy
clj -M:outdated
clj -M:outdated --upgrade
clj -M:cljstyle check
clj -M:cljstyle fix
Issues and pull requests welcome!
Copyright (c) 2025 Alice Burns
Released under the MIT license.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |