{"id":51061814,"url":"https://github.com/aygp-dr/atom-validator","last_synced_at":"2026-06-24T04:01:03.677Z","repository":{"id":365999777,"uuid":"1274516486","full_name":"aygp-dr/atom-validator","owner":"aygp-dr","description":"Clojure library for RFC 4287 Atom feed validation. Validates required elements, content types, date formats. Publishes to Clojars.","archived":false,"fork":false,"pushed_at":"2026-06-22T18:11:21.000Z","size":1764,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-23T03:22:32.123Z","etag":null,"topics":["atom","clojure","feed","property-based-testing","rfc4287","validation"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aygp-dr.png","metadata":{"files":{"readme":"README.org","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.org","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-19T15:40:21.000Z","updated_at":"2026-06-22T15:13:08.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/aygp-dr/atom-validator","commit_stats":null,"previous_names":["aygp-dr/atom-validator"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/aygp-dr/atom-validator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aygp-dr%2Fatom-validator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aygp-dr%2Fatom-validator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aygp-dr%2Fatom-validator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aygp-dr%2Fatom-validator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aygp-dr","download_url":"https://codeload.github.com/aygp-dr/atom-validator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aygp-dr%2Fatom-validator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34716326,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["atom","clojure","feed","property-based-testing","rfc4287","validation"],"created_at":"2026-06-23T03:00:58.569Z","updated_at":"2026-06-24T04:01:03.600Z","avatar_url":"https://github.com/aygp-dr.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"#+TITLE: atom-validator\n#+AUTHOR: aygp-dr\n#+DATE: 2026-06-19\n\n[[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\u0026label=CI]]\n[[https://clojars.org/org.clojars.apace/atom-validator][https://img.shields.io/clojars/v/org.clojars.apace/atom-validator.svg]]\n[[https://cljdoc.org/d/org.clojars.apace/atom-validator][https://img.shields.io/badge/cljdoc-docs-blue.svg]]\n[[https://opensource.org/licenses/MIT][https://img.shields.io/badge/License-MIT-blue.svg]]\n\n[[https://datatracker.ietf.org/doc/html/rfc4287][RFC 4287]] Atom feed validator with property-based testing for Clojure.\n\n* Features\n\n- Structural validation per [[https://datatracker.ietf.org/doc/html/rfc4287][RFC 4287]] (required elements, dates)\n- Semantic checks (day-of-week in titles, feed freshness)\n- URL/IRI validation (detects typos like =wal.shsite= → =wal.sh/site=)\n- Property-based testing with [[https://github.com/clojure/test.check][test.check]] generators\n\n* Installation\n\n** deps.edn\n\n#+begin_src clojure\n{:deps {org.clojars.apace/atom-validator {:mvn/version \"RELEASE\"}}}\n#+end_src\n\n** Leiningen\n\n#+begin_src clojure\n[org.clojars.apace/atom-validator \"RELEASE\"]\n#+end_src\n\n* Development Workflow\n\nOrg-mode literate programming with live REPL evaluation:\n\n[[file:images/04-error-handling.png]]\n\nSee [[file:doc/emacs-setup.org][Emacs setup guide]] for configuration.\n\n* Quick Start\n\n** Require the namespace\n\n#+begin_src clojure\n(require '[atom-validator.core :as v])\n#+end_src\n\n** Validate a feed map\n\n#+begin_src clojure\n(def feed {:id \"urn:uuid:feed-1\"\n           :title \"My Blog\"\n           :updated \"2026-06-19T12:00:00Z\"\n           :entries [{:id \"urn:uuid:entry-1\"\n                      :title \"Morning Brief: Thursday, June 19\"\n                      :updated \"2026-06-19T00:00:00Z\"\n                      :links [{:href \"https://example.com/1/\" :rel \"alternate\"}]}]})\n\n(v/validate-feed feed)\n;; =\u003e {:valid? false\n;;     :errors [{:type :error\n;;               :code :day-of-week-mismatch\n;;               :message \"Title says 'Thursday' but updated date is a Friday\"\n;;               :path [:entries 0 :title]}]\n;;     :warnings []}\n#+end_src\n\n** Parse and validate XML\n\n#+begin_src clojure\n(def feed-xml \"\u003c?xml version='1.0'?\u003e\n\u003cfeed xmlns='http://www.w3.org/2005/Atom'\u003e\n  \u003cid\u003eurn:uuid:feed-1\u003c/id\u003e\n  \u003ctitle\u003eMy Blog\u003c/title\u003e\n  \u003cupdated\u003e2026-06-19T12:00:00Z\u003c/updated\u003e\n\u003c/feed\u003e\")\n\n(v/validate-feed feed-xml)\n;; =\u003e {:valid? true :errors [] :warnings [...]}\n#+end_src\n\n* REPL Examples\n\n** Start the REPL\n\n#+begin_src bash\n# With [[https://docs.cider.mx][CIDER]] middleware for Emacs\nmake repl\n\n# Or with [[https://github.com/flow-storm/flow-storm-debugger][FlowStorm]] time-travel debugger\nmake storm\n#+end_src\n\n** Fetch and validate a real Atom feed\n\n#+begin_src clojure\n(require '[atom-validator.core :as v]\n         '[clojure.java.io :as io])\n\n;; Fetch a real Atom feed (GitHub releases)\n(def github-feed\n  (slurp \"https://github.com/clojure/clojure/releases.atom\"))\n\n(def result (v/validate-feed github-feed))\n\n(:valid? result)\n;; =\u003e true (or false with :errors)\n\n;; Check specific errors\n(map :code (:errors result))\n;; =\u003e (:stale-feed-updated :invalid-url-host ...)\n#+end_src\n\n** Generate random valid feeds (for testing)\n\n#+begin_src clojure\n(require '[clojure.test.check.generators :as gen]\n         '[atom-validator.generators :as g])\n\n;; Generate a valid Atom feed\n(gen/generate g/gen-valid-atom-feed)\n;; =\u003e {:id \"urn:uuid:...\"\n;;     :title \"Article Update\"\n;;     :updated \"2025-03-15T14:22:33Z\"\n;;     :entries [...]}\n\n;; Generate an entry with day-of-week mismatch (for testing Issue #1)\n(gen/generate (g/gen-entry-with-mismatched-day))\n;; =\u003e {:title \"Morning Brief: Thursday, June 19\"\n;;     :updated \"2026-06-19T00:00:00Z\"  ; Friday!\n;;     ...}\n\n;; Generate a feed with stale updated timestamp (for testing Issue #2)\n(gen/generate (g/gen-feed-with-stale-updated))\n;; =\u003e {:updated \"2026-06-14T...\"\n;;     :entries [{:updated \"2026-06-19T...\" ...}]}  ; newer than feed!\n#+end_src\n\n** Interactive validation workflow\n\n#+begin_src clojure\n;; Step 1: Parse the feed\n(def feed (v/parse-feed github-feed))\n(:title feed)\n;; =\u003e \"Release notes from clojure\"\n\n;; Step 2: Check feed metadata\n(count (:entries feed))\n;; =\u003e 10\n\n;; Step 3: Validate individual entries\n(v/validate-entry (first (:entries feed)))\n;; =\u003e {:valid? true ...}\n\n;; Step 4: Find problematic entries\n(-\u003e\u003e (:entries feed)\n     (map-indexed (fn [i e] [i (:title e) (:valid? (v/validate-entry e))]))\n     (filter (fn [[_ _ valid?]] (not valid?))))\n;; =\u003e ([3 \"Bad Entry Title\" false] ...)\n#+end_src\n\n* Validation Rules\n\n| Issue | Check | Error Code |\n|-------+-------+------------|\n| #1 | Title day-of-week matches =\u003cupdated\u003e= date | =:day-of-week-mismatch= |\n| #2 | Feed =\u003cupdated\u003e= \u003e= max(entry =\u003cupdated\u003e=) | =:stale-feed-updated= |\n| #3 | Entry URLs are valid absolute URLs | =:invalid-url-host= |\n\n* Development\n\n** Commands\n\n| Command | Description |\n|---------+-------------|\n| =make test= | Run unit tests + property-based tests |\n| =make lint= | Run [[https://github.com/clj-kondo/clj-kondo][clj-kondo]] static analysis |\n| =make repl= | Start [[https://docs.cider.mx][CIDER]]-enabled [[https://nrepl.org][nREPL]] on port 7888 |\n| =make storm= | Start [[https://github.com/flow-storm/flow-storm-debugger][FlowStorm]] time-travel debugger |\n| =make check= | Run lint + test |\n| =make jar= | Build JAR file |\n| =make install= | Install to local Maven repo |\n| =make deploy= | Deploy to [[https://clojars.org][Clojars]] (requires credentials) |\n\n** Connect from Emacs\n\n#+begin_src bash\n# Terminal 1: Start REPL in [[https://github.com/tmux/tmux][tmux]]\ntmux new-session -s atom-validator -d 'make repl'\n\n# Emacs: Connect\nM-x cider-connect RET localhost RET 7888\n#+end_src\n\n** Project Structure\n\n| File | Description |\n|------+-------------|\n| [[file:src/atom_validator/core.clj][core.clj]] | Main API: validate-feed, validate-entry, parse-feed |\n| [[file:src/atom_validator/parser.clj][parser.clj]] | XML parsing with [[https://github.com/clojure/data.xml][clojure.data.xml]] |\n| [[file:src/atom_validator/rules.clj][rules.clj]] | [[https://datatracker.ietf.org/doc/html/rfc4287][RFC 4287]] structural validation |\n| [[file:src/atom_validator/semantic.clj][semantic.clj]] | Semantic checks (day-of-week) |\n| [[file:src/atom_validator/url.clj][url.clj]] | URL/IRI validation |\n| [[file:test/atom_validator/core_test.clj][core_test.clj]] | Unit + property-based tests |\n| [[file:test/atom_validator/generators.clj][generators.clj]] | [[https://github.com/clojure/test.check][test.check]] generators for Atom feeds |\n\n* API Reference\n\n** =validate-feed=\n\n#+begin_src clojure\n(v/validate-feed feed)\n(v/validate-feed feed {:semantic? true :strict? false})\n#+end_src\n\nReturns ={:valid? bool :errors [...] :warnings [...] :feed parsed-map}=\n\nOptions:\n- =:semantic?= - Enable semantic checks like day-of-week (default =true=)\n- =:strict?= - Treat warnings as errors (default =false=)\n\n** =validate-entry=\n\n#+begin_src clojure\n(v/validate-entry entry)\n(v/validate-entry entry {:semantic? true})\n#+end_src\n\nReturns ={:valid? bool :errors [...] :warnings [...]}=\n\n** =parse-feed=\n\n#+begin_src clojure\n(v/parse-feed xml-string-or-input-stream)\n#+end_src\n\nReturns parsed feed as Clojure map with =:id=, =:title=, =:updated=, =:entries=, etc.\n\n** =valid?=\n\n#+begin_src clojure\n(v/valid? feed)\n(v/valid? feed {:semantic? false})\n#+end_src\n\nQuick check - returns =true= or =false=.\n\n* License\n\nMIT\n\n* Related\n\n- [[https://datatracker.ietf.org/doc/html/rfc4287][RFC 4287 - The Atom Syndication Format]]\n- [[https://validator.w3.org/feed/][W3C Feed Validation Service]]\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faygp-dr%2Fatom-validator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faygp-dr%2Fatom-validator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faygp-dr%2Fatom-validator/lists"}