65 lines
1.8 KiB
Clojure
65 lines
1.8 KiB
Clojure
(ns net.deertopia.publisher.slug
|
|
(:require [clojure.spec.alpha :as s]
|
|
[spec-tools.core :as st])
|
|
(:import (java.nio ByteBuffer)
|
|
(java.util Base64 UUID)))
|
|
|
|
(defrecord Slug [slug-string]
|
|
Object
|
|
(toString [this]
|
|
(:slug-string this)))
|
|
|
|
(defn from-string [s]
|
|
(try (let [decoder (Base64/getUrlDecoder)]
|
|
(when (= 16 (count (.decode decoder s)))
|
|
(Slug. s)))
|
|
;; really stupid
|
|
(catch IllegalArgumentException _
|
|
nil)))
|
|
|
|
(defn to-string [s]
|
|
(str s))
|
|
|
|
(defn- coerce-to-uuid [string-or-uuid]
|
|
(cond (string? string-or-uuid) (UUID/fromString string-or-uuid)
|
|
(uuid? string-or-uuid) string-or-uuid))
|
|
|
|
(defn- uuid->bytes [string-or-uuid]
|
|
(let [uuid (coerce-to-uuid string-or-uuid)]
|
|
(.array (doto (ByteBuffer/wrap (byte-array 16))
|
|
(.putLong (.getMostSignificantBits uuid))
|
|
(.putLong (.getLeastSignificantBits uuid))))))
|
|
|
|
(defn- bytes->uuid [bytes]
|
|
(when (= (count bytes) 16)
|
|
(let [bb (ByteBuffer/wrap bytes)
|
|
high (.getLong bb)
|
|
low (.getLong bb)]
|
|
(UUID. high low))))
|
|
|
|
(defn from-uuid [string-or-uuid]
|
|
(let [uuid (coerce-to-uuid string-or-uuid)
|
|
encoder (.withoutPadding (Base64/getUrlEncoder))]
|
|
(Slug. (.encodeToString encoder (uuid->bytes uuid)))))
|
|
|
|
(defn to-uuid [slug]
|
|
(let [decoder (Base64/getUrlDecoder)]
|
|
(bytes->uuid (.decode decoder (str slug)))))
|
|
|
|
(comment
|
|
(let [uuid #uuid "f9eab66e-7773-4b87-b854-0bfc8f563809"
|
|
slug (from-uuid uuid)
|
|
round-tripped (to-uuid slug)]
|
|
{:uuid uuid, :slug slug, :round-tripped round-tripped}))
|
|
|
|
(defn make-slug [string]
|
|
(assert (try (to-uuid string)
|
|
(catch Throwable _
|
|
nil))
|
|
"invalid slug")
|
|
(->Slug string))
|
|
|
|
(s/def ::slug
|
|
(s/conformer #(or (some-> % from-string)
|
|
::s/invalid)))
|