<commit_before>(ns workflo.macros.entity
  (:require [clojure.spec :as s]
            [clojure.string :as string]
            [workflo.macros.command.util :as util]
            [workflo.macros.specs.entity]))

;;;; Configuration

(defonce ^:private +configuration+
  (atom {:auth-query nil}))

(defn configure!
  "Configures how entities are created with defentity and how aspects
   like authorization are performed against them. Supports the
   following options:

   :auth-query - a function that takes a parsed query and entity data;
                 this query result from this function is then passed to
                 the entity's auth function to perform authorization."
  [{:keys [auth-query] :as options}]
  (swap! +configuration+ assoc
         :auth-query auth-query))

(defn get-config
  "Returns the configuration for a given configuration key, e.g.
   :auth-query."
  [key]
  (@+configuration+ key))

;;;; Entity registry

(defonce ^:private +registry+ (atom {}))

(defn register-entity!
  [entity-name env]
  (let [entity-sym     (util/unqualify entity-name)
        definition-sym (util/prefix-form-name 'definition entity-sym)
        definition     (symbol (str (ns-name *ns*))
                               (str definition-sym))]
    (swap! +registry+ assoc entity-name definition)))

(defn registered-entities
  []
  @+registry+)

(defn resolve-entity-sym
  [entity-name]
  (let [entity-sym (get @+registry+ entity-name)]
    (when (nil? entity-sym)
      (let [err-msg (str "Failed to resolve entity '" entity-name "'")]
        (throw (Exception. err-msg))))
    entity-sym))

(defn resolve-entity
  [entity-name]
  @(resolve (resolve-entity-sym entity-name)))

(defn authenticate
  [entity data])

(defn validate
  [entity-or-name data]
  (letfn [(validate* [[spec-name spec] data]
            (or (s/valid? spec data)
                (throw (Exception.
                        (format "%s failed: %s"
                                (string/capitalize (name spec-name))
                                (s/explain-str spec data))))))]
    (let [entity (cond-> entity-or-name
                   (symbol? entity-or-name)
                   resolve-entity)]
      (->> [:schema :validation]
           (select-keys entity)
           (map #(validate* % data))
           (every? true?)))))

(defn validation
  [entity]
  (:validation entity))

(defn schema
  [entity]
  (:schema entity))

;;;; Utilities

(defn prefixed-form-name
  [form prefix]
  (symbol (str (ns-name *ns*))
          (str (util/prefix-form-name (:form-name form) prefix))))

;;;; The defentity macro

(s/fdef defentity*
  :args :workflo.macros.specs.entity/defentity-args
  :ret  ::s/any)

(defn defentity*
  ([name forms]
   (defentity* name forms nil))
  ([name forms env]
   (let [args-spec       :workflo.macros.specs.entity/defentity-args
         args            (if (s/valid? args-spec [name forms])
                           (s/conform args-spec [name forms])
                           (throw (Exception.
                                   (s/explain-str args-spec
                                                  [name forms]))))
         description     (:description (:forms args))
         auth            (:auth (:forms args))
         validation      (:validation (:forms args))
         schema          (:schema (:forms args))
         forms           (-> (:forms args)
                             (select-keys [:auth :validation :schema])
                             (vals))
         name-sym        (util/unqualify name)
         forms-map       (zipmap (map (comp keyword :form-name) forms)
                                 (map #(prefixed-form-name % name-sym)
                                      forms))
         auth-form       (when auth
                           `((~'defn ~(util/prefix-form-name 'auth
                                                             name-sym)
                              []
                              ~@(:form-body auth))))
         validation-form (when validation
                           `((~'def ~(util/prefix-form-name 'validation
                                                            name-sym)
                              ~@(:form-body validation))))
         schema-form     (when schema
                           `((~'def ~(util/prefix-form-name 'schema
                                                            name-sym)
                              ~@(:form-body schema))))
         definition      `(~'def ~(util/prefix-form-name 'definition
                                                         name-sym)
                           ~forms-map)]
     `(do
        ~@auth-form
        ~@validation-form
        ~@schema-form
        ~definition))))

(defmacro defentity
  [name & forms]
  (register-entity! name &env)
  (defentity* name forms &env))<commit_msg>Include the auth query in the defentity definition<commit_after>(ns workflo.macros.entity
  (:require [clojure.spec :as s]
            [clojure.string :as string]
            [workflo.macros.command.util :as util]
            [workflo.macros.query :as q]
            [workflo.macros.specs.entity]))

;;;; Configuration

(defonce ^:private +configuration+
  (atom {:auth-query nil}))

(defn configure!
  "Configures how entities are created with defentity and how aspects
   like authorization are performed against them. Supports the
   following options:

   :auth-query - a function that takes a parsed query and entity data;
                 this query result from this function is then passed to
                 the entity's auth function to perform authorization."
  [{:keys [auth-query] :as options}]
  (swap! +configuration+ assoc
         :auth-query auth-query))

(defn get-config
  "Returns the configuration for a given configuration key, e.g.
   :auth-query."
  [key]
  (@+configuration+ key))

;;;; Entity registry

(defonce ^:private +registry+ (atom {}))

(defn register-entity!
  [entity-name env]
  (let [entity-sym     (util/unqualify entity-name)
        definition-sym (util/prefix-form-name 'definition entity-sym)
        definition     (symbol (str (ns-name *ns*))
                               (str definition-sym))]
    (swap! +registry+ assoc entity-name definition)))

(defn registered-entities
  []
  @+registry+)

(defn resolve-entity-sym
  [entity-name]
  (let [entity-sym (get @+registry+ entity-name)]
    (when (nil? entity-sym)
      (let [err-msg (str "Failed to resolve entity '" entity-name "'")]
        (throw (Exception. err-msg))))
    entity-sym))

(defn resolve-entity
  [entity-name]
  @(resolve (resolve-entity-sym entity-name)))

(defn authenticate
  [entity data])

(defn validate
  [entity-or-name data]
  (letfn [(validate* [[spec-name spec] data]
            (or (s/valid? spec data)
                (throw (Exception.
                        (format "%s failed: %s"
                                (string/capitalize (name spec-name))
                                (s/explain-str spec data))))))]
    (let [entity (cond-> entity-or-name
                   (symbol? entity-or-name)
                   resolve-entity)]
      (->> [:schema :validation]
           (select-keys entity)
           (map #(validate* % data))
           (every? true?)))))

(defn validation
  [entity]
  (:validation entity))

(defn schema
  [entity]
  (:schema entity))

;;;; Utilities

(defn prefixed-form-name
  [form prefix]
  (symbol (str (ns-name *ns*))
          (str (util/prefix-form-name (:form-name form) prefix))))

;;;; The defentity macro

(s/fdef defentity*
  :args :workflo.macros.specs.entity/defentity-args
  :ret  ::s/any)

(defn defentity*
  ([name forms]
   (defentity* name forms nil))
  ([name forms env]
   (let [args-spec       :workflo.macros.specs.entity/defentity-args
         args            (if (s/valid? args-spec [name forms])
                           (s/conform args-spec [name forms])
                           (throw (Exception.
                                   (s/explain-str args-spec
                                                  [name forms]))))
         description     (:description (:forms args))
         auth            (:auth (:forms args))
         auth-query      (some-> (:auth-query auth) q/parse)
         validation      (:validation (:forms args))
         schema          (:schema (:forms args))
         forms           (-> (:forms args)
                             (select-keys [:auth :validation :schema])
                             (vals))
         name-sym        (util/unqualify name)
         all-forms       (cond-> forms
                           auth-query (conj {:form-name 'auth-query}))
         forms-map       (zipmap (map (comp keyword :form-name)
                                      all-forms)
                                 (map #(prefixed-form-name % name-sym)
                                      all-forms))
         auth-form       (when auth
                           `((~'defn ~(util/prefix-form-name 'auth
                                                             name-sym)
                              []
                              ~@(:form-body auth))))
         auth-query-form (when auth-query
                           `((~'def ~(util/prefix-form-name 'auth-query
                                                            name-sym)
                              '~auth-query)))
         validation-form (when validation
                           `((~'def ~(util/prefix-form-name 'validation
                                                            name-sym)
                              ~@(:form-body validation))))
         schema-form     (when schema
                           `((~'def ~(util/prefix-form-name 'schema
                                                            name-sym)
                              ~@(:form-body schema))))
         definition      `(~'def ~(util/prefix-form-name 'definition
                                                         name-sym)
                           ~forms-map)]
     `(do
        ~@auth-form
        ~@auth-query-form
        ~@validation-form
        ~@schema-form
        ~definition))))

(defmacro defentity
  [name & forms]
  (register-entity! name &env)
  (defentity* name forms &env))
{'fraction_non_alphanumeric': 0.12264611181578691, 'fraction_numerical': 0.0, 'mean_word_length': 1.462758289283998, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 29, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '910361', 'n_tokens_mistral': 2809, 'n_tokens_neox': 2722, 'n_words': 732}