### Full OpenAPI 3 Specification Example Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/openapi3 An example demonstrating the generation of a complete OpenAPI 3 specification, including info, servers, components (schemas and headers), and paths with operations. ```APIDOC ## `openapi/openapi-spec` Full Example ### Description This example shows how to construct a comprehensive OpenAPI 3 specification using `openapi/openapi-spec`. It includes defining API information, server URLs, component schemas and headers, and defining paths with HTTP methods, parameters, request bodies, and responses. ### Method `openapi/openapi-spec` ### Parameters - `spec` (map): The Clojure data structure representing the OpenAPI specification. - `:openapi` (string): OpenAPI version (e.g., "3.0.3"). - `:info` (map): API metadata (title, description, termsOfService, contact, license, version). - `:servers` (array of maps): List of server objects, each with a `url` and `description`. - `:components` (map): - `::openapi/schemas` (map): Definitions for reusable schemas. - `::openapi/headers` (map): Definitions for reusable headers. - `:paths` (map): A map where keys are API paths (e.g., "/api/ping") and values are objects defining operations (GET, POST, etc.). - Each operation object can contain: - `:tags` (array of strings) - `:description` (string) - `:summary` (string) - `:operationId` (string) - `:requestBody` (map): Defines the request body, including content types and schemas. - `:responses` (map): Defines possible responses, including status codes, descriptions, and content. - `::openapi/parameters` (map): Defines parameters for the operation (path, query, header, cookie). ### Request Example ```clojure (openapi/openapi-spec {:openapi "3.0.3" :info {:title "Sample Pet Store App" :description "This is a sample server for a pet store." :termsOfService "http://example.com/terms/" :contact {:name "API Support", :url "http://www.example.com/support" :email "support@example.com"} :license {:name "Apache 2.0", :url "https://www.apache.org/licenses/LICENSE-2.0.html"} :version "1.0.1"} :servers [{:url "https://development.gigantic-server.com/v1" :description "Development server"} {:url "https://staging.gigantic-server.com/v1" :description "Staging server"} {:url "https://api.gigantic-server.com/v1" :description "Production server"}] :components {::openapi/schemas {:user ::user :address ::address} ::openapi/headers {:token ::token}} :paths {"/api/ping" {:get {:description "Returns all pets from the system that the user has access to" :responses {200 {::openapi/content {"application/xml" ::user "application/json" (st/spec {:spec ::address :openapi/example "Some examples here" :openapi/examples {:admin {:summary "Admin user" :description "Super user" :value {:anything :here} :externalValue "External value"}} :openapi/encoding {:contentType "application/json"}})}}}} "/user/:id" {:post {:tags ["user"] :description "Returns pets based on ID" :summary "Find pets by ID" :operationId "getPetsById" :requestBody {::openapi/content {"application/json" ::user}} :responses {200 {:description "pet response" ::openapi/content {"application/json" ::user}} :default {:description "error payload", ::openapi/content {"text/html" ::user}}} ::openapi/parameters {:path (s/keys :req-un [::id]) :header (s/keys :req-un [::token])}}}}}) ``` ### Response #### Success Response (map) Returns a map representing the complete OpenAPI 3 specification. #### Response Example ```clojure {:openapi "3.0.3", :info {:title "Sample Pet Store App", :description "This is a sample server for a pet store.", :termsOfService "http://example.com/terms/", :contact {:name "API Support", :url "http://www.example.com/support", :email "support@example.com"}, :license {:name "Apache 2.0", :url "https://www.apache.org/licenses/LICENSE-2.0.html"}, :version "1.0.1"}, :servers [{:url "https://development.gigantic-server.com/v1", :description "Development server"} {:url "https://staging.gigantic-server.com/v1", :description "Staging server"} {:url "https://api.gigantic-server.com/v1", :description "Production server"}], :components {:schemas {:user {:type "object", :properties {"id" {:type "integer", :format "int64"}, "name" {:type "string"}, ``` -------------------------------- ### Full OpenAPI 3 Specification Example Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/openapi3 Provides a comprehensive example of generating an OpenAPI 3 specification, including API info, servers, components (schemas and headers), and paths with operations. This demonstrates the full capabilities of `openapi-spec`. ```clojure (openapi/openapi-spec {:openapi "3.0.3" :info {:title "Sample Pet Store App" :description "This is a sample server for a pet store." :termsOfService "http://example.com/terms/" :contact {:name "API Support", :url "http://www.example.com/support" :email "support@example.com"} :license {:name "Apache 2.0", :url "https://www.apache.org/licenses/LICENSE-2.0.html"} :version "1.0.1"} :servers [{:url "https://development.gigantic-server.com/v1" :description "Development server"} {:url "https://staging.gigantic-server.com/v1" :description "Staging server"} {:url "https://api.gigantic-server.com/v1" :description "Production server"}] :components {::openapi/schemas {:user ::user :address ::address} ::openapi/headers {:token ::token}} :paths {"/api/ping" {:get {:description "Returns all pets from the system that the user has access to" :responses {200 {::openapi/content {"application/xml" ::user "application/json" (st/spec {:spec ::address :openapi/example "Some examples here" :openapi/examples {:admin {:summary "Admin user" :description "Super user" :value {:anything :here} :externalValue "External value"}} :openapi/encoding {:contentType "application/json"}})}}}} "/user/:id" {:post {:tags ["user"] :description "Returns pets based on ID" :summary "Find pets by ID" :operationId "getPetsById" :requestBody {::openapi/content {"application/json" ::user}} :responses {200 {:description "pet response" ::openapi/content {"application/json" ::user}} :default {:description "error payload", ::openapi/content {"text/html" ::user}}} ::openapi/parameters {:path (s/keys :req-un [::id]) :header (s/keys :req-un [::token])}}}}) ;; => ;; {:openapi "3.0.3", ;; :info ;; {:title "Sample Pet Store App", ;; :description "This is a sample server for a pet store.", ;; :termsOfService "http://example.com/terms/", ;; :contact ;; {:name "API Support", ;; :url "http://www.example.com/support", ;; :email "support@example.com"}, ;; :license ;; {:name "Apache 2.0", ;; :url "https://www.apache.org/licenses/LICENSE-2.0.html"}, ;; :version "1.0.1"}, ;; :servers ;; [{:url "https://development.gigantic-server.com/v1", ;; :description "Development server"} ;; {:url "https://staging.gigantic-server.com/v1", ;; :description "Staging server"} ;; {:url "https://api.gigantic-server.com/v1", ;; :description "Production server"}], ;; :components ;; {:schemas ;; {:user ;; {:type "object", ;; :properties ;; {"id" {:type "integer", :format "int64"}, ;; "name" {:type "string"}, ``` -------------------------------- ### Dependency Update Example Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/changelog Shows an example of dependency updates for Clojure and clojurescript. This format is used across multiple versions. ```clojure [org.clojure/spec.alpha "0.1.134"] is available but we use "0.1.123" copy ``` -------------------------------- ### Dependency Update Example Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/changelog Shows an example of dependency updates for Clojure and clojurescript. This format is used across multiple versions. ```clojure [org.clojure/clojure "1.9.0-beta2"] is available but we use "1.9.0-alpha19" [org.clojure/clojurescript "1.9.946"] is available but we use "1.9.908" copy ``` -------------------------------- ### Dependency Update Example Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/changelog Shows an example of dependency updates for Clojure and clojurescript. This format is used across multiple versions. ```clojure [org.clojure/clojure "1.9.0-beta4"] is available but we use "1.9.0-beta4" [org.clojure/spec.alpha "0.1.143"] is available but we use "0.1.134" copy ``` -------------------------------- ### Example Order Data Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/spec-coercion An example of a valid order data structure conforming to the defined specs. ```clojure (def order {:id 123, :items {1 {:description "vadelmalimsa" :tags #{:good :red} :amount 10}, 2 {:description "korvapuusti" :tags #{:raisin :sugar} :amount 20}}, :delivery #inst"2007-11-20T20:19:17.000-00:00", :location [61.499374 23.7408149]}) (s/valid? ::order order) ``` -------------------------------- ### Update Dependencies Example Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/changelog Lists available dependency versions and the versions currently used in the project. ```clojure [org.clojure/spec.alpha "0.2.176"] is available but we use "0.1.143" [org.clojure/clojurescript "1.10.339"] is available but we use "1.10.329" [com.fasterxml.jackson.core/jackson-databind "2.9.7"] is available but we use "2.9.6" ``` -------------------------------- ### Spec-Driven Transformation Example Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/changelog Demonstrates encoding and decoding a keyword using custom string transformations. ```clojure (require '[clojure.spec.alpha :as s]) (require '[spec-tools.core :as st]) (require '[clojure.string :as str]) (s/def ::spec (st/spec {:spec #(and (simple-keyword? %) (-> % name str/lower-case keyword (= %))) :description "a lowercase keyword, encoded in uppercase in string-mode" :decode/string #(-> %2 name str/lower-case keyword) :encode/string #(-> %2 name str/upper-case)})) (st/decode ::spec :kikka) ; :kikka (as-> "KiKka" $ (st/decode ::spec $)) ; :clojure.spec.alpha/invalid (as-> "KiKka" $ (st/decode ::spec $ st/string-transformer)) ; :kikka (as-> "KiKka" $ (st/decode ::spec $ st/string-transformer) (st/encode ::spec $ st/string-transformer)) ; "KIKKA" ``` -------------------------------- ### Simple Coercion Example Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/spec-coercion Demonstrates coercing a string to an integer using `st/coerce` with `st/string-transformer` and `st/json-transformer`. ```clojure (st/coerce int? "1" st/string-transformer) ``` ```clojure (st/coerce int? "1" st/json-transformer) ``` -------------------------------- ### Dependency Update Example Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/changelog Shows a dependency update for spec.alpha. This snippet is informational and does not contain executable code. ```clojure [org.clojure/spec.alpha "1.10.439"] is available but we use "1.10.339" ``` -------------------------------- ### OpenAPI 3 Specification Example Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/openapi3 This snippet shows a full OpenAPI 3 specification including schemas for user and address, and paths for API endpoints. ```yaml openapi: 3.0.0 info: title: API with User and Address Schemas version: 1.0.0 servers: - url: http://localhost:3000 components: schemas: User: type: object properties: id: type: integer format: int64 name: type: string address: $ref: '#/components/schemas/Address' required: ["id", "name", "address"] Address: type: object properties: street: type: string city: oneOf: - enum: ["tre", "hki"] type: string - type: null required: ["street", "city"] paths: /api/ping: get: summary: Get all pets description: Returns all pets from the system that the user has access to responses: 200: description: A list of pets content: application/xml: schema: $ref: '#/components/schemas/User' application/json: schema: $ref: '#/components/schemas/Address' examples: admin: summary: Admin user description: Super user value: { anything: here } externalValue: External value /user/{id}: post: tags: - user summary: Find pets by ID description: Returns pets based on ID operationId: getPetsById parameters: - name: id in: path description: ID of pet to return required: true schema: type: integer format: int64 - name: token in: header description: Authentication token required: true schema: type: string requestBody: description: Pet object that needs to be fetched required: true content: application/json: schema: $ref: '#/components/schemas/User' responses: 200: description: pet response content: application/json: schema: $ref: '#/components/schemas/User' default: description: error payload content: text/html: schema: $ref: '#/components/schemas/User' ``` -------------------------------- ### Convert Data Spec to Clojure Spec (Legacy Syntax) Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/data-specs This example shows the legacy syntax for converting a data spec to a Clojure spec, providing the root spec name and the data spec directly. ```clojure ;; legacy syntax (def person-spec (ds/spec ::person person)) (def new-person-spec (ds/spec ::person new-person)) ``` -------------------------------- ### Decode Inst? with String Transformer Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/spec-driven-transformations Shows an example of decoding a string into an `inst?` (instance of time) using the `st/string-transformer`. This highlights how type-driven transformations work for standard types like dates. ```clojure (as-> "2014-02-18T18:25:37Z" $ (st/decode inst? $ st/string-transformer)) ``` -------------------------------- ### Spec Bijection Example Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/spec-driven-transformations Demonstrates the guaranteed round-trip transformation: a decoded value X is always encoded into Y, which can be decoded back into X. This example traces the transformation of a string through encoding and decoding. ```clojure (as-> "KikKa" $ (doto $ prn) (st/encode ::spec $ st/string-transformer) (doto $ prn) (st/decode ::spec $ st/string-transformer) (doto $ prn) (st/encode ::spec $ st/string-transformer) (prn $)) ``` -------------------------------- ### Validate Data Against a Spec Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/data-specs This example demonstrates how to use `s/valid?` to check if a given data structure conforms to the `new-person-spec` that was previously defined and registered. ```clojure (s/valid? new-person-spec {::age 63 :boss true :name "Liisa" :languages #{:clj :cljs} :aliases [{:alias "Lissu"} "Liisu"] :orders [{:id 1, :description "cola"} {:id 2, :description "kebab"}] :description "Liisa is a valid boss" :address {:street "Amurinkatu 2" :zip "33210"}}) ; true ``` -------------------------------- ### Encode Inst? with String Transformer Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/spec-driven-transformations Demonstrates encoding an `inst?` (instance of time) back into a string using the `st/string-transformer`. This follows the decoding example and shows the full cycle for date/time transformations. ```clojure (as-> "2014-02-18T18:25:37Z" $ (st/decode inst? $ st/string-transformer) (st/encode inst? $ st/string-transformer)) ``` -------------------------------- ### GET /api/ping Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/openapi3 Retrieves all pets accessible to the user from the system. ```APIDOC ## GET /api/ping ### Description Returns all pets from the system that the user has access to. ### Method GET ### Endpoint /api/ping ### Responses #### Success Response (200) - **id** (integer) - - **name** (string) - - **address** (object) - - **street** (string) - - **city** (string) - #### Response Example ```json { "id": 0, "name": "string", "address": { "street": "string", "city": "string" } } ``` ``` -------------------------------- ### Updating Clojure version dependency Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/changelog Shows an example of updating the Clojure version dependency in a project. This is a common maintenance task for libraries to stay current with the language. ```clojure [org.clojure/clojure "1.9.0"] ``` -------------------------------- ### Composing Type-Based Transformers Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/spec-driven-transformations Shows how to compose type-based transformers by merging decoders and encoders. This example defines `strict-json-transformer` by combining JSON and strip-extra-keys functionalities. ```clojure (def strict-json-transformer (st/type-transformer {:name :custom :decoders (merge stt/json-type-decoders stt/strip-extra-keys-type-decoders) :encoders stt/json-type-encoders})) ``` ```clojure (def strict-json-transformer (st/type-transformer st/json-transformer st/strip-extra-keys-transformer st/strip-extra-values-transformer)) ``` -------------------------------- ### Define a Data Spec for a Person Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/data-specs This example defines a 'person' data spec using a map literal. It demonstrates various spec types like integers, keywords, optional/required fields, sets, and nested collections. The 'ds/maybe' wrapper makes the 'address' field nilable. ```clojure (s/def ::age pos-int?) ;; a data-spec (def person {::id integer? ::age ::age :boss boolean? (ds/req :name) string? (ds/opt :description) string? :languages #{keyword?} :aliases [(ds/or {:maps {:alias string?} :strings string?})] :orders [{:id int? :description string?}] :address (ds/maybe {:street string? :zip string?})}) ;; it's just data. (def new-person (dissoc person ::id)) ``` -------------------------------- ### Composite Spec Coercion Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/spec-coercion Use `s/and` to combine multiple predicates, allowing coercion to apply all found transformations sequentially. This example coerces a string to an integer that also satisfies a minimum value. ```clojure (def adult? (s/and int? #(>= % 18))) (st/coerce adult? "20" st/string-transformer) ``` -------------------------------- ### Create Release Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/developer-guide Steps to create a new release, including updating changelog and version. ```bash ./scripts/release.sh ``` -------------------------------- ### Using ds/spec with 1-arity and options Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/changelog Illustrates the 1-arity version of `ds/spec` which supports options like `:keys-spec` and `:keys-default`. This allows for more concise spec definitions, especially for optional keys. ```clojure (require '[clojure.spec.alpha :as s]) (require '[spec-tools.data-spec :as ds]) (s/valid? (ds/spec {:name ::optiona-user :spec {(ds/req :id) int? :age pos-int? :name string?} :keys-default ds/opt}) {:id 123}) ``` -------------------------------- ### Transform Spec to JSON Schema Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/json-schemas Converts a Clojure spec into a JSON Schema draft-04. This example defines a user spec and transforms it. ```clojure (require '[spec-tools.json-schema :as json-schema]) (require '[clojure.spec.alpha :as s]) (s/def ::id string?) (s/def ::name string?) (s/def ::street string?) (s/def ::city #{:tre :hki}) (s/def ::address (s/keys :req-un [::street ::city])) (s/def ::user (s/keys :req-un [::id ::name ::address])) (json-schema/transform ::user) ``` -------------------------------- ### Run Tests Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/developer-guide Execute tests for Clojure or ClojureScript using the provided script. ```bash ./scripts/test.sh clj ./scripts/test.sh cljs ``` -------------------------------- ### Creating Spec Records Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/spec-records Specs can be created using the `spec-tools.core/spec` macro with type inference or explicit types, or by using the `spec-tools.core/create-spec` function. The `:type` and `:form` keys can often be inferred automatically. ```clojure (require '[spec-tools.core :as st]) ;; using type inference (st/spec integer?) ;; with explicit type (st/spec integer? {:type :long}) ;; map form (st/spec {:spec integer?}) (st/spec {:spec integer?, :type :long}) ;; function (st/create-spec {:spec integer? :form `integer? :type :long}) ;; function, with type and form inference (st/create-spec {:spec integer?}) ``` -------------------------------- ### Annotate Spec for Swagger2 Schema Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/swagger2 Uses annotations within a spec to populate fields like `:title`, `:description`, `:example`, and `:default` in the generated Swagger2 Schema. ```clojure (require '[spec-tools.core :as st]) (swagger/transform (st/spec {:spec integer? :name "integer" :description "it's an int" :swagger/example 42 :json-schema/default 42})) ;{:type "integer" ; :title "integer" ; :description "it's an int" ; :example 42 ; :default 42} ``` -------------------------------- ### Generate OpenAPI3 Spec with Parameter Definitions Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/openapi3 This snippet illustrates generating an OpenAPI3 specification map by defining path parameters using Clojure specs and the ::openapi/parameters dispatch key. It shows how to specify query, path, and header parameters, including overriding existing ones. ```clojure (require '[clojure.spec.alpha :as s]) (s/def ::id int?) (s/def ::name string?) (s/def ::street string?) (s/def ::city (s/nilable #{:tre :hki})) (s/def ::filters (s/coll-of string? :into [])) (s/def ::address (s/keys :req-un [::street ::city])) (s/def ::user (s/keys :req-un [::id ::name ::address])) (s/def ::token string?) (openapi/openapi-spec {:path {"/test" {:parameters [{:name "username" :in "path" :description "username to fetch" :required true :schema {:type "string"} :style "simple"} {:name "name" :in "query" :description "This will be overridden" :required true :schema {:type "string"} :style "simple"}] ::openapi/parameters {:path (s/keys :req-un [::id]) :query (s/keys :req-un [::name] :opt-un [::street ::city ::filters]) :header ::user}}}) ;; => ;; {:path ;; {"/test" ;; {:parameters ;; [{:name "username", ;; :in "path", ;; :description "username to fetch", ;; :required true, ;; :schema {:type "string"}, ;; :style "simple"} ;; {:name "id", ;; :in "path", ;; :description "", ;; :required true, ;; :schema {:type "integer", :format "int64"}} ;; {:name "name", ;; :in "query", ;; :description "", ;; :required true, ;; :schema {:type "string"}} ;; {:name "street", ;; :in "query", ;; :description "", ;; :required false, ;; :schema {:type "string"}} ;; {:name "city", ;; :in "query", ;; :description "", ;; :required false, ;; :schema {:oneOf [{:enum [:tre :hki], :type "string"} {:type "null"}]}} ;; {:name "filters", ;; :in "query", ;; :description "", ;; :required false, ;; :schema {:type "array", :items {:type "string"}}} ;; {:name "id", ;; :in "header", ;; :description "", ;; :required true, ;; :schema {:type "integer", :format "int64"}} ;; {:name "name", ;; :in "header", ;; :description "", ;; :required true, ;; :schema {:type "string"}} ;; {:name "address", ;; :in "header", ;; :description "", ;; :required true, ;; :schema ;; {:type "object", ;; :properties ;; {"street" {:type "string"}, ;; "city" {:oneOf [{:enum [:tre :hki], :type "string"} {:type "null"}]}}, ;; :required ["street" "city"], ;; :title "spec-tools.openapi.core-test/address"}}]}}} ``` ```clojure (require '[clojure.spec.alpha :as s]) ``` -------------------------------- ### Collect Spec Names and Forms Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/spec-visitor Demonstrates how to use the `visitor/visit` function to recursively collect all registered spec forms within a given spec. This is useful for introspection and analysis of spec definitions. ```clojure (require '[clojure.spec.alpha :as s]) (require '[spec-tools.visitor :as visitor]) (s/def ::id string?) (s/def ::name string?) (s/def ::street string?) (s/def ::city #{:tre :hki}) (s/def ::address (s/keys :req-un [::street ::city])) (s/def ::user (s/keys :req-un [::id ::name ::address])) ;; visitor to recursively collect all registered spec forms (let [specs (atom {})] (visitor/visit ::user (fn [_ spec _ _] (if-let [s (s/get-spec spec)] (swap! specs assoc spec (s/form s)) @specs)))) ``` -------------------------------- ### Generating OpenAPI Spec with Headers Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/openapi3 Demonstrates how to merge custom headers into an OpenAPI specification using the `::openapi/headers` option. ```APIDOC ## `openapi/openapi-spec` with `::openapi/headers` ### Description This function generates an OpenAPI 3 specification. The `::openapi/headers` option allows you to define or merge headers into the specification. It expects a map where keys are header names and values are Clojure specs representing the header schema. The function merges these headers with any existing headers, overriding duplicates. ### Method `openapi/openapi-spec` ### Parameters - `spec` (map): The Clojure data structure representing the OpenAPI specification. - `::openapi/headers` (map): A map defining headers. Keys are header names (e.g., `:City`), and values are Clojure specs (e.g., `::city`). ### Request Example ```clojure (openapi/openapi-spec {:headers {:X-Rate-Limit-Limit {:description "The number of allowed requests in the current period" :schema {:type "integer"}}} ::openapi/headers {:City ::city :Authorization ::token :User ::user}}) ``` ### Response #### Success Response (map) Returns a map representing the OpenAPI specification with merged headers. #### Response Example ```clojure {:headers {:X-Rate-Limit-Limit {:description "The number of allowed requests in the current period", :schema {:type "integer"}}, :City {:description "", :required false, :schema {:oneOf [{:enum [:tre :hki], :type "string"} {:type "null"}]}}, :Authorization {:description "", :required true, :schema {:type "string"}}, :User {:description "", :required true, :schema {:type "object", :properties {"id" {:type "integer", :format "int64"}, "name" {:type "string"}, "address" {:type "object", :properties {"street" {:type "string"}, "city" {:oneOf [{:enum [:tre :hki], :type "string"} {:type "null"}]}}, :required ["street" "city"], :title "spec-tools.openapi.core-test/address"}}, :required ["id" "name" "address"], :title "spec-tools.openapi.core-test/user"}}}} ``` -------------------------------- ### any->string Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/api/spec-tools.transform Converts any input value to its string representation. A utility for consistent string formatting. ```APIDOC ## any->string ### Description Converts any input value to its string representation. A utility for consistent string formatting. ### Method N/A (Function Signature) ### Endpoint N/A ### Parameters N/A ### Request Example ```clojure (any->string _ x) ``` ### Response N/A ``` -------------------------------- ### Annotate Spec for JSON Schema Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/json-schemas Annotates a Clojure spec with metadata like `:name`, `:description`, and `json-schema` keys to enrich the generated JSON Schema. This example creates an integer spec with a default value. ```clojure (require '[spec-tools.core :as st]) (json-schema/transform (st/spec {:spec integer? :name "integer" :description "it's an int" :json-schema/default 42})) ``` -------------------------------- ### Selecting spec with st/coerce Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/changelog Illustrates `st/select-spec` using `st/coerce` to strip extra keys from specs. Requires `clojure.spec.alpha` and `spec-tools.core` to be required. ```clojure (require '[clojure.spec.alpha :as s]) (require '[spec-tools.core :as st]) (s/def ::height integer?) (s/def ::weight integer?) (s/def ::person (s/keys :req-un [::height ::weight])) (s/def ::persons (s/coll-of ::person :into [])) (s/def ::data (s/keys :req-un [::persons])) (st/select-spec ::data {:TOO "MUCH" :persons [{:INFOR "MATION" :height 200 :weight 80}]}) ; => {:persons [{:weight 80, :height 200}]} copy ``` -------------------------------- ### Manually Typed Spec Coercion Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/spec-coercion Annotate a spec with `:type` using `st/spec` to manually define the expected data type for coercion. This example explicitly sets the type to `:long` for a spec that checks if a number is at least 18. ```clojure (def adult? (st/spec {:spec #(>= % 18) :type :long})) (st/coerce adult? "20" st/string-transformer) ``` -------------------------------- ### Spec-Based Transformations with Custom Encode/Decode Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/spec-coercion Define custom `:decode` and `:encode` functions within a spec annotation to control how values are transformed between string and other formats. This example defines a spec for a lowercase keyword that is encoded as uppercase in string format and decoded to lowercase. ```clojure (require '[clojure.string :as str]) (s/def ::my-keyword (st/spec {:spec #(and (simple-keyword? %) (-> % name str/lower-case keyword (= %))) :description "a lowercase keyword, uppercase in string-mode" :json-schema/type {:type "string", :format "keyword"} :json-schema/example "kikka" :decode/string #(-> %2 name str/lower-case keyword) :encode/string #(-> %2 name str/upper-case)})) (st/coerce ::my-keyword "OLIPA.KERRAN/AVARUUS" st/string-transformer) ``` -------------------------------- ### Decode with String Transformer (Fallback) Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/spec-driven-transformations Shows how `st/decode` falls back to `conform` when the value is not valid against the spec without a transformer. It then demonstrates using `st/string-transformer` explicitly to achieve the desired decoding. ```clojure (as-> "KiKka" $ (st/decode ::spec $)) ``` ```clojure (as-> "KiKka" $ (st/decode ::spec $ st/string-transformer)) ``` -------------------------------- ### Dependency Updates in 0.2.2 Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/changelog Lists the dependency versions used in version 0.2.2 of spec-tools. ```text [org.clojure/clojure "0.1.123"] is available but we use "0.1.108" [org.clojure/clojure "1.9.0-alpha17"] is available but we use "1.9.0-alpha16" [org.clojure/clojurescript "1.9.562"] is available but we use "1.9.542" copy ``` -------------------------------- ### Query Registered Specs Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/data-specs This code snippet shows how to query the registry for specs that match a given regular expression, useful for verifying which specs have been generated. ```clojure (keys (st/registry #"user.*\.*")) ``` -------------------------------- ### Using spec-tools.spell for Closed Map Specs Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/changelog Illustrates how to use the `spec-tools.spell` namespace to define closed map specifications, ensuring no unknown or misspelled keys are present. Requires explicit dependencies on `com.bhauman/spell-spec` and `expound`. ```clojure (require '[clojure.spec.alpha :as s]) (require '[spec-tools.spell :as spell]) (s/def ::name string?) (s/def ::use-history boolean?) (s/def ::config (spell/closed (s/keys :opt-un [::name ::use-history]))) (s/def ::options (spell/closed (s/keys :opt-un [::config]))) (def invalid {:config {:name "John" :use-hisory false :countr 1}}) (s/explain-data ::options invalid) ;#:clojure.spec.alpha{:problems ({:path [:config 0], ; :pred #{:use-history}, ; :val :use-hisory, ; :via [:user/options :user/config], ; :in [:config :use-hisory 0], ; :expound.spec.problem/type :spell-spec.alpha/misspelled-key, ; :spell-spec.alpha/misspelled-key :use-hisory, ; :spell-spec.alpha/likely-misspelling-of (:use-history)} ; {:path [:config 0], ; :pred #{:name :use-history}, ; :val :countr, ; :via [:user/options :user/config], ; :in [:config :countr 0], ; :expound.spec.problem/type :spell-spec.alpha/unknown-key, ; :spell-spec.alpha/unknown-key :countr}), ; :spec :user/options, ; :value {:config {:name "John", :use-hisory false, :countr 1}}} (println (spell/explain-str ::options invalid)) ; -- Misspelled map key ------------- ; ; {:config {:name ..., :countr ..., :use-hisory ...}} ; ^^^^^^^^^^^ ; ; should probably be: :use-history ; ; -- Unknown map key ---------------- ; ; {:config {:name ..., :use-hisory ..., :countr ...}} ; ; ^^^^^^^ ; ; should be one of: :name, :use-history ; ; ------------------------- ; ; Detected 2 errors ; ``` -------------------------------- ### Compare Date Encoding: RFC3339 vs ISO-8601 Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/changelog Demonstrates the change in date encoding behavior introduced in version 0.10.0, moving from a +0000 offset to a Z suffix for RFC3339 compatibility. ```clojure ;; the new behavior - version 0.10.0 and later user=> (st/encode inst? (java.util.Date.) st/json-transformer) "2019-06-26T06:49:15.538Z" ;; the old behavior - version 0.9.3 and earlier user=> (st/encode inst? (java.util.Date.) st/json-transformer) "2019-06-26T06:50:02.233+0000" ``` -------------------------------- ### Define and Encode Order Data Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/spec-coercion Defines a sample order with extra data and encodes it to JSON. This sets up a scenario for demonstrating strict coercion. ```clojure (def evil-json-order (->> (-> order (assoc :owner "ikitommi") (assoc :LONGSTRING ".................................") (assoc-in [:items 1 :discount] 80) (assoc-in [:items 2 ::discount] 80)) (m/encode "application/json") (m/decode "application/json"))) evil-json-order ``` -------------------------------- ### Transform Spec to OpenAPI3 Schema (anyOf) Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/openapi3 Shows how to transform a spec with a sequence of types (cat) into an OpenAPI3 schema using 'anyOf'. ```clojure (openapi/transform (s/cat :string string? :int integer?)) ;;=> {:type "array", :items {:anyOf [{:type "integer"} {:type "string"}]}} ``` -------------------------------- ### Parsing s/and Spec Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/changelog Demonstrates how `parse/parse-spec` represents an `s/and` specification, showing the parsed items and the resulting type. ```clojure (testing "s/and" (is (= {::parse/items [{:spec int?, :type :long} {:spec keyword?, :type :keyword}] :type [:and [:long :keyword]]} (parse/parse-spec (s/and int? keyword?))))) ``` -------------------------------- ### string->keyword Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/api/spec-tools.transform Converts a string into a keyword. Useful for dynamic keyword creation. ```APIDOC ## string->keyword ### Description Converts a string into a keyword. Useful for dynamic keyword creation. ### Method N/A (Function Signature) ### Endpoint N/A ### Parameters N/A ### Request Example ```clojure (string->keyword _ x) ``` ### Response N/A ``` -------------------------------- ### Transforming values with JSON transformer Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/changelog Demonstrates using `st/coerce` with `st/json-transformer` to transform values from keywords. Ensure `spec-tools.core` is required. ```clojure (require '[spec-tools.core :as st]) (st/coerce (s/map-of int? int?) {:1 1, :2 2} st/json-transformer) ; {1 1, 2 2} copy ``` -------------------------------- ### Require Spec Visitor Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/spec-visitor Imports the spec-tools.visitor namespace for use in your project. ```clojure (require '[spec-tools.visitor :as visitor]) ``` -------------------------------- ### Convert a string to a ratio Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/api/spec-tools.transform Use this function to parse a string into a ratio (fraction). Useful for representing exact fractional values. ```clojure (string->ratio _ x) ``` -------------------------------- ### keyword-or-string-> Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/api/spec-tools.transform Transforms a value into either a keyword or a string, depending on the input. Provides flexibility in key handling. ```APIDOC ## keyword-or-string-> ### Description Transforms a value into either a keyword or a string, depending on the input. Provides flexibility in key handling. ### Method N/A (Function Signature) ### Endpoint N/A ### Parameters N/A ### Request Example ```clojure (keyword-or-string-> f) ``` ### Response N/A ``` -------------------------------- ### Encode and Decode with String Transformer Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/spec-driven-transformations Illustrates a full encode-decode cycle using the `st/string-transformer`. This snippet shows how to transform a string value first by decoding it and then encoding it back using the defined spec and transformer. ```clojure (as-> "KiKka" $ (st/decode ::spec $ st/string-transformer) (st/encode ::spec $ st/string-transformer)) ``` -------------------------------- ### Dependency Updates in 0.2.0 Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/changelog Lists the dependency versions used in version 0.2.0 of spec-tools, including a breaking change to update spec to alpha16. ```text [org.clojure/spec.alpha "0.1.108"] [org.clojure/clojure "1.9.0-alpha16"] is available but we use "1.9.0-alpha15" [org.clojure/clojurescript "1.9.542"] is available but we use "1.9.518" copy ``` -------------------------------- ### Using ds/spec with ds/or for complex data structures Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/changelog Demonstrates how to use `ds/spec` with `ds/or` to define a spec that accepts either a map with a string alias or a plain string. This is useful for defining flexible input structures. ```clojure (require '[clojure.spec.alpha :as s]) (require '[spec-tools.data-spec :as ds]) (s/conform (ds/spec ::user [(ds/or {:map {:alias string?} :string string?})]) [{:alias "Rudi"}, "Rudolf"]) ``` -------------------------------- ### Encode Data Using a Spec and Transformer Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/data-specs This snippet shows how to use `st/encode` with a spec and a transformer (e.g., `st/string-transformer`) to transform data according to the spec's rules. It demonstrates how values are coerced. ```clojure (st/encode new-person-spec {::age "63" :boss "true" :name "Liisa" :languages ["clj" "cljs"] :aliases [{:alias "Lissu"} "Liisu"] :orders [{:id "1", :description "cola"} {:id "2", :description "kebab"}] :description "Liisa is a valid boss" :address nil} st/string-transformer) ; {::age 63 ; :boss true ; :name "Liisa" ; :aliases [{:alias "Lissu"} "Liisu"] ; :languages #{:clj :cljs} ; :orders [{:id 1, :description "cola"} ; {:id 2, :description "kebab"}] ; :description "Liisa is a valid boss" ; :address nil} ``` -------------------------------- ### Using ds/spec with non-qualified map keys Source: https://cljdoc.org/d/metosin/spec-tools/0.10.8/doc/changelog Demonstrates that the `:name` option in `ds/spec` is only required when non-qualified map keys are present. This simplifies spec definitions for maps using keywords. ```clojure (require '[clojure.spec.alpha :as s]) (require '[spec-tools.data-spec :as ds]) (s/valid? (ds/spec {:spec [{::alias string?}]}) [{::alias "kikka"} {::alias "kukka"}]) ```