{"action":"created","comment":{"url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/pulls/comments/60056781","id":60056781,"diff_hunk":"@@ -51,16 +39,159 @@\n                                         (conj a x))) [] with-leaves)]\n     (into (sorted-map) with-froms)))\n \n-(defn- get-catalog-entry\n-  [catalog id]\n-  (some #(when (= (:witan/name %) id) %) catalog))\n+(s/defn validate-workspace\n+  [{:keys [workflow contracts catalog] :as workspace} :- as/Workspace]\n+  (let [workflow* (set workflow)\n+        nodes (workflow->long-hand-workflow workflow)\n+        outputs-from-parents-fn (fn [from] (mapcat (fn [x]\n+                                                     (let [parent-ce          (get-catalog-entry catalog x)\n+                                                           parent-contract    (get-contract workspace x)\n+                                                           parent-con-outputs (set (map :witan/key\n+                                                                                        (:witan/outputs parent-contract)))\n+                                                           output-mappings    (map (juxt :witan/output-src-key\n+                                                                                         :witan/output-dest-key)\n+                                                                                   (:witan/outputs parent-ce))]\n+                                                       (reduce (fn [a [from to]]\n+                                                                 (-> a\n+                                                                     (disj from)\n+                                                                     (conj to))) parent-con-outputs\n+                                                               output-mappings)))\n+                                                   from))]\n \n-(defn- get-contract\n-  [{:keys [catalog contracts]} id]\n-  (let [catalog-entry          (get-catalog-entry catalog id)\n-        fnc                    (:witan/fn catalog-entry)\n-        version                (:witan/version catalog-entry)]\n-    (some #(when (and (= (:witan/fn %) fnc) (= (:witan/version %) version)) %) contracts)))\n+    ;;\n+    ;; Does the contract have duplicates in input or output keys?\n+    ;;\n+    (let [duplicate-contract-inputs\n+          (->> contracts\n+               (remove (fn [{:keys [witan/inputs]}]\n+                         (let [input-set (set (map :witan/key inputs))]\n+                           (= (count input-set) (count inputs)))))\n+               (vec))]\n+      (when (not-empty duplicate-contract-inputs)\n+        (throw\n+         (IllegalArgumentException.\n+          (str \"One or more contracts have duplicated inputs: \" duplicate-contract-inputs)))))\n+    (let [duplicate-contract-outputs\n+          (->> contracts\n+               (remove (fn [{:keys [witan/outputs]}]\n+                         (let [output-set (set (map :witan/key outputs))]\n+                           (= (count output-set) (count outputs)))))\n+               (vec))]\n+      (when (not-empty duplicate-contract-outputs)\n+        (throw\n+         (IllegalArgumentException.\n+          (str \"One or more contracts have duplicated outputs: \" duplicate-contract-outputs)))))\n+\n+    ;;\n+    ;; Does the catalog have duplicates?\n+    ;;\n+    (let [duplicate-catalog-entries\n+          (->> catalog\n+               (map :witan/name)\n+               (utils/find-dupes)\n+               (vec))]\n+      (when (not-empty duplicate-catalog-entries)\n+        (throw\n+         (IllegalArgumentException.\n+          (str \"One or more catalog entries are duplicated: \" duplicate-catalog-entries)))))\n+\n+    ;;\n+    ;; Are all workflow nodes represented in the catalog?\n+    ;;\n+    (let [catalog-names\n+          (->> catalog\n+               (map :witan/name)\n+               (set))\n+          diff (not-empty (clojure.set/difference workflow* catalog-names))]\n+      (when diff\n+        (throw\n+         (IllegalArgumentException.\n+          (str \"One or more workflow entries are unrepresented in the catalog: \" diff)))))\n+\n+    ;;\n+    ;; Do all functions in the catalog have a contract for the specified version?\n+    ;;\n+    (let [missing-contracts\n+          (->> workflow*\n+               (keep (fn [node] (some #(when (= (:witan/name %) node) ((juxt :witan/fn :witan/version) %)) catalog)))\n+               (keep (fn [[fn-name fn-version]]\n+                       (some #(when-not (or (= (:witan/fn %) fn-name)\n+                                            (= (:witan/version %) fn-version)) [fn-name fn-version]) contracts)))\n+               (vec))]\n+      (when (not-empty missing-contracts)\n+        (throw\n+         (IllegalArgumentException.\n+          (str \"There are no contracts for the following function + version combinations: \" missing-contracts)))))\n+\n+    ;;\n+    ;; Does each catalog-entry list all inputs from the contract?\n+    ;;\n+    (let [missing-inputs\n+          (->> nodes\n+               (keep (fn [[k node]]\n+                       (let [ce       (get-catalog-entry catalog k)\n+                             contract (get-contract workspace k)\n+                             from-ce (map (fn [{:keys [witan/input-src-key\n+                                                       witan/input-dest-key\n+                                                       witan/input-src-fn]}]\n+                                            (if input-src-fn\n+                                              input-dest-key\n+                                              input-src-key)) (:witan/inputs ce))\n+                             from-contract (map :witan/key (:witan/inputs contract))]\n+                         (when-not (= (set from-contract) (set from-ce))\n+                           (hash-map k {:has from-ce :expects from-contract})))))\n+               (vec))]\n+      (when (not-empty missing-inputs)\n+        (throw\n+         (IllegalArgumentException.\n+          (str \"The following nodes are either missing a reference to an input or provide an unexpected input: \" missing-inputs)))))\n+\n+    ;;\n+    ;; Will each node have its inputs provided by a parent?\n+    ;;\n+    (let [missing-inputs-from-parents\n+          (->> nodes\n+               (keep (fn [[k node]]\n+                       (let [ce       (get-catalog-entry catalog k)\n+                             contract (get-contract workspace k)\n+                             inputs   (map :witan/key (:witan/inputs contract))\n+                             outputs-from-parents (when (:from node) (outputs-from-parents-fn (:from node)))\n+                             inputs-from-ext (keep (fn [{:keys [witan/input-src-key\n+                                                                witan/input-dest-key\n+                                                                witan/input-src-fn]}]\n+                                                     (when input-src-fn\n+                                                       input-dest-key)) (:witan/inputs ce))\n+                             diff (clojure.set/difference (set inputs) (set (concat inputs-from-ext outputs-from-parents)))]\n+                         (when (not-empty diff)\n+                           (hash-map k diff)))))\n+               (vec))]","path":"src/witan/workspace_executor/core.clj","position":181,"original_position":181,"commit_id":"0b02de95cfed28d3e63beb19c19e2a2b3c5c37f0","original_commit_id":"0b02de95cfed28d3e63beb19c19e2a2b3c5c37f0","user":{"login":"otfrom","id":64839,"avatar_url":"https://avatars.githubusercontent.com/u/64839?v=3","gravatar_id":"","url":"https://api.github.com/users/otfrom","html_url":"https://github.com/otfrom","followers_url":"https://api.github.com/users/otfrom/followers","following_url":"https://api.github.com/users/otfrom/following{/other_user}","gists_url":"https://api.github.com/users/otfrom/gists{/gist_id}","starred_url":"https://api.github.com/users/otfrom/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/otfrom/subscriptions","organizations_url":"https://api.github.com/users/otfrom/orgs","repos_url":"https://api.github.com/users/otfrom/repos","events_url":"https://api.github.com/users/otfrom/events{/privacy}","received_events_url":"https://api.github.com/users/otfrom/received_events","type":"User","site_admin":false},"body":"into...","created_at":"2016-04-18T13:26:53Z","updated_at":"2016-04-18T13:26:53Z","html_url":"https://github.com/MastodonC/witan.workspace-executor/pull/1#discussion_r60056781","pull_request_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/pulls/1","_links":{"self":{"href":"https://api.github.com/repos/MastodonC/witan.workspace-executor/pulls/comments/60056781"},"html":{"href":"https://github.com/MastodonC/witan.workspace-executor/pull/1#discussion_r60056781"},"pull_request":{"href":"https://api.github.com/repos/MastodonC/witan.workspace-executor/pulls/1"}}},"pull_request":{"url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/pulls/1","id":66691783,"html_url":"https://github.com/MastodonC/witan.workspace-executor/pull/1","diff_url":"https://github.com/MastodonC/witan.workspace-executor/pull/1.diff","patch_url":"https://github.com/MastodonC/witan.workspace-executor/pull/1.patch","issue_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/issues/1","number":1,"state":"open","locked":false,"title":"Feature/add tests","user":{"login":"acron0","id":812199,"avatar_url":"https://avatars.githubusercontent.com/u/812199?v=3","gravatar_id":"","url":"https://api.github.com/users/acron0","html_url":"https://github.com/acron0","followers_url":"https://api.github.com/users/acron0/followers","following_url":"https://api.github.com/users/acron0/following{/other_user}","gists_url":"https://api.github.com/users/acron0/gists{/gist_id}","starred_url":"https://api.github.com/users/acron0/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/acron0/subscriptions","organizations_url":"https://api.github.com/users/acron0/orgs","repos_url":"https://api.github.com/users/acron0/repos","events_url":"https://api.github.com/users/acron0/events{/privacy}","received_events_url":"https://api.github.com/users/acron0/received_events","type":"User","site_admin":false},"body":"","created_at":"2016-04-15T19:20:56Z","updated_at":"2016-04-18T13:26:53Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c3fc5dceb36842aed7bb2f754e936592197b2e30","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/pulls/1/commits","review_comments_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/pulls/1/comments","review_comment_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/pulls/comments{/number}","comments_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/issues/1/comments","statuses_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/statuses/0b02de95cfed28d3e63beb19c19e2a2b3c5c37f0","head":{"label":"MastodonC:feature/add-tests","ref":"feature/add-tests","sha":"0b02de95cfed28d3e63beb19c19e2a2b3c5c37f0","user":{"login":"MastodonC","id":2911834,"avatar_url":"https://avatars.githubusercontent.com/u/2911834?v=3","gravatar_id":"","url":"https://api.github.com/users/MastodonC","html_url":"https://github.com/MastodonC","followers_url":"https://api.github.com/users/MastodonC/followers","following_url":"https://api.github.com/users/MastodonC/following{/other_user}","gists_url":"https://api.github.com/users/MastodonC/gists{/gist_id}","starred_url":"https://api.github.com/users/MastodonC/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MastodonC/subscriptions","organizations_url":"https://api.github.com/users/MastodonC/orgs","repos_url":"https://api.github.com/users/MastodonC/repos","events_url":"https://api.github.com/users/MastodonC/events{/privacy}","received_events_url":"https://api.github.com/users/MastodonC/received_events","type":"Organization","site_admin":false},"repo":{"id":56317186,"name":"witan.workspace-executor","full_name":"MastodonC/witan.workspace-executor","owner":{"login":"MastodonC","id":2911834,"avatar_url":"https://avatars.githubusercontent.com/u/2911834?v=3","gravatar_id":"","url":"https://api.github.com/users/MastodonC","html_url":"https://github.com/MastodonC","followers_url":"https://api.github.com/users/MastodonC/followers","following_url":"https://api.github.com/users/MastodonC/following{/other_user}","gists_url":"https://api.github.com/users/MastodonC/gists{/gist_id}","starred_url":"https://api.github.com/users/MastodonC/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MastodonC/subscriptions","organizations_url":"https://api.github.com/users/MastodonC/orgs","repos_url":"https://api.github.com/users/MastodonC/repos","events_url":"https://api.github.com/users/MastodonC/events{/privacy}","received_events_url":"https://api.github.com/users/MastodonC/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/MastodonC/witan.workspace-executor","description":"","fork":false,"url":"https://api.github.com/repos/MastodonC/witan.workspace-executor","forks_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/forks","keys_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/keys{/key_id}","collaborators_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/teams","hooks_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/hooks","issue_events_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/issues/events{/number}","events_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/events","assignees_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/assignees{/user}","branches_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/branches{/branch}","tags_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/tags","blobs_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/git/refs{/sha}","trees_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/git/trees{/sha}","statuses_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/statuses/{sha}","languages_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/languages","stargazers_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/stargazers","contributors_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/contributors","subscribers_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/subscribers","subscription_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/subscription","commits_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/commits{/sha}","git_commits_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/git/commits{/sha}","comments_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/comments{/number}","issue_comment_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/issues/comments{/number}","contents_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/contents/{+path}","compare_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/compare/{base}...{head}","merges_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/merges","archive_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/downloads","issues_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/issues{/number}","pulls_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/pulls{/number}","milestones_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/milestones{/number}","notifications_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/labels{/name}","releases_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/releases{/id}","deployments_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/deployments","created_at":"2016-04-15T12:05:27Z","updated_at":"2016-04-15T12:06:08Z","pushed_at":"2016-04-18T13:15:51Z","git_url":"git://github.com/MastodonC/witan.workspace-executor.git","ssh_url":"git@github.com:MastodonC/witan.workspace-executor.git","clone_url":"https://github.com/MastodonC/witan.workspace-executor.git","svn_url":"https://github.com/MastodonC/witan.workspace-executor","homepage":null,"size":12,"stargazers_count":0,"watchers_count":0,"language":"Clojure","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"MastodonC:master","ref":"master","sha":"cccc55ab4bbe2756f6f923132015f08518a27f59","user":{"login":"MastodonC","id":2911834,"avatar_url":"https://avatars.githubusercontent.com/u/2911834?v=3","gravatar_id":"","url":"https://api.github.com/users/MastodonC","html_url":"https://github.com/MastodonC","followers_url":"https://api.github.com/users/MastodonC/followers","following_url":"https://api.github.com/users/MastodonC/following{/other_user}","gists_url":"https://api.github.com/users/MastodonC/gists{/gist_id}","starred_url":"https://api.github.com/users/MastodonC/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MastodonC/subscriptions","organizations_url":"https://api.github.com/users/MastodonC/orgs","repos_url":"https://api.github.com/users/MastodonC/repos","events_url":"https://api.github.com/users/MastodonC/events{/privacy}","received_events_url":"https://api.github.com/users/MastodonC/received_events","type":"Organization","site_admin":false},"repo":{"id":56317186,"name":"witan.workspace-executor","full_name":"MastodonC/witan.workspace-executor","owner":{"login":"MastodonC","id":2911834,"avatar_url":"https://avatars.githubusercontent.com/u/2911834?v=3","gravatar_id":"","url":"https://api.github.com/users/MastodonC","html_url":"https://github.com/MastodonC","followers_url":"https://api.github.com/users/MastodonC/followers","following_url":"https://api.github.com/users/MastodonC/following{/other_user}","gists_url":"https://api.github.com/users/MastodonC/gists{/gist_id}","starred_url":"https://api.github.com/users/MastodonC/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MastodonC/subscriptions","organizations_url":"https://api.github.com/users/MastodonC/orgs","repos_url":"https://api.github.com/users/MastodonC/repos","events_url":"https://api.github.com/users/MastodonC/events{/privacy}","received_events_url":"https://api.github.com/users/MastodonC/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/MastodonC/witan.workspace-executor","description":"","fork":false,"url":"https://api.github.com/repos/MastodonC/witan.workspace-executor","forks_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/forks","keys_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/keys{/key_id}","collaborators_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/teams","hooks_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/hooks","issue_events_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/issues/events{/number}","events_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/events","assignees_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/assignees{/user}","branches_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/branches{/branch}","tags_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/tags","blobs_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/git/refs{/sha}","trees_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/git/trees{/sha}","statuses_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/statuses/{sha}","languages_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/languages","stargazers_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/stargazers","contributors_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/contributors","subscribers_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/subscribers","subscription_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/subscription","commits_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/commits{/sha}","git_commits_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/git/commits{/sha}","comments_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/comments{/number}","issue_comment_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/issues/comments{/number}","contents_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/contents/{+path}","compare_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/compare/{base}...{head}","merges_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/merges","archive_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/downloads","issues_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/issues{/number}","pulls_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/pulls{/number}","milestones_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/milestones{/number}","notifications_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/labels{/name}","releases_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/releases{/id}","deployments_url":"https://api.github.com/repos/MastodonC/witan.workspace-executor/deployments","created_at":"2016-04-15T12:05:27Z","updated_at":"2016-04-15T12:06:08Z","pushed_at":"2016-04-18T13:15:51Z","git_url":"git://github.com/MastodonC/witan.workspace-executor.git","ssh_url":"git@github.com:MastodonC/witan.workspace-executor.git","clone_url":"https://github.com/MastodonC/witan.workspace-executor.git","svn_url":"https://github.com/MastodonC/witan.workspace-executor","homepage":null,"size":12,"stargazers_count":0,"watchers_count":0,"language":"Clojure","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/MastodonC/witan.workspace-executor/pulls/1"},"html":{"href":"https://github.com/MastodonC/witan.workspace-executor/pull/1"},"issue":{"href":"https://api.github.com/repos/MastodonC/witan.workspace-executor/issues/1"},"comments":{"href":"https://api.github.com/repos/MastodonC/witan.workspace-executor/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/MastodonC/witan.workspace-executor/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/MastodonC/witan.workspace-executor/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/MastodonC/witan.workspace-executor/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/MastodonC/witan.workspace-executor/statuses/0b02de95cfed28d3e63beb19c19e2a2b3c5c37f0"}}}}