polyn 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/.github/workflows/ruby.yml +39 -0
 - data/.gitignore +12 -0
 - data/.rspec +3 -0
 - data/.rubocop.yml +44 -0
 - data/.tool-versions +1 -0
 - data/.travis.yml +6 -0
 - data/CHANGELOG.md +5 -0
 - data/CODE_OF_CONDUCT.md +84 -0
 - data/Gemfile +32 -0
 - data/Gemfile.lock +84 -0
 - data/LICENSE.txt +21 -0
 - data/README.md +160 -0
 - data/Rakefile +12 -0
 - data/bin/console +15 -0
 - data/bin/setup +8 -0
 - data/docker-compose.yml +7 -0
 - data/lib/cloud-event-schema.json +187 -0
 - data/lib/polyn/cloud_event.rb +13 -0
 - data/lib/polyn/configuration.rb +30 -0
 - data/lib/polyn/errors/configuration_error.rb +10 -0
 - data/lib/polyn/errors/error.rb +13 -0
 - data/lib/polyn/errors/errors.rb +6 -0
 - data/lib/polyn/errors/schema_error.rb +10 -0
 - data/lib/polyn/errors/validation_error.rb +10 -0
 - data/lib/polyn/event.rb +138 -0
 - data/lib/polyn/naming.rb +100 -0
 - data/lib/polyn/pull_subscriber.rb +61 -0
 - data/lib/polyn/schema_store.rb +50 -0
 - data/lib/polyn/serializers/json.rb +136 -0
 - data/lib/polyn/utils/hash.rb +82 -0
 - data/lib/polyn/utils/string.rb +49 -0
 - data/lib/polyn/utils/utils.rb +21 -0
 - data/lib/polyn/version.rb +5 -0
 - data/lib/polyn.rb +116 -0
 - data/polyn.gemspec +52 -0
 - metadata +123 -0
 
