{"id":37019786,"url":"https://github.com/c-fraser/graph-guard","last_synced_at":"2026-01-14T02:12:42.625Z","repository":{"id":188478926,"uuid":"620881255","full_name":"c-fraser/graph-guard","owner":"c-fraser","description":"@neo4j schema and query validation","archived":false,"fork":false,"pushed_at":"2026-01-02T15:16:05.000Z","size":99857,"stargazers_count":10,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-08T21:57:47.160Z","etag":null,"topics":["bolt","cypher","graph-database","neo4j","proxy-server","query-validation","schema","schema-validation"],"latest_commit_sha":null,"homepage":"https://c-fraser.github.io/graph-guard/","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/c-fraser.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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":"2023-03-29T14:52:54.000Z","updated_at":"2026-01-08T11:43:43.000Z","dependencies_parsed_at":null,"dependency_job_id":"eee07fb3-763b-4e3e-a2bb-6f242a16d2a6","html_url":"https://github.com/c-fraser/graph-guard","commit_stats":null,"previous_names":["c-fraser/graph-guard"],"tags_count":65,"template":false,"template_full_name":null,"purl":"pkg:github/c-fraser/graph-guard","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c-fraser%2Fgraph-guard","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c-fraser%2Fgraph-guard/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c-fraser%2Fgraph-guard/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c-fraser%2Fgraph-guard/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/c-fraser","download_url":"https://codeload.github.com/c-fraser/graph-guard/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c-fraser%2Fgraph-guard/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408711,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T01:52:23.358Z","status":"online","status_checked_at":"2026-01-14T02:00:06.678Z","response_time":107,"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":["bolt","cypher","graph-database","neo4j","proxy-server","query-validation","schema","schema-validation"],"created_at":"2026-01-14T02:12:41.912Z","updated_at":"2026-01-14T02:12:42.617Z","avatar_url":"https://github.com/c-fraser.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n    \u003cpicture\u003e\n        \u003csource media=\"(prefers-color-scheme: dark)\" src=\"docs/graph-guard-dark.png\"\u003e\n        \u003cimg alt=\"graph-guard logo\" src=\"docs/graph-guard-light.png\"\u003e\n    \u003c/picture\u003e\n\u003c/div\u003e\n\n[![Test](https://github.com/c-fraser/graph-guard/workflows/test/badge.svg)](https://github.com/c-fraser/graph-guard/actions)\n[![Release](https://img.shields.io/github/v/release/c-fraser/graph-guard?logo=github\u0026sort=semver)](https://github.com/c-fraser/graph-guard/releases)\n[![Maven Central](https://img.shields.io/maven-central/v/io.github.c-fraser/graph-guard.svg)](https://search.maven.org/search?q=g:io.github.c-fraser%20AND%20a:graph-guard)\n[![Apache License 2.0](https://img.shields.io/badge/License-Apache2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)\n\n`graph-guard` is an extensible [Bolt](https://neo4j.com/docs/bolt/current/bolt/) proxy server,\nthat's capable of performing realtime [Cypher](https://opencypher.org/) query validation,\nfor [Neo4j](https://neo4j.com/) 5+ (compatible databases).\n\n\u003c!--- TOC --\u003e\n\n* [Design](#design)\n  * [Schema](#schema)\n    * [Graph](#graph)\n    * [Nodes](#nodes)\n    * [Relationships](#relationships)\n    * [Properties](#properties)\n    * [Metadata](#metadata)\n    * [Violations](#violations)\n    * [Grammar](#grammar)\n* [Usage](#usage)\n  * [Examples](#examples)\n    * [Kotlin](#kotlin)\n    * [Java](#java)\n  * [Documentation](#documentation)\n  * [Application](#application)\n    * [Demo](#demo)\n* [Libraries](#libraries)\n  * [graph-guard-validate](#graph-guard-validate)\n  * [graph-guard-script](#graph-guard-script)\n* [License](#license)\n\n\u003c!--- END --\u003e\n\n## Design\n\nThe [Server](https://c-fraser.github.io/graph-guard/api/graph-guard/io.github.cfraser.graphguard/-server/index.html)\nproxies [Bolt](https://c-fraser.github.io/graph-guard/api/graph-guard/io.github.cfraser.graphguard/-bolt/index.html)\nmessages as displayed in the diagram below.\n\n![proxy-server](docs/proxy-server.png)\n\nProxied messages are intercepted by\nthe [Plugin](https://c-fraser.github.io/graph-guard/api/graph-guard/io.github.cfraser.graphguard/-server/-plugin/index.html),\nenabling\nthe [Server](https://c-fraser.github.io/graph-guard/api/graph-guard/io.github.cfraser.graphguard/-server/index.html)\nto dynamically transform the incoming and outgoing data.\n\n[Validator](https://c-fraser.github.io/graph-guard/api/graph-guard/io.github.cfraser.graphguard.plugin/-validator/index.html)\nis\na [Plugin](https://c-fraser.github.io/graph-guard/api/graph-guard/io.github.cfraser.graphguard/-server/-plugin/index.html)\nthat performs realtime query validation by\nintercepting [RUN](https://c-fraser.github.io/graph-guard/api/graph-guard/io.github.cfraser.graphguard/-bolt/-run/index.html)\nrequests then analyzing the [Cypher](https://opencypher.org/) (and parameters) for\nschema [violations](#violations). If the intercepted query is determined to be *invalid* according\nto the schema, then\na [FAILURE](https://c-fraser.github.io/graph-guard/api/graph-guard/io.github.cfraser.graphguard/-bolt/-failure/index.html)\nresponse is sent to the *client*.\n\nFor example, validate [movies](https://github.com/neo4j-graph-examples/movies) queries align with\nthe [schema](#schema) via the [Server](#design), using the [graph-guard](#usage) library.\n\n\u003c!--- INCLUDE\nimport org.neo4j.driver.Driver\nimport org.neo4j.driver.exceptions.DatabaseException\n--\u003e\n\n[//]: # (@formatter:off)\n```kotlin\n/** Use the [driver] to run queries that violate the *movies* schema. */\nfun runInvalidMoviesQueries(driver: Driver) {\n  driver.session().use { session -\u003e\n    for (query in\n      listOf(\n        \"CREATE (:TVShow {title: 'The Office', released: 2005})\",\n        \"MATCH (theMatrix:Movie {title: 'The Matrix'}) SET theMatrix.budget = 63000000\",\n        \"MERGE (:Person {name: 'Chris Fraser'})-[:WATCHED]-\u003e(:Movie {title: 'The Matrix'})\",\n        \"MATCH (:Person)-[produced:PRODUCED]-\u003e(:Movie {title: 'The Matrix'}) SET produced.studio = 'Warner Bros.'\",\n        \"CREATE (Keanu:Person {name: 'Keanu Reeves', born: '09/02/1964'})\")) {\n      // run the invalid query and print the schema violation message\n      try {\n        session.run(query)\n        error(\"Expected schema violation for query '$query'\")\n      } catch (exception: DatabaseException) {\n        println(exception.message)\n      }\n    }\n  }\n}\n```\n[//]: # (@formatter:on)\n\u003c!--- KNIT Example01.kt --\u003e\n\n\u003c!--- TEST_NAME Example02Test --\u003e \n\u003c!--- INCLUDE\nimport io.github.cfraser.graphguard.Server\nimport io.github.cfraser.graphguard.plugin.Validator\nimport io.github.cfraser.graphguard.validate.Schema\nimport io.github.cfraser.graphguard.withNeo4j\nimport org.neo4j.driver.Config\nimport org.neo4j.driver.GraphDatabase\n\nfun runExample02() {\n  withNeo4j {\n----- SUFFIX\n  }\n}\n--\u003e\n\n[//]: # (@formatter:off)\n```kotlin\nServer(boltURI(), Validator(Schema(MOVIES_SCHEMA)), Server.Address.InetSocket(\"localhost\", 8787)).use {\n    server -\u003e\n  server.start()\n  GraphDatabase.driver(\"bolt://localhost:8787\", Config.builder().withoutEncryption().build())\n    .use(::runInvalidMoviesQueries)\n}\n```\n[//]: # (@formatter:on)\n\u003c!--- KNIT Example02.kt --\u003e \n\nThe code above prints the following *schema violation* messages.\n\n```text\nUnknown node TVShow\nUnknown property 'budget' for node Movie\nUnknown relationship WATCHED from Person to Movie\nUnknown property 'studio' for relationship PRODUCED from Person to Movie\nInvalid query value(s) '09/02/1964' for property 'born: Integer' on node Person\n```\n\n\u003c!--- TEST --\u003e\n\n### Schema\n\nA schema describes the nodes and relationships in a graph. The schema is defined\nusing a custom DSL language, demonstrated below for\nthe [movies](https://github.com/neo4j-graph-examples/movies) graph.\n\n\u003c!--- INCLUDE\n/** The schema DSL for the *movies* graph. */\nconst val MOVIES_SCHEMA =\n    \"\"\"\n----- SUFFIX\n\"\"\"\n--\u003e\n\n[//]: # (@formatter:off)\n```kotlin\ngraph Movies {\n  node Person(name: String, born: Integer):\n      ACTED_IN(roles: List\u003cString\u003e) -\u003e Movie,\n      DIRECTED -\u003e Movie,\n      PRODUCED -\u003e Movie,\n      WROTE -\u003e Movie,\n      REVIEWED(summary: String, rating: Integer) -\u003e Movie;\n\n  node Movie(title: String, released: Integer, tagline: String);\n}\n```\n[//]: # (@formatter:on)\n\u003c!--- KNIT Example03.kt --\u003e \n\n#### Graph\n\nA `graph` contains [node statements](#nodes). A [schema](#schema) may include multiple\ninterconnected [graphs](#graph). To reference a *node* in another *graph*, qualify the *node* name\nwith the *graph* name, as shown below.\n\n\u003c!--- INCLUDE\nconst val PLACES_SCHEMA =\n    \"\"\"\n----- SUFFIX\n\"\"\"\n--\u003e\n\n[//]: # (@formatter:off)\n```kotlin\ngraph Places {\n  node Theater(name: String):\n      SHOWING(times: List\u003cInteger\u003e) -\u003e Movies.Movie;\n}\n```\n[//]: # (@formatter:on)\n\u003c!--- KNIT Example04.kt --\u003e \n\n#### Nodes\n\nA `node` must have a unique name, and may\nhave [properties](#properties) and/or [relationship definitions](#relationships).\n\n#### Relationships\n\nRelationships are defined relative to the *source* [node](#nodes). A relationship definition must\nhave a name, direction (`-\u003e` for directed, or `--` for undirected), and target node. A relationship\nmust have a unique `(source)-[name]-(target)`, and may also have [properties](#properties).\n\n#### Properties\n\nA [node](#nodes) or [relationship](#relationships) may have *typed* properties. The supported\nproperty types are listed below.\n\n\u003e The types align with the\n\u003e supported [Cypher values](https://neo4j.com/docs/cypher-manual/current/values-and-types/).\n\n- `Any` - a dynamically typed property\n- `Boolean`\n- `Date` - a\n  [date()](https://neo4j.com/docs/cypher-manual/current/functions/temporal/#functions-date)\n- `DateTime` -\n  a [datetime()](https://neo4j.com/docs/cypher-manual/current/functions/temporal/#functions-datetime)\n- `Duration` -\n  a [duration()](https://neo4j.com/docs/cypher-manual/current/functions/temporal/duration/)\n- `Float`\n- `Integer`\n- `List\u003cT\u003e` - where `T` is another (un-parameterized) supported type\n- `LocalDateTime` -\n  a [localdatetime()](https://neo4j.com/docs/cypher-manual/current/functions/temporal/#functions-localdatetime)\n- `LocalTime` -\n  a [localtime()](https://neo4j.com/docs/cypher-manual/current/functions/temporal/#functions-localtime)\n- `String`\n- `Time` -\n  a [time()](https://neo4j.com/docs/cypher-manual/current/functions/temporal/#functions-time)\n- String literal - any value enclosed in double quotation marks, e.g. `\"example\"`\n\nA property can be designated as nullable by including the `?` suffix on the type, for\nexample `String?` and `List\u003cAny?\u003e`.\n\n\u003e The omission of a *type* results in the usage of `Any?`, which effectively disables property value\n\u003e validation.\n\nA [property](#properties) may specify multiple types of values with the *union type* syntax, as\nshown below.\n\n\u003c!--- INCLUDE\nconst val UNION_SCHEMA =\n    \"\"\"\n----- SUFFIX\n\"\"\"\n--\u003e\n\n[//]: # (@formatter:off)\n```kotlin\ngraph G {\n  node N(p: Boolean | \"true\" | \"false\");\n}\n```\n[//]: # (@formatter:on)\n\u003c!--- KNIT Example05.kt --\u003e \n\n#### Metadata\n\nA [node](#nodes), [relationship](#relationships), or [property](#properties) may have arbitrary\n*metadata*.\n\n\u003e Currently, the metadata is purely information, it isn't used in [schema](#schema) verification.\n\n\u003c!--- INCLUDE\nconst val METADATA_SCHEMA =\n    \"\"\"\n----- SUFFIX\n\"\"\"\n--\u003e\n\n[//]: # (@formatter:off)\n```kotlin\ngraph G {\n  node @a N(@b(\"c\") p: Any):\n      @d R(@e(\"f\") @g p: Any) -- N;\n}\n```\n[//]: # (@formatter:on)\n\u003c!--- KNIT Example06.kt --\u003e \n\nThe metadata annotations can have any name, and may include a string literal value within\nparenthesis.\n\n#### Violations\n\nThe Cypher query validation prevents the following [schema](#schema) violations.\n\n- `\"Unknown ${entity}\"` - a query has a node or relationship not defined in the schema\n- `\"Unknown property '${property}' for ${entity}\"` - a query has a property (on a node or\n  relationship) not defined in the schema\n- `\"Invalid query value(s) '${values}' for property '${property}' on ${entity}\"` - a query has\n  property value(s) (on a node or relationship) conflicting with the type defined in the schema\n\n#### Grammar\n\nRefer to\nthe ([antlr4](https://github.com/antlr/antlr4))\n[grammar](https://github.com/c-fraser/graph-guard/blob/main/graph-guard-validate/src/main/antlr/Schema.g4)\nfor an exact specification of the [schema](#schema) DSL.\n\n## Usage\n\nThe `graph-guard*` libraries are accessible\nvia [Maven Central](https://search.maven.org/search?q=g:io.github.c-fraser%20AND%20a:graph-guard*)\nand the `graph-guard-app` application is published in\nthe [releases](https://github.com/c-fraser/graph-guard/releases).\n\n\u003e `graph-guard` requires Java 21+.\n\n\u003e `Server` doesn't currently support TLS (because\n\u003e of [ktor-network](https://youtrack.jetbrains.com/issue/KTOR-694) limitations).\n\u003e Use [NGINX](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-tcp/) or a\n\u003e cloud load balancer to decrypt *Bolt* traffic for the proxy server.\n\n### Examples\n\nRefer to the snippets below to see how to initialize and run a `Server` with the `graph-guard`\nlibrary.\n\n#### Kotlin\n\n\u003c!--- INCLUDE\nimport io.github.cfraser.graphguard.Server\n--\u003e\n\n[//]: # (@formatter:off)\n```kotlin\n/** Run the [test] [block] after `this` [Server] has started. */\nfun Server.test(block: () -\u003e Unit) {\n  use { server -\u003e // use the server to automate cleanup\n    server.start() // start the server\n    block() // execute a function interacting with the server\n  } // stop the server\n}\n```\n[//]: # (@formatter:on)\n\u003c!--- KNIT Example07.kt --\u003e\n\n\u003c!--- INCLUDE\nimport io.github.cfraser.graphguard.Server\nimport io.github.cfraser.graphguard.Server.Plugin.DSL.plugin\nimport io.github.cfraser.graphguard.withNeo4j\n\n@Suppress(\"unused\")\nfun runExample08() {\n  withNeo4j {\n----- SUFFIX\n  }\n}\n--\u003e\n\n[//]: # (@formatter:off)\n```kotlin\nServer(\n  boltURI(),\n  plugin { // define plugin using DSL\n    intercept { _, message -\u003e message.also(::println) }\n    observe { event -\u003e println(event) }\n  })\n  .use { TODO(\"interact with the running server\") }\n```\n[//]: # (@formatter:on)\n\u003c!--- KNIT Example08.kt --\u003e\n\n#### Java\n\n[//]: # (@formatter:off)\n```java\nExecutor executor = Executors.newVirtualThreadPerTaskExecutor(); // run blocking operations on virtual threads\nServer.Plugin plugin = // implement blocking plugin; can't utilize Kotlin coroutines plugin interface in Java\n    new Server.Plugin.Blocking(executor) {\n      @NotNull\n      @Override\n      public Message interceptBlocking(@NotNull String session, @NotNull Bolt.Message message) {\n        System.out.println(message);\n        return message;\n      }\n\n      @Override\n      public void observeBlocking(@NotNull Server.Event event) {\n        System.out.println(event);\n      }\n    };\ntry (Server server = new Server(boltUri, plugin)) { // use server in try-with-resources to automate cleanup\n    server.start(); // start the server\n    /* TODO: interact with the running server */\n} // stop the running server\n```\n[//]: # (@formatter:on)\n\n### Documentation\n\nRefer to the [graph-guard website](https://c-fraser.github.io/graph-guard/api/).\n\n### Application\n\nDownload and run the `graph-guard-app` application.\n\n```shell\ncurl -OL https://github.com/c-fraser/graph-guard/releases/latest/download/graph-guard-app-shadow.tar\nmkdir graph-guard-app\ntar -xvf graph-guard-app.tar --strip-components=1 -C graph-guard-app\n./graph-guard-app/bin/graph-guard-app --help\n```\n\n#### Demo\n\nRefer to the *Docker Compose* [demo](https://github.com/c-fraser/graph-guard/blob/main/demo/run.sh).\n\n\u003cvideo src=\"docs/app-demo.webm\" controls width=\"100%\"\u003e\u003c/video\u003e\n\n## Libraries\n\nThe following `graph-guard-*` libraries provide types and/or implementations to facilitate\naugmenting [Server](https://c-fraser.github.io/graph-guard/api/graph-guard/io.github.cfraser.graphguard/-server/index.html)\nfunctionality.\n\n### graph-guard-validate\n\n`graph-guard-validate`\ndefines [Rule](https://c-fraser.github.io/graph-guard/api/graph-guard-validate/io.github.cfraser.graphguard.validate/-rule/index.html),\nwhich is the basis for query validation, through\nthe [Validator](https://c-fraser.github.io/graph-guard/api/graph-guard/io.github.cfraser.graphguard.plugin/-validator/index.html)\nplugin. [Schema](https://c-fraser.github.io/graph-guard/api/graph-guard-validate/io.github.cfraser.graphguard.validate/-schema/index.html)\nis\nthe [Rule](https://c-fraser.github.io/graph-guard/api/graph-guard-validate/io.github.cfraser.graphguard.validate/-rule/index.html)\nthat evaluates whether a Cypher query contains [schema](#schema) violations. Refer\nto [Patterns](https://c-fraser.github.io/graph-guard/api/graph-guard-validate/io.github.cfraser.graphguard.validate/-patterns/index.html)\nfor additional\nCypher [Rule](https://c-fraser.github.io/graph-guard/api/graph-guard-validate/io.github.cfraser.graphguard.validate/-rule/index.html)\nimplementations.\n\n### graph-guard-script\n\n[Script.evaluate](https://c-fraser.github.io/graph-guard/api/graph-guard-script/io.github.cfraser.graphguard.plugin/-script/-companion/evaluate.html)\nenables [plugins](https://c-fraser.github.io/graph-guard/api/graph-guard/io.github.cfraser.graphguard/-server/-plugin/index.html)\nto be compiled and loaded from\na [Kotlin script](https://kotlinlang.org/docs/custom-script-deps-tutorial.html).\nThe [Script.Context](https://c-fraser.github.io/graph-guard/api/graph-guard-script/io.github.cfraser.graphguard.plugin/-script/-context/index.html)\nexposes\na [DSL](https://c-fraser.github.io/graph-guard/api/graph-guard/io.github.cfraser.graphguard/-server/-plugin/-d-s-l/index.html)\nto\nbuild [plugins](https://c-fraser.github.io/graph-guard/api/graph-guard/io.github.cfraser.graphguard/-server/-plugin/index.html).\n\nFor example, use a [plugin script](#graph-guard-script) with the [Server](#design).\n\n\u003c!--- TEST_NAME Example09Test --\u003e \n\u003c!--- INCLUDE\nimport io.github.cfraser.graphguard.Server\nimport io.github.cfraser.graphguard.driver\nimport io.github.cfraser.graphguard.plugin.Script\nimport io.github.cfraser.graphguard.runMoviesQueries\nimport io.github.cfraser.graphguard.withNeo4j\n\nfun runExample09() {\n  withNeo4j {\n----- SUFFIX\n  }\n}\n--\u003e\n\n[//]: # (@formatter:off)\n```kotlin\nval script = \"\"\"\n@file:DependsOn(\n    \"io.github.resilience4j:resilience4j-ratelimiter:2.2.0\",\n    \"io.github.resilience4j:resilience4j-kotlin:2.2.0\")\n\nimport io.github.resilience4j.kotlin.ratelimiter.RateLimiterConfig\nimport io.github.resilience4j.kotlin.ratelimiter.executeSuspendFunction\nimport io.github.resilience4j.ratelimiter.RateLimiter\nimport java.util.concurrent.atomic.AtomicInteger\n\nplugin {\n  val rateLimiter = RateLimiter.of(\"message-limiter\", RateLimiterConfig {})\n  intercept { _, message -\u003e rateLimiter.executeSuspendFunction { message } }\n}\n\nplugin { \n  val messages = AtomicInteger()\n  intercept { _, message -\u003e\n    if (messages.getAndIncrement() == 0) println(message::class.simpleName)\n    message\n  }\n}\n\"\"\"\nval plugin = Script.evaluate(script)\nval server = Server(boltURI(), plugin)\nserver.test { server.driver.use(block = ::runMoviesQueries) }\n```\n[//]: # (@formatter:on)\n\n\u003e Script compilation and evaluation takes longer, thus the 10 second `wait`.\n\n\u003c!--- KNIT Example09.kt --\u003e \n\nThe code above prints the following message.\n\n```text\nHello\n```\n\n\u003c!--- TEST --\u003e\n\n## License\n\n    Copyright 2023 c-fraser\n    \n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n    \n        https://www.apache.org/licenses/LICENSE-2.0\n    \n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc-fraser%2Fgraph-guard","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fc-fraser%2Fgraph-guard","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc-fraser%2Fgraph-guard/lists"}