An open API service indexing awesome lists of open source software.

https://github.com/aygp-dr/atom-validator

Clojure library for RFC 4287 Atom feed validation. Validates required elements, content types, date formats. Publishes to Clojars.
https://github.com/aygp-dr/atom-validator

atom clojure feed property-based-testing rfc4287 validation

Last synced: about 14 hours ago
JSON representation

Clojure library for RFC 4287 Atom feed validation. Validates required elements, content types, date formats. Publishes to Clojars.

Awesome Lists containing this project

README

          

#+TITLE: atom-validator
#+AUTHOR: aygp-dr
#+DATE: 2026-06-19

[[https://github.com/aygp-dr/atom-validator/actions/workflows/ci.yml][https://img.shields.io/github/actions/workflow/status/aygp-dr/atom-validator/ci.yml.svg?branch=main&label=CI]]
[[https://clojars.org/org.clojars.apace/atom-validator][https://img.shields.io/clojars/v/org.clojars.apace/atom-validator.svg]]
[[https://cljdoc.org/d/org.clojars.apace/atom-validator][https://img.shields.io/badge/cljdoc-docs-blue.svg]]
[[https://opensource.org/licenses/MIT][https://img.shields.io/badge/License-MIT-blue.svg]]

[[https://datatracker.ietf.org/doc/html/rfc4287][RFC 4287]] Atom feed validator with property-based testing for Clojure.

* Features

- Structural validation per [[https://datatracker.ietf.org/doc/html/rfc4287][RFC 4287]] (required elements, dates)
- Semantic checks (day-of-week in titles, feed freshness)
- URL/IRI validation (detects typos like =wal.shsite= → =wal.sh/site=)
- Property-based testing with [[https://github.com/clojure/test.check][test.check]] generators

* Installation

** deps.edn

#+begin_src clojure
{:deps {org.clojars.apace/atom-validator {:mvn/version "RELEASE"}}}
#+end_src

** Leiningen

#+begin_src clojure
[org.clojars.apace/atom-validator "RELEASE"]
#+end_src

* Development Workflow

Org-mode literate programming with live REPL evaluation:

[[file:images/04-error-handling.png]]

See [[file:doc/emacs-setup.org][Emacs setup guide]] for configuration.

* Quick Start

** Require the namespace

#+begin_src clojure
(require '[atom-validator.core :as v])
#+end_src

** Validate a feed map

#+begin_src clojure
(def feed {:id "urn:uuid:feed-1"
:title "My Blog"
:updated "2026-06-19T12:00:00Z"
:entries [{:id "urn:uuid:entry-1"
:title "Morning Brief: Thursday, June 19"
:updated "2026-06-19T00:00:00Z"
:links [{:href "https://example.com/1/" :rel "alternate"}]}]})

(v/validate-feed feed)
;; => {:valid? false
;; :errors [{:type :error
;; :code :day-of-week-mismatch
;; :message "Title says 'Thursday' but updated date is a Friday"
;; :path [:entries 0 :title]}]
;; :warnings []}
#+end_src

** Parse and validate XML

#+begin_src clojure
(def feed-xml "

urn:uuid:feed-1
My Blog
2026-06-19T12:00:00Z
")

(v/validate-feed feed-xml)
;; => {:valid? true :errors [] :warnings [...]}
#+end_src

* REPL Examples

** Start the REPL

#+begin_src bash
# With [[https://docs.cider.mx][CIDER]] middleware for Emacs
make repl

# Or with [[https://github.com/flow-storm/flow-storm-debugger][FlowStorm]] time-travel debugger
make storm
#+end_src

** Fetch and validate a real Atom feed

#+begin_src clojure
(require '[atom-validator.core :as v]
'[clojure.java.io :as io])

;; Fetch a real Atom feed (GitHub releases)
(def github-feed
(slurp "https://github.com/clojure/clojure/releases.atom"))

(def result (v/validate-feed github-feed))

(:valid? result)
;; => true (or false with :errors)

;; Check specific errors
(map :code (:errors result))
;; => (:stale-feed-updated :invalid-url-host ...)
#+end_src

** Generate random valid feeds (for testing)

#+begin_src clojure
(require '[clojure.test.check.generators :as gen]
'[atom-validator.generators :as g])

;; Generate a valid Atom feed
(gen/generate g/gen-valid-atom-feed)
;; => {:id "urn:uuid:..."
;; :title "Article Update"
;; :updated "2025-03-15T14:22:33Z"
;; :entries [...]}

;; Generate an entry with day-of-week mismatch (for testing Issue #1)
(gen/generate (g/gen-entry-with-mismatched-day))
;; => {:title "Morning Brief: Thursday, June 19"
;; :updated "2026-06-19T00:00:00Z" ; Friday!
;; ...}

;; Generate a feed with stale updated timestamp (for testing Issue #2)
(gen/generate (g/gen-feed-with-stale-updated))
;; => {:updated "2026-06-14T..."
;; :entries [{:updated "2026-06-19T..." ...}]} ; newer than feed!
#+end_src

** Interactive validation workflow

#+begin_src clojure
;; Step 1: Parse the feed
(def feed (v/parse-feed github-feed))
(:title feed)
;; => "Release notes from clojure"

;; Step 2: Check feed metadata
(count (:entries feed))
;; => 10

;; Step 3: Validate individual entries
(v/validate-entry (first (:entries feed)))
;; => {:valid? true ...}

;; Step 4: Find problematic entries
(->> (:entries feed)
(map-indexed (fn [i e] [i (:title e) (:valid? (v/validate-entry e))]))
(filter (fn [[_ _ valid?]] (not valid?))))
;; => ([3 "Bad Entry Title" false] ...)
#+end_src

* Validation Rules

| Issue | Check | Error Code |
|-------+-------+------------|
| #1 | Title day-of-week matches == date | =:day-of-week-mismatch= |
| #2 | Feed == >= max(entry ==) | =:stale-feed-updated= |
| #3 | Entry URLs are valid absolute URLs | =:invalid-url-host= |

* Development

** Commands

| Command | Description |
|---------+-------------|
| =make test= | Run unit tests + property-based tests |
| =make lint= | Run [[https://github.com/clj-kondo/clj-kondo][clj-kondo]] static analysis |
| =make repl= | Start [[https://docs.cider.mx][CIDER]]-enabled [[https://nrepl.org][nREPL]] on port 7888 |
| =make storm= | Start [[https://github.com/flow-storm/flow-storm-debugger][FlowStorm]] time-travel debugger |
| =make check= | Run lint + test |
| =make jar= | Build JAR file |
| =make install= | Install to local Maven repo |
| =make deploy= | Deploy to [[https://clojars.org][Clojars]] (requires credentials) |

** Connect from Emacs

#+begin_src bash
# Terminal 1: Start REPL in [[https://github.com/tmux/tmux][tmux]]
tmux new-session -s atom-validator -d 'make repl'

# Emacs: Connect
M-x cider-connect RET localhost RET 7888
#+end_src

** Project Structure

| File | Description |
|------+-------------|
| [[file:src/atom_validator/core.clj][core.clj]] | Main API: validate-feed, validate-entry, parse-feed |
| [[file:src/atom_validator/parser.clj][parser.clj]] | XML parsing with [[https://github.com/clojure/data.xml][clojure.data.xml]] |
| [[file:src/atom_validator/rules.clj][rules.clj]] | [[https://datatracker.ietf.org/doc/html/rfc4287][RFC 4287]] structural validation |
| [[file:src/atom_validator/semantic.clj][semantic.clj]] | Semantic checks (day-of-week) |
| [[file:src/atom_validator/url.clj][url.clj]] | URL/IRI validation |
| [[file:test/atom_validator/core_test.clj][core_test.clj]] | Unit + property-based tests |
| [[file:test/atom_validator/generators.clj][generators.clj]] | [[https://github.com/clojure/test.check][test.check]] generators for Atom feeds |

* API Reference

** =validate-feed=

#+begin_src clojure
(v/validate-feed feed)
(v/validate-feed feed {:semantic? true :strict? false})
#+end_src

Returns ={:valid? bool :errors [...] :warnings [...] :feed parsed-map}=

Options:
- =:semantic?= - Enable semantic checks like day-of-week (default =true=)
- =:strict?= - Treat warnings as errors (default =false=)

** =validate-entry=

#+begin_src clojure
(v/validate-entry entry)
(v/validate-entry entry {:semantic? true})
#+end_src

Returns ={:valid? bool :errors [...] :warnings [...]}=

** =parse-feed=

#+begin_src clojure
(v/parse-feed xml-string-or-input-stream)
#+end_src

Returns parsed feed as Clojure map with =:id=, =:title=, =:updated=, =:entries=, etc.

** =valid?=

#+begin_src clojure
(v/valid? feed)
(v/valid? feed {:semantic? false})
#+end_src

Quick check - returns =true= or =false=.

* License

MIT

* Related

- [[https://datatracker.ietf.org/doc/html/rfc4287][RFC 4287 - The Atom Syndication Format]]
- [[https://validator.w3.org/feed/][W3C Feed Validation Service]]