| 
         @@ -0,0 +1,187 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            {
         
     | 
| 
      
 2 
     | 
    
         
            +
              "$schema": "http://json-schema.org/draft-07/schema#",
         
     | 
| 
      
 3 
     | 
    
         
            +
              "description": "CloudEvents Specification JSON Schema, extended for Polyn",
         
     | 
| 
      
 4 
     | 
    
         
            +
              "type": "object",
         
     | 
| 
      
 5 
     | 
    
         
            +
              "properties": {
         
     | 
| 
      
 6 
     | 
    
         
            +
                "id": {
         
     | 
| 
      
 7 
     | 
    
         
            +
                  "description": "Identifies the event.",
         
     | 
| 
      
 8 
     | 
    
         
            +
                  "$ref": "#/definitions/iddef",
         
     | 
| 
      
 9 
     | 
    
         
            +
                  "examples": [
         
     | 
| 
      
 10 
     | 
    
         
            +
                    "A234-1234-1234"
         
     | 
| 
      
 11 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 12 
     | 
    
         
            +
                },
         
     | 
| 
      
 13 
     | 
    
         
            +
                "source": {
         
     | 
| 
      
 14 
     | 
    
         
            +
                  "description": "Identifies the context in which an event happened.",
         
     | 
| 
      
 15 
     | 
    
         
            +
                  "$ref": "#/definitions/sourcedef",
         
     | 
| 
      
 16 
     | 
    
         
            +
                  "examples" : [
         
     | 
| 
      
 17 
     | 
    
         
            +
                    "https://github.com/cloudevents",
         
     | 
| 
      
 18 
     | 
    
         
            +
                    "mailto:cncf-wg-serverless@lists.cncf.io",
         
     | 
| 
      
 19 
     | 
    
         
            +
                    "urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66",
         
     | 
| 
      
 20 
     | 
    
         
            +
                    "cloudevents/spec/pull/123",
         
     | 
| 
      
 21 
     | 
    
         
            +
                    "/sensors/tn-1234567/alerts",
         
     | 
| 
      
 22 
     | 
    
         
            +
                    "1-555-123-4567"
         
     | 
| 
      
 23 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 24 
     | 
    
         
            +
                },
         
     | 
| 
      
 25 
     | 
    
         
            +
                "specversion": {
         
     | 
| 
      
 26 
     | 
    
         
            +
                  "description": "The version of the CloudEvents specification which the event uses.",
         
     | 
| 
      
 27 
     | 
    
         
            +
                  "$ref": "#/definitions/specversiondef",
         
     | 
| 
      
 28 
     | 
    
         
            +
                  "examples": [
         
     | 
| 
      
 29 
     | 
    
         
            +
                    "1.0"
         
     | 
| 
      
 30 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 31 
     | 
    
         
            +
                },
         
     | 
| 
      
 32 
     | 
    
         
            +
                "type": {
         
     | 
| 
      
 33 
     | 
    
         
            +
                  "description": "Describes the type of event related to the originating occurrence.",
         
     | 
| 
      
 34 
     | 
    
         
            +
                  "$ref": "#/definitions/typedef",
         
     | 
| 
      
 35 
     | 
    
         
            +
                  "examples" : [
         
     | 
| 
      
 36 
     | 
    
         
            +
                    "com.github.pull_request.opened",
         
     | 
| 
      
 37 
     | 
    
         
            +
                    "com.example.object.deleted.v2"
         
     | 
| 
      
 38 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 39 
     | 
    
         
            +
                },
         
     | 
| 
      
 40 
     | 
    
         
            +
                "datacontenttype": {
         
     | 
| 
      
 41 
     | 
    
         
            +
                  "description": "Content type of the data value. Must adhere to RFC 2046 format.",
         
     | 
| 
      
 42 
     | 
    
         
            +
                  "$ref": "#/definitions/datacontenttypedef",
         
     | 
| 
      
 43 
     | 
    
         
            +
                  "examples": [
         
     | 
| 
      
 44 
     | 
    
         
            +
                    "text/xml",
         
     | 
| 
      
 45 
     | 
    
         
            +
                    "application/json",
         
     | 
| 
      
 46 
     | 
    
         
            +
                    "image/png",
         
     | 
| 
      
 47 
     | 
    
         
            +
                    "multipart/form-data"
         
     | 
| 
      
 48 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 49 
     | 
    
         
            +
                },
         
     | 
| 
      
 50 
     | 
    
         
            +
                "dataschema": {
         
     | 
| 
      
 51 
     | 
    
         
            +
                  "description": "Identifies the schema that data adheres to.",
         
     | 
| 
      
 52 
     | 
    
         
            +
                  "$ref": "#/definitions/dataschemadef"
         
     | 
| 
      
 53 
     | 
    
         
            +
                },
         
     | 
| 
      
 54 
     | 
    
         
            +
                "subject": {
         
     | 
| 
      
 55 
     | 
    
         
            +
                  "description": "Describes the subject of the event in the context of the event producer (identified by source).",
         
     | 
| 
      
 56 
     | 
    
         
            +
                  "$ref": "#/definitions/subjectdef",
         
     | 
| 
      
 57 
     | 
    
         
            +
                  "examples": [
         
     | 
| 
      
 58 
     | 
    
         
            +
                    "mynewfile.jpg"
         
     | 
| 
      
 59 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 60 
     | 
    
         
            +
                },
         
     | 
| 
      
 61 
     | 
    
         
            +
                "time": {
         
     | 
| 
      
 62 
     | 
    
         
            +
                  "description": "Timestamp of when the occurrence happened. Must adhere to RFC 3339.",
         
     | 
| 
      
 63 
     | 
    
         
            +
                  "$ref": "#/definitions/timedef",
         
     | 
| 
      
 64 
     | 
    
         
            +
                  "examples": [
         
     | 
| 
      
 65 
     | 
    
         
            +
                    "2018-04-05T17:31:00Z"
         
     | 
| 
      
 66 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 67 
     | 
    
         
            +
                },
         
     | 
| 
      
 68 
     | 
    
         
            +
                "data": {
         
     | 
| 
      
 69 
     | 
    
         
            +
                  "description": "The event payload.",
         
     | 
| 
      
 70 
     | 
    
         
            +
                  "$ref": "#/definitions/datadef",
         
     | 
| 
      
 71 
     | 
    
         
            +
                  "examples": [
         
     | 
| 
      
 72 
     | 
    
         
            +
                    "<much wow=\"xml\"/>"
         
     | 
| 
      
 73 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 74 
     | 
    
         
            +
                },
         
     | 
| 
      
 75 
     | 
    
         
            +
                "data_base64": {
         
     | 
| 
      
 76 
     | 
    
         
            +
                  "description": "Base64 encoded event payload. Must adhere to RFC4648.",
         
     | 
| 
      
 77 
     | 
    
         
            +
                  "$ref": "#/definitions/data_base64def",
         
     | 
| 
      
 78 
     | 
    
         
            +
                  "examples": [
         
     | 
| 
      
 79 
     | 
    
         
            +
                    "Zm9vYg=="
         
     | 
| 
      
 80 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 81 
     | 
    
         
            +
                },
         
     | 
| 
      
 82 
     | 
    
         
            +
                "polyndata": {
         
     | 
| 
      
 83 
     | 
    
         
            +
                  "$ref": "#/definitions/polyndatadef",
         
     | 
| 
      
 84 
     | 
    
         
            +
                  "description": "Information about the client that produced the event and additional metadata",
         
     | 
| 
      
 85 
     | 
    
         
            +
                  "examples": [
         
     | 
| 
      
 86 
     | 
    
         
            +
                    {
         
     | 
| 
      
 87 
     | 
    
         
            +
                      "clientlang": "elixir",
         
     | 
| 
      
 88 
     | 
    
         
            +
                      "clientlangversion": "1.13.2",
         
     | 
| 
      
 89 
     | 
    
         
            +
                      "clientversion": "0.1.0"
         
     | 
| 
      
 90 
     | 
    
         
            +
                    }
         
     | 
| 
      
 91 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 92 
     | 
    
         
            +
                },
         
     | 
| 
      
 93 
     | 
    
         
            +
                "polyntrace": {
         
     | 
| 
      
 94 
     | 
    
         
            +
                  "$ref": "#/definitions/polyntracedef",
         
     | 
| 
      
 95 
     | 
    
         
            +
                  "description": "Previous events that led to this one",
         
     | 
| 
      
 96 
     | 
    
         
            +
                  "examples": [
         
     | 
| 
      
 97 
     | 
    
         
            +
                    [
         
     | 
| 
      
 98 
     | 
    
         
            +
                      {
         
     | 
| 
      
 99 
     | 
    
         
            +
                        "type": "<topic>",
         
     | 
| 
      
 100 
     | 
    
         
            +
                        "time": "2018-04-05T17:31:00Z",
         
     | 
| 
      
 101 
     | 
    
         
            +
                        "id": "<uuid>"
         
     | 
| 
      
 102 
     | 
    
         
            +
                      }
         
     | 
| 
      
 103 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 104 
     | 
    
         
            +
                  ]
         
     | 
| 
      
 105 
     | 
    
         
            +
                }
         
     | 
| 
      
 106 
     | 
    
         
            +
              },
         
     | 
| 
      
 107 
     | 
    
         
            +
              "required": ["id", "source", "specversion", "type"],
         
     | 
| 
      
 108 
     | 
    
         
            +
              "definitions": {
         
     | 
| 
      
 109 
     | 
    
         
            +
                "iddef": {
         
     | 
| 
      
 110 
     | 
    
         
            +
                  "type": "string",
         
     | 
| 
      
 111 
     | 
    
         
            +
                  "minLength": 1
         
     | 
| 
      
 112 
     | 
    
         
            +
                },
         
     | 
| 
      
 113 
     | 
    
         
            +
                "sourcedef": {
         
     | 
| 
      
 114 
     | 
    
         
            +
                  "type": "string",
         
     | 
| 
      
 115 
     | 
    
         
            +
                  "format": "uri-reference",
         
     | 
| 
      
 116 
     | 
    
         
            +
                  "minLength": 1
         
     | 
| 
      
 117 
     | 
    
         
            +
                },
         
     | 
| 
      
 118 
     | 
    
         
            +
                "specversiondef": {
         
     | 
| 
      
 119 
     | 
    
         
            +
                  "type": "string",
         
     | 
| 
      
 120 
     | 
    
         
            +
                  "minLength": 1
         
     | 
| 
      
 121 
     | 
    
         
            +
                },
         
     | 
| 
      
 122 
     | 
    
         
            +
                "typedef": {
         
     | 
| 
      
 123 
     | 
    
         
            +
                  "type": "string",
         
     | 
| 
      
 124 
     | 
    
         
            +
                  "minLength": 1
         
     | 
| 
      
 125 
     | 
    
         
            +
                },
         
     | 
| 
      
 126 
     | 
    
         
            +
                "datacontenttypedef": {
         
     | 
| 
      
 127 
     | 
    
         
            +
                  "type": ["string", "null"],
         
     | 
| 
      
 128 
     | 
    
         
            +
                  "minLength": 1
         
     | 
| 
      
 129 
     | 
    
         
            +
                },
         
     | 
| 
      
 130 
     | 
    
         
            +
                "dataschemadef": {
         
     | 
| 
      
 131 
     | 
    
         
            +
                  "type": ["string", "null"],
         
     | 
| 
      
 132 
     | 
    
         
            +
                  "format": "uri",
         
     | 
| 
      
 133 
     | 
    
         
            +
                  "minLength": 1
         
     | 
| 
      
 134 
     | 
    
         
            +
                },
         
     | 
| 
      
 135 
     | 
    
         
            +
                "subjectdef": {
         
     | 
| 
      
 136 
     | 
    
         
            +
                  "type": ["string", "null"],
         
     | 
| 
      
 137 
     | 
    
         
            +
                  "minLength": 1
         
     | 
| 
      
 138 
     | 
    
         
            +
                },
         
     | 
| 
      
 139 
     | 
    
         
            +
                "timedef": {
         
     | 
| 
      
 140 
     | 
    
         
            +
                  "type": ["string", "null"],
         
     | 
| 
      
 141 
     | 
    
         
            +
                  "format": "date-time",
         
     | 
| 
      
 142 
     | 
    
         
            +
                  "minLength": 1
         
     | 
| 
      
 143 
     | 
    
         
            +
                },
         
     | 
| 
      
 144 
     | 
    
         
            +
                "datadef": {
         
     | 
| 
      
 145 
     | 
    
         
            +
                  "type": ["object", "string", "number", "array", "boolean", "null"]
         
     | 
| 
      
 146 
     | 
    
         
            +
                },
         
     | 
| 
      
 147 
     | 
    
         
            +
                "data_base64def": {
         
     | 
| 
      
 148 
     | 
    
         
            +
                  "type": ["string", "null"],
         
     | 
| 
      
 149 
     | 
    
         
            +
                  "contentEncoding": "base64"
         
     | 
| 
      
 150 
     | 
    
         
            +
                },
         
     | 
| 
      
 151 
     | 
    
         
            +
                "polyndatadef": {
         
     | 
| 
      
 152 
     | 
    
         
            +
                  "type": "object",
         
     | 
| 
      
 153 
     | 
    
         
            +
                  "properties": {
         
     | 
| 
      
 154 
     | 
    
         
            +
                    "clientlang": {
         
     | 
| 
      
 155 
     | 
    
         
            +
                      "type": "string"
         
     | 
| 
      
 156 
     | 
    
         
            +
                    },
         
     | 
| 
      
 157 
     | 
    
         
            +
                    "clientlangversion": {
         
     | 
| 
      
 158 
     | 
    
         
            +
                      "type": "string"
         
     | 
| 
      
 159 
     | 
    
         
            +
                    },
         
     | 
| 
      
 160 
     | 
    
         
            +
                    "clientversion": {
         
     | 
| 
      
 161 
     | 
    
         
            +
                      "type": "string"
         
     | 
| 
      
 162 
     | 
    
         
            +
                    }
         
     | 
| 
      
 163 
     | 
    
         
            +
                  },
         
     | 
| 
      
 164 
     | 
    
         
            +
                  "required": ["clientlang", "clientlangversion", "clientversion"]
         
     | 
| 
      
 165 
     | 
    
         
            +
                },
         
     | 
| 
      
 166 
     | 
    
         
            +
                "polyntracedef": {
         
     | 
| 
      
 167 
     | 
    
         
            +
                  "type" : "array",
         
     | 
| 
      
 168 
     | 
    
         
            +
                  "items": {
         
     | 
| 
      
 169 
     | 
    
         
            +
                    "type": "object",
         
     | 
| 
      
 170 
     | 
    
         
            +
                    "properties": {
         
     | 
| 
      
 171 
     | 
    
         
            +
                      "type": {
         
     | 
| 
      
 172 
     | 
    
         
            +
                        "type": "string"
         
     | 
| 
      
 173 
     | 
    
         
            +
                      },
         
     | 
| 
      
 174 
     | 
    
         
            +
                      "time": {
         
     | 
| 
      
 175 
     | 
    
         
            +
                        "type": "string",
         
     | 
| 
      
 176 
     | 
    
         
            +
                        "format": "date-time"
         
     | 
| 
      
 177 
     | 
    
         
            +
                      },
         
     | 
| 
      
 178 
     | 
    
         
            +
                      "id"  : {
         
     | 
| 
      
 179 
     | 
    
         
            +
                        "type": "string",
         
     | 
| 
      
 180 
     | 
    
         
            +
                        "format": "uuid"
         
     | 
| 
      
 181 
     | 
    
         
            +
                      }
         
     | 
| 
      
 182 
     | 
    
         
            +
                    },
         
     | 
| 
      
 183 
     | 
    
         
            +
                    "required": ["type", "time", "id"]
         
     | 
| 
      
 184 
     | 
    
         
            +
                  }
         
     | 
| 
      
 185 
     | 
    
         
            +
                }
         
     | 
| 
      
 186 
     | 
    
         
            +
              }
         
     | 
| 
      
 187 
     | 
    
         
            +
            }
         
     | 
| 
         @@ -0,0 +1,13 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Polyn
         
     | 
| 
      
 4 
     | 
    
         
            +
              ##
         
     | 
| 
      
 5 
     | 
    
         
            +
              # Access cloud event information
         
     | 
| 
      
 6 
     | 
    
         
            +
              class CloudEvent
         
     | 
| 
      
 7 
     | 
    
         
            +
                def self.to_h
         
     | 
| 
      
 8 
     | 
    
         
            +
                  path = File.expand_path(File.join(File.dirname(__FILE__), "../cloud-event-schema.json"))
         
     | 
| 
      
 9 
     | 
    
         
            +
                  file = File.open(path)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  JSON.parse(file.read)
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,30 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Polyn
         
     | 
| 
      
 4 
     | 
    
         
            +
              ##
         
     | 
| 
      
 5 
     | 
    
         
            +
              # Configuration data for Polyn
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Configuration
         
     | 
| 
      
 7 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @domain      = nil
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @source_root = nil
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def domain
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @domain ||= Polyn::Naming.validate_domain_name!(@domain)
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def domain=(name)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  Polyn::Naming.validate_domain_name!(name)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @domain = name
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def source_root
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @source_root ||= Polyn::Naming.validate_source_root!(@source_root)
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def source_root=(name)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  Polyn::Naming.validate_source_root!(name)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @source_root = name
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
              end
         
     | 
| 
      
 30 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/polyn/event.rb
    ADDED
    
    | 
         @@ -0,0 +1,138 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            # Copyright 2021-2022 Spiff, Inc.
         
     | 
| 
      
 4 
     | 
    
         
            +
            #
         
     | 
| 
      
 5 
     | 
    
         
            +
            # Permission is hereby granted, free of charge, to any person obtaining a copy of this
         
     | 
| 
      
 6 
     | 
    
         
            +
            # software and associated documentation files (the "Software"), to deal in the Software
         
     | 
| 
      
 7 
     | 
    
         
            +
            # without restriction, including without limitation the rights to use, copy, modify, merge,
         
     | 
| 
      
 8 
     | 
    
         
            +
            # publish, distribute, sublicense, and/or sell copies of the Software, and to permit
         
     | 
| 
      
 9 
     | 
    
         
            +
            # persons to whom the Software is furnished to do so, subject to the following conditions:
         
     | 
| 
      
 10 
     | 
    
         
            +
            #
         
     | 
| 
      
 11 
     | 
    
         
            +
            # The above copyright notice and this permission notice shall be included in all copies or
         
     | 
| 
      
 12 
     | 
    
         
            +
            # substantial portions of the Software.
         
     | 
| 
      
 13 
     | 
    
         
            +
            #
         
     | 
| 
      
 14 
     | 
    
         
            +
            # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
         
     | 
| 
      
 15 
     | 
    
         
            +
            # NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         
     | 
| 
      
 16 
     | 
    
         
            +
            # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
         
     | 
| 
      
 17 
     | 
    
         
            +
            # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         
     | 
| 
      
 18 
     | 
    
         
            +
            # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            module Polyn
         
     | 
| 
      
 21 
     | 
    
         
            +
              ##
         
     | 
| 
      
 22 
     | 
    
         
            +
              # Represents an event. Events follow the [Cloudevents](https://github.com/cloudevents)
         
     | 
| 
      
 23 
     | 
    
         
            +
              # specification.
         
     | 
| 
      
 24 
     | 
    
         
            +
              class Event
         
     | 
| 
      
 25 
     | 
    
         
            +
                CLOUD_EVENT_VERSION = "1.0"
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                class UnsupportedVersionError < Errors::Error; end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                ##
         
     | 
| 
      
 30 
     | 
    
         
            +
                # @return [String] the cloud event version
         
     | 
| 
      
 31 
     | 
    
         
            +
                attr_reader :specversion
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                ##
         
     | 
| 
      
 34 
     | 
    
         
            +
                # @return [String] event id
         
     | 
| 
      
 35 
     | 
    
         
            +
                attr_reader :id
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                ##
         
     | 
| 
      
 38 
     | 
    
         
            +
                # @return [String] event type
         
     | 
| 
      
 39 
     | 
    
         
            +
                attr_reader :type
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                ##
         
     | 
| 
      
 42 
     | 
    
         
            +
                # @return [String] event source
         
     | 
| 
      
 43 
     | 
    
         
            +
                attr_reader :source
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                ##
         
     | 
| 
      
 46 
     | 
    
         
            +
                # @return [String] time of event creation
         
     | 
| 
      
 47 
     | 
    
         
            +
                attr_reader :time
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                ##
         
     | 
| 
      
 50 
     | 
    
         
            +
                # @return [String] the data content type
         
     | 
| 
      
 51 
     | 
    
         
            +
                attr_accessor :datacontenttype
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                ##
         
     | 
| 
      
 54 
     | 
    
         
            +
                # @return [String] the data
         
     | 
| 
      
 55 
     | 
    
         
            +
                attr_reader :data
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                ##
         
     | 
| 
      
 58 
     | 
    
         
            +
                # @return [Array] Previous events that led to this one
         
     | 
| 
      
 59 
     | 
    
         
            +
                attr_reader :polyntrace
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                ##
         
     | 
| 
      
 62 
     | 
    
         
            +
                # @return [Hash] Represents the information about the client that published the event
         
     | 
| 
      
 63 
     | 
    
         
            +
                # as well as additional metadata
         
     | 
| 
      
 64 
     | 
    
         
            +
                attr_reader :polyndata
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                def initialize(hash)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  @specversion = hash.key?(:specversion) ? hash[:specversion] : "1.0"
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                  unless Gem::Dependency.new("", "~> #{CLOUD_EVENT_VERSION}").match?("", @specversion)
         
     | 
| 
      
 70 
     | 
    
         
            +
                    raise UnsupportedVersionError, "Unsupported version: '#{hash[:specversion]}'"
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                  @id              = hash.fetch(:id, SecureRandom.uuid)
         
     | 
| 
      
 74 
     | 
    
         
            +
                  @type            = self.class.full_type(hash.fetch(:type))
         
     | 
| 
      
 75 
     | 
    
         
            +
                  @source          = self.class.full_source(hash[:source])
         
     | 
| 
      
 76 
     | 
    
         
            +
                  @time            = hash.fetch(:time, Time.now.utc.iso8601)
         
     | 
| 
      
 77 
     | 
    
         
            +
                  @data            = hash.fetch(:data)
         
     | 
| 
      
 78 
     | 
    
         
            +
                  @datacontenttype = hash.fetch(:datacontenttype, "application/json")
         
     | 
| 
      
 79 
     | 
    
         
            +
                  @polyntrace      = self.class.build_polyntrace(hash[:triggered_by])
         
     | 
| 
      
 80 
     | 
    
         
            +
                  @polyndata       = {
         
     | 
| 
      
 81 
     | 
    
         
            +
                    clientlang:        "ruby",
         
     | 
| 
      
 82 
     | 
    
         
            +
                    clientlangversion: RUBY_VERSION,
         
     | 
| 
      
 83 
     | 
    
         
            +
                    clientversion:     Polyn::VERSION,
         
     | 
| 
      
 84 
     | 
    
         
            +
                  }
         
     | 
| 
      
 85 
     | 
    
         
            +
                end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                def to_h
         
     | 
| 
      
 88 
     | 
    
         
            +
                  {
         
     | 
| 
      
 89 
     | 
    
         
            +
                    "specversion"     => specversion,
         
     | 
| 
      
 90 
     | 
    
         
            +
                    "id"              => id,
         
     | 
| 
      
 91 
     | 
    
         
            +
                    "type"            => type,
         
     | 
| 
      
 92 
     | 
    
         
            +
                    "source"          => source,
         
     | 
| 
      
 93 
     | 
    
         
            +
                    "time"            => time,
         
     | 
| 
      
 94 
     | 
    
         
            +
                    "data"            => Utils::Hash.deep_stringify_keys(data),
         
     | 
| 
      
 95 
     | 
    
         
            +
                    "datacontenttype" => datacontenttype,
         
     | 
| 
      
 96 
     | 
    
         
            +
                    "polyntrace"      => Utils::Hash.deep_stringify_keys(polyntrace),
         
     | 
| 
      
 97 
     | 
    
         
            +
                    "polyndata"       => Utils::Hash.deep_stringify_keys(polyndata),
         
     | 
| 
      
 98 
     | 
    
         
            +
                  }
         
     | 
| 
      
 99 
     | 
    
         
            +
                end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                ##
         
     | 
| 
      
 102 
     | 
    
         
            +
                # Get the Event `source` prefixed with reverse domain name
         
     | 
| 
      
 103 
     | 
    
         
            +
                def self.full_source(source = nil)
         
     | 
| 
      
 104 
     | 
    
         
            +
                  root = Polyn.configuration.source_root
         
     | 
| 
      
 105 
     | 
    
         
            +
                  name = Polyn::Naming.dot_to_colon("#{domain}:#{root}")
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                  if source
         
     | 
| 
      
 108 
     | 
    
         
            +
                    Polyn::Naming.validate_source_name!(source)
         
     | 
| 
      
 109 
     | 
    
         
            +
                    "#{name}:#{Polyn::Naming.dot_to_colon(source)}"
         
     | 
| 
      
 110 
     | 
    
         
            +
                  else
         
     | 
| 
      
 111 
     | 
    
         
            +
                    name
         
     | 
| 
      
 112 
     | 
    
         
            +
                  end
         
     | 
| 
      
 113 
     | 
    
         
            +
                end
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                ##
         
     | 
| 
      
 116 
     | 
    
         
            +
                # Get the Event `type` prefixed with reverse domain name
         
     | 
| 
      
 117 
     | 
    
         
            +
                def self.full_type(type)
         
     | 
| 
      
 118 
     | 
    
         
            +
                  Polyn::Naming.validate_event_type!(type)
         
     | 
| 
      
 119 
     | 
    
         
            +
                  "#{domain}.#{Polyn::Naming.trim_domain_prefix(type)}"
         
     | 
| 
      
 120 
     | 
    
         
            +
                end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                ##
         
     | 
| 
      
 123 
     | 
    
         
            +
                # Use a triggering event to build the polyntrace of a new event
         
     | 
| 
      
 124 
     | 
    
         
            +
                def self.build_polyntrace(triggered_by)
         
     | 
| 
      
 125 
     | 
    
         
            +
                  return [] unless triggered_by
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
                  triggered_by.polyntrace.concat([{
         
     | 
| 
      
 128 
     | 
    
         
            +
                    id:   triggered_by.id,
         
     | 
| 
      
 129 
     | 
    
         
            +
                    type: triggered_by.type,
         
     | 
| 
      
 130 
     | 
    
         
            +
                    time: triggered_by.time,
         
     | 
| 
      
 131 
     | 
    
         
            +
                  }])
         
     | 
| 
      
 132 
     | 
    
         
            +
                end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                def self.domain
         
     | 
| 
      
 135 
     | 
    
         
            +
                  Polyn.configuration.domain
         
     | 
| 
      
 136 
     | 
    
         
            +
                end
         
     | 
| 
      
 137 
     | 
    
         
            +
              end
         
     | 
| 
      
 138 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/polyn/naming.rb
    ADDED
    
    | 
         @@ -0,0 +1,100 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Polyn
         
     | 
| 
      
 4 
     | 
    
         
            +
              ##
         
     | 
| 
      
 5 
     | 
    
         
            +
              # Methods for formatting and validating names of fields
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Naming
         
     | 
| 
      
 7 
     | 
    
         
            +
                ##
         
     | 
| 
      
 8 
     | 
    
         
            +
                # Convert a dot separated name into a colon separated name
         
     | 
| 
      
 9 
     | 
    
         
            +
                def self.dot_to_colon(str)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  str.gsub(".", ":")
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                ##
         
     | 
| 
      
 14 
     | 
    
         
            +
                # Validate that the configured `domain` is in the correct format
         
     | 
| 
      
 15 
     | 
    
         
            +
                def self.validate_domain_name!(name)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  if name.is_a?(String) && name.match?(/\A[a-z0-9]+(?:(?:\.|:)[a-z0-9]+)*\z/)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    name
         
     | 
| 
      
 18 
     | 
    
         
            +
                  else
         
     | 
| 
      
 19 
     | 
    
         
            +
                    raise Polyn::Errors::ConfigurationError,
         
     | 
| 
      
 20 
     | 
    
         
            +
                      "You must configure the `domain` for Polyn. It must be lowercase, alphanumeric and dot/colon separated, got #{name}"
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                ##
         
     | 
| 
      
 25 
     | 
    
         
            +
                # Validate the `source` name
         
     | 
| 
      
 26 
     | 
    
         
            +
                def self.validate_source_name(name)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  if name.is_a?(String) && name.match?(/\A[a-z0-9]+(?:(?:\.|:)[a-z0-9]+)*\z/)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    true
         
     | 
| 
      
 29 
     | 
    
         
            +
                  else
         
     | 
| 
      
 30 
     | 
    
         
            +
                    "Event source must be lowercase, alphanumeric and dot/colon separated, got #{name}"
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                ##
         
     | 
| 
      
 35 
     | 
    
         
            +
                # Validate the `source` name and raise if invalid
         
     | 
| 
      
 36 
     | 
    
         
            +
                def self.validate_source_name!(name)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  message = validate_source_name(name)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  if message == true
         
     | 
| 
      
 39 
     | 
    
         
            +
                    name
         
     | 
| 
      
 40 
     | 
    
         
            +
                  else
         
     | 
| 
      
 41 
     | 
    
         
            +
                    raise Polyn::Errors::ValidationError, message
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                ##
         
     | 
| 
      
 46 
     | 
    
         
            +
                # Validate that the configured `source_root` is in the correct format
         
     | 
| 
      
 47 
     | 
    
         
            +
                def self.validate_source_root!(name)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  message = validate_source_name(name)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  if message == true
         
     | 
| 
      
 50 
     | 
    
         
            +
                    name
         
     | 
| 
      
 51 
     | 
    
         
            +
                  else
         
     | 
| 
      
 52 
     | 
    
         
            +
                    raise Polyn::Errors::ConfigurationError,
         
     | 
| 
      
 53 
     | 
    
         
            +
                      "You must configure the `source_root` for Polyn. #{message}"
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                ##
         
     | 
| 
      
 58 
     | 
    
         
            +
                # Validate the event type
         
     | 
| 
      
 59 
     | 
    
         
            +
                def self.validate_event_type!(name)
         
     | 
| 
      
 60 
     | 
    
         
            +
                  if name.is_a?(String) && name.match?(/\A[a-z0-9]+(?:\.[a-z0-9]+)*\z/)
         
     | 
| 
      
 61 
     | 
    
         
            +
                    name
         
     | 
| 
      
 62 
     | 
    
         
            +
                  else
         
     | 
| 
      
 63 
     | 
    
         
            +
                    raise Polyn::Errors::ValidationError,
         
     | 
| 
      
 64 
     | 
    
         
            +
                      "Event types must be lowercase, alphanumeric and dot separated"
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                ##
         
     | 
| 
      
 69 
     | 
    
         
            +
                # Remove the `domain` name from the beginning of a string
         
     | 
| 
      
 70 
     | 
    
         
            +
                def self.trim_domain_prefix(str)
         
     | 
| 
      
 71 
     | 
    
         
            +
                  str = str.sub("#{domain}.", "")
         
     | 
| 
      
 72 
     | 
    
         
            +
                  str.sub("#{dot_to_colon(domain)}:", "")
         
     | 
| 
      
 73 
     | 
    
         
            +
                end
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                ##
         
     | 
| 
      
 76 
     | 
    
         
            +
                # Create a consumer name from a source and type
         
     | 
| 
      
 77 
     | 
    
         
            +
                def self.consumer_name(type, source = nil)
         
     | 
| 
      
 78 
     | 
    
         
            +
                  validate_event_type!(type)
         
     | 
| 
      
 79 
     | 
    
         
            +
                  type = trim_domain_prefix(type)
         
     | 
| 
      
 80 
     | 
    
         
            +
                  type = type.gsub(".", "_")
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                  root = Polyn.configuration.source_root
         
     | 
| 
      
 83 
     | 
    
         
            +
                  root = root.gsub(".", "_")
         
     | 
| 
      
 84 
     | 
    
         
            +
                  root = root.gsub(":", "_")
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                  if source
         
     | 
| 
      
 87 
     | 
    
         
            +
                    validate_source_name!(source)
         
     | 
| 
      
 88 
     | 
    
         
            +
                    source = source.gsub(".", "_")
         
     | 
| 
      
 89 
     | 
    
         
            +
                    source = source.gsub(":", "_")
         
     | 
| 
      
 90 
     | 
    
         
            +
                    [root, source, type].join("_")
         
     | 
| 
      
 91 
     | 
    
         
            +
                  else
         
     | 
| 
      
 92 
     | 
    
         
            +
                    [root, type].join("_")
         
     | 
| 
      
 93 
     | 
    
         
            +
                  end
         
     | 
| 
      
 94 
     | 
    
         
            +
                end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                def self.domain
         
     | 
| 
      
 97 
     | 
    
         
            +
                  Polyn.configuration.domain
         
     | 
| 
      
 98 
     | 
    
         
            +
                end
         
     | 
| 
      
 99 
     | 
    
         
            +
              end
         
     | 
| 
      
 100 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,61 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Polyn
         
     | 
| 
      
 4 
     | 
    
         
            +
              ##
         
     | 
| 
      
 5 
     | 
    
         
            +
              # Wrapper around nats-pure that can validate polyn messages
         
     | 
| 
      
 6 
     | 
    
         
            +
              class PullSubscriber
         
     | 
| 
      
 7 
     | 
    
         
            +
                ##
         
     | 
| 
      
 8 
     | 
    
         
            +
                # @param fields [Object] :nats - Connected NATS instance from `NATS.connect`
         
     | 
| 
      
 9 
     | 
    
         
            +
                # @param fields [String] :type - The type of event
         
     | 
| 
      
 10 
     | 
    
         
            +
                # @option fields [String] :source - If the `source` portion of the consumer name
         
     | 
| 
      
 11 
     | 
    
         
            +
                # is more than the `source_root`
         
     | 
| 
      
 12 
     | 
    
         
            +
                def initialize(fields)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @nats          = fields.fetch(:nats)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @type          = fields.fetch(:type)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @type          = Polyn::Naming.trim_domain_prefix(@type)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @consumer_name = Polyn::Naming.consumer_name(@type, fields[:source])
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @stream        = @nats.jetstream.find_stream_name_by_subject(@type)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  self.class.validate_consumer_exists!(@nats, @stream, @consumer_name)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @psub          = @nats.jetstream.pull_subscribe(@type, @consumer_name)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @store_name    = store_name(fields)
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                # nats-pure will create a consumer if the one you passed does not exist.
         
     | 
| 
      
 24 
     | 
    
         
            +
                # Polyn wants to avoid this functionality and instead encourage
         
     | 
| 
      
 25 
     | 
    
         
            +
                # consumer creation in the centralized `events` codebase so that
         
     | 
| 
      
 26 
     | 
    
         
            +
                # it's documented, discoverable, and polyn-cli can manage it
         
     | 
| 
      
 27 
     | 
    
         
            +
                def self.validate_consumer_exists!(nats, stream, consumer_name)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  nats.jetstream.consumer_info(stream, consumer_name)
         
     | 
| 
      
 29 
     | 
    
         
            +
                rescue NATS::JetStream::Error::NotFound
         
     | 
| 
      
 30 
     | 
    
         
            +
                  raise Polyn::Errors::ValidationError,
         
     | 
| 
      
 31 
     | 
    
         
            +
                    "Consumer #{consumer_name} does not exist. Use polyn-cli to create"\
         
     | 
| 
      
 32 
     | 
    
         
            +
                    "it before attempting to subscribe"
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                # fetch makes a request to be delivered more messages from a pull consumer.
         
     | 
| 
      
 36 
     | 
    
         
            +
                #
         
     | 
| 
      
 37 
     | 
    
         
            +
                # @param batch [Fixnum] Number of messages to pull from the stream.
         
     | 
| 
      
 38 
     | 
    
         
            +
                # @param params [Hash] Options to customize the fetch request.
         
     | 
| 
      
 39 
     | 
    
         
            +
                # @option params [Float] :timeout Duration of the fetch request before it expires.
         
     | 
| 
      
 40 
     | 
    
         
            +
                # @return [Array<NATS::Msg>]
         
     | 
| 
      
 41 
     | 
    
         
            +
                def fetch(batch = 1, params = {})
         
     | 
| 
      
 42 
     | 
    
         
            +
                  msgs = @psub.fetch(batch, params)
         
     | 
| 
      
 43 
     | 
    
         
            +
                  msgs.map do |msg|
         
     | 
| 
      
 44 
     | 
    
         
            +
                    event    = Polyn::Serializers::Json.deserialize(@nats, msg.data, store_name: @store_name)
         
     | 
| 
      
 45 
     | 
    
         
            +
                    if event.is_a?(Polyn::Errors::Error)
         
     | 
| 
      
 46 
     | 
    
         
            +
                      msg.term
         
     | 
| 
      
 47 
     | 
    
         
            +
                      raise event
         
     | 
| 
      
 48 
     | 
    
         
            +
                    end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                    msg.data = event
         
     | 
| 
      
 51 
     | 
    
         
            +
                    msg
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                private
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                def store_name(opts)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  opts.fetch(:store_name, Polyn::SchemaStore.store_name)
         
     | 
| 
      
 59 
     | 
    
         
            +
                end
         
     | 
| 
      
 60 
     | 
    
         
            +
              end
         
     | 
| 
      
 61 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,50 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Polyn
         
     | 
| 
      
 4 
     | 
    
         
            +
              ##
         
     | 
| 
      
 5 
     | 
    
         
            +
              # Persisting and interacting with persisted schemas
         
     | 
| 
      
 6 
     | 
    
         
            +
              class SchemaStore
         
     | 
| 
      
 7 
     | 
    
         
            +
                STORE_NAME = "POLYN_SCHEMAS"
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                ##
         
     | 
| 
      
 10 
     | 
    
         
            +
                # Persist a schema. In prod/dev schemas should have already been persisted via
         
     | 
| 
      
 11 
     | 
    
         
            +
                # the Polyn CLI.
         
     | 
| 
      
 12 
     | 
    
         
            +
                def self.save(nats, type, schema, **opts)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  json_schema?(schema)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  kv = nats.jetstream.key_value(store_name(**opts))
         
     | 
| 
      
 15 
     | 
    
         
            +
                  kv.put(type, JSON.generate(schema))
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def self.json_schema?(schema)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  JSONSchemer.schema(schema)
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def self.get!(nats, type, **opts)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  result = get(nats, type, **opts)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  raise result if result.is_a?(Polyn::Errors::SchemaError)
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  result
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def self.get(nats, type, **opts)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  kv    = nats.jetstream.key_value(store_name(**opts))
         
     | 
| 
      
 31 
     | 
    
         
            +
                  entry = kv.get(type)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  entry.value
         
     | 
| 
      
 33 
     | 
    
         
            +
                rescue NATS::KeyValue::BucketNotFoundError
         
     | 
| 
      
 34 
     | 
    
         
            +
                  Polyn::Errors::SchemaError.new(
         
     | 
| 
      
 35 
     | 
    
         
            +
                    "The Schema Store has not been setup on your NATS server. Make sure you use "\
         
     | 
| 
      
 36 
     | 
    
         
            +
                    "the Polyn CLI to create it",
         
     | 
| 
      
 37 
     | 
    
         
            +
                  )
         
     | 
| 
      
 38 
     | 
    
         
            +
                rescue NATS::JetStream::Error::NotFound
         
     | 
| 
      
 39 
     | 
    
         
            +
                  Polyn::Errors::SchemaError.new(
         
     | 
| 
      
 40 
     | 
    
         
            +
                    "Schema for #{type} does not exist. Make sure it's "\
         
     | 
| 
      
 41 
     | 
    
         
            +
                    "been added to your `events` codebase and has been loaded "\
         
     | 
| 
      
 42 
     | 
    
         
            +
                    "into the schema store on your NATS server",
         
     | 
| 
      
 43 
     | 
    
         
            +
                  )
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                def self.store_name(**opts)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  opts.fetch(:name, STORE_NAME)
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
              end
         
     | 
| 
      
 50 
     | 
    
         
            +
            end
         
     |