data_model 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +10 -0
- data/.rubocop.yml +4 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +80 -0
- data/config/sus.rb +2 -0
- data/fixtures/schema.rb +14 -0
- data/lib/data_model/model.rb +54 -0
- data/lib/data_model/registry.rb +44 -0
- data/lib/data_model/scanner.rb +67 -0
- data/lib/data_model/type.rb +48 -0
- data/lib/data_model/version.rb +3 -0
- data/lib/data_model.rb +28 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d6f87b58a7e7706f284041ccc8479520af309c3e632ef89311be431a11112273
|
4
|
+
data.tar.gz: e945d11a1ac73459be8b1c067eb8bc40cafe7f43a665cb069fecf880d413d91d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f6470deed88de3b60a5aad2e5926724f4d3255567d0668016ff6c13e1a82608ea71dc8b82ae82a4b0993771c222ad3a9353236243e6c0e8f8c4c0423614e8c71
|
7
|
+
data.tar.gz: 82434cccbdb292e7e416e46eb1eb353193ff39329008a107b2837d97b2c907228c79c62f3be16a2f5966c437c777372f2c08708e0acf5b688fb2f22232565dae
|
data/.editorconfig
ADDED
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
data_model (0.0.1)
|
5
|
+
zeitwerk (~> 2.6)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
ast (2.4.2)
|
11
|
+
backport (1.2.0)
|
12
|
+
benchmark (0.2.0)
|
13
|
+
diff-lcs (1.5.0)
|
14
|
+
e2mmap (0.1.0)
|
15
|
+
jaro_winkler (1.5.4)
|
16
|
+
json (2.6.2)
|
17
|
+
kramdown (2.4.0)
|
18
|
+
rexml
|
19
|
+
kramdown-parser-gfm (1.1.0)
|
20
|
+
kramdown (~> 2.0)
|
21
|
+
mini_portile2 (2.8.0)
|
22
|
+
nokogiri (1.13.8)
|
23
|
+
mini_portile2 (~> 2.8.0)
|
24
|
+
racc (~> 1.4)
|
25
|
+
parallel (1.22.1)
|
26
|
+
parser (3.1.2.1)
|
27
|
+
ast (~> 2.4.1)
|
28
|
+
racc (1.6.0)
|
29
|
+
rainbow (3.1.1)
|
30
|
+
regexp_parser (2.6.0)
|
31
|
+
reverse_markdown (2.1.1)
|
32
|
+
nokogiri
|
33
|
+
rexml (3.2.5)
|
34
|
+
rubocop (1.36.0)
|
35
|
+
json (~> 2.3)
|
36
|
+
parallel (~> 1.10)
|
37
|
+
parser (>= 3.1.2.1)
|
38
|
+
rainbow (>= 2.2.2, < 4.0)
|
39
|
+
regexp_parser (>= 1.8, < 3.0)
|
40
|
+
rexml (>= 3.2.5, < 4.0)
|
41
|
+
rubocop-ast (>= 1.20.1, < 2.0)
|
42
|
+
ruby-progressbar (~> 1.7)
|
43
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
44
|
+
rubocop-ast (1.21.0)
|
45
|
+
parser (>= 3.1.1.0)
|
46
|
+
ruby-progressbar (1.11.0)
|
47
|
+
solargraph (0.47.2)
|
48
|
+
backport (~> 1.2)
|
49
|
+
benchmark
|
50
|
+
bundler (>= 1.17.2)
|
51
|
+
diff-lcs (~> 1.4)
|
52
|
+
e2mmap
|
53
|
+
jaro_winkler (~> 1.5)
|
54
|
+
kramdown (~> 2.3)
|
55
|
+
kramdown-parser-gfm (~> 1.1)
|
56
|
+
parser (~> 3.0)
|
57
|
+
reverse_markdown (>= 1.0.5, < 3)
|
58
|
+
rubocop (>= 0.52)
|
59
|
+
thor (~> 1.0)
|
60
|
+
tilt (~> 2.0)
|
61
|
+
yard (~> 0.9, >= 0.9.24)
|
62
|
+
sus (0.14.0)
|
63
|
+
thor (1.2.1)
|
64
|
+
tilt (2.0.11)
|
65
|
+
unicode-display_width (2.3.0)
|
66
|
+
webrick (1.7.0)
|
67
|
+
yard (0.9.28)
|
68
|
+
webrick (~> 1.7.0)
|
69
|
+
zeitwerk (2.6.1)
|
70
|
+
|
71
|
+
PLATFORMS
|
72
|
+
ruby
|
73
|
+
|
74
|
+
DEPENDENCIES
|
75
|
+
data_model!
|
76
|
+
solargraph
|
77
|
+
sus (~> 0.14)
|
78
|
+
|
79
|
+
BUNDLED WITH
|
80
|
+
2.3.7
|
data/config/sus.rb
ADDED
data/fixtures/schema.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
module DataModel
|
2
|
+
module Model
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def defaults
|
6
|
+
{
|
7
|
+
# name of validator if a child, and validating a named property
|
8
|
+
property: nil,
|
9
|
+
|
10
|
+
# context passed to read and write
|
11
|
+
config: {},
|
12
|
+
types: nil,
|
13
|
+
children: [],
|
14
|
+
|
15
|
+
# default writer
|
16
|
+
write: ->(val) { val }
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate!(model)
|
21
|
+
model => {
|
22
|
+
property:, config:, types:,
|
23
|
+
read: Proc, write: Proc,
|
24
|
+
children: Array,
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def read(model, data)
|
29
|
+
validate! model
|
30
|
+
invoke(model, :read, data)
|
31
|
+
end
|
32
|
+
|
33
|
+
def write(model, data)
|
34
|
+
validate! model
|
35
|
+
invoke(model, :write, data)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def invoke(model, action, data)
|
41
|
+
fn = model.fetch(action)
|
42
|
+
|
43
|
+
case fn.arity
|
44
|
+
when 1
|
45
|
+
fn.call(data)
|
46
|
+
when 2
|
47
|
+
ctx = model.slice(:config, :types, :children)
|
48
|
+
fn.call(data, ctx)
|
49
|
+
else
|
50
|
+
raise "expected an arity of 1 or 2, got: #{fn.arity}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module DataModel
|
2
|
+
class Registry
|
3
|
+
def self.instance
|
4
|
+
@instance ||= new(defaults)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.register(name, type)
|
8
|
+
instance.register(name, type)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.defaults
|
12
|
+
{
|
13
|
+
string: Type.string,
|
14
|
+
hash: Type.hash
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(*defaults)
|
19
|
+
@types = {}
|
20
|
+
|
21
|
+
defaults.each do |d|
|
22
|
+
d.each do |k, s|
|
23
|
+
register(k, s)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def register(name, type)
|
29
|
+
@types[name] = Model.defaults.merge(type)
|
30
|
+
end
|
31
|
+
|
32
|
+
def type?(name)
|
33
|
+
@types.key?(name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def type(name, property = nil)
|
37
|
+
unless @types.key?(name)
|
38
|
+
raise "#{name} is not registered as a type"
|
39
|
+
end
|
40
|
+
|
41
|
+
@types.fetch(name).merge(property:)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module DataModel
|
2
|
+
module Scanner
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def scan(schema, registry = Registry.instance)
|
6
|
+
start = { validator: nil, property: nil, state: :start }
|
7
|
+
result = schema.each_with_index.reduce(start) do |context, (token, pos)|
|
8
|
+
context => {state:, validator:, property:}
|
9
|
+
peek = schema[pos + 1]
|
10
|
+
|
11
|
+
# detect named validators, a name could be any type so cannot be detected
|
12
|
+
# by ruby type and state. detect name by ensuring we are at the start
|
13
|
+
# of scanning, and the next element is a valid type.
|
14
|
+
if state == :start && registry.type?(peek)
|
15
|
+
context.merge(
|
16
|
+
property: token,
|
17
|
+
state: :named,
|
18
|
+
)
|
19
|
+
else
|
20
|
+
# with the special case of a name aside, we can detect the meaning
|
21
|
+
# of further tokens by their type and scanner state
|
22
|
+
case token
|
23
|
+
when Symbol
|
24
|
+
unless [:start, :named].include?(state)
|
25
|
+
raise "got a symbol at pos #{pos}, but validator already defined"
|
26
|
+
end
|
27
|
+
|
28
|
+
unless registry.type?(token)
|
29
|
+
raise "expected a type in pos #{pos}, but found #{token.inspect} which is not a registered type"
|
30
|
+
end
|
31
|
+
|
32
|
+
context.merge(
|
33
|
+
validator: registry.type(token, property),
|
34
|
+
state: :defined,
|
35
|
+
)
|
36
|
+
|
37
|
+
when Hash
|
38
|
+
unless state == :defined
|
39
|
+
raise "got a hash at pos #{pos}, but state is not :defined (#{state.inspect})"
|
40
|
+
end
|
41
|
+
|
42
|
+
context.merge(
|
43
|
+
validator: validator.merge(config: token),
|
44
|
+
state: :configured,
|
45
|
+
)
|
46
|
+
|
47
|
+
when Array
|
48
|
+
unless [:defined, :configured].include?(state)
|
49
|
+
raise "#{schema.inspect} at pos #{pos}: expected (String | Hash | Symbol | Array), got #{token.class.name}"
|
50
|
+
end
|
51
|
+
|
52
|
+
children = token.map { |s| scan(s, registry) }
|
53
|
+
|
54
|
+
context.merge(
|
55
|
+
validator: validator.merge(children:),
|
56
|
+
state: :complete,
|
57
|
+
)
|
58
|
+
else
|
59
|
+
raise "got token #{token.inspect} at position #{pos} which was unexpected given the scanner was in a state of #{state}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
result.fetch(:validator)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module DataModel
|
2
|
+
module Type
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def string
|
6
|
+
{
|
7
|
+
read: lambda do |val|
|
8
|
+
err = {}
|
9
|
+
|
10
|
+
unless val.is_a? String
|
11
|
+
err[:root] = "#{val} is not a string, it is a #{val.class.name}"
|
12
|
+
end
|
13
|
+
|
14
|
+
[val, err]
|
15
|
+
end
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def hash
|
20
|
+
{
|
21
|
+
read: lambda do |val, ctx|
|
22
|
+
err = {}
|
23
|
+
ctx => {config:, children:}
|
24
|
+
|
25
|
+
unless val.is_a? Hash
|
26
|
+
err[:root] = "#{val} is not a hash, it is a #{val.class.name}"
|
27
|
+
return [val, err]
|
28
|
+
end
|
29
|
+
|
30
|
+
# TODO: this needs to be wayyyy better
|
31
|
+
if !config[:open?] && (val.length > children.length)
|
32
|
+
err[:root] = "more elements found in closed hash then specified children"
|
33
|
+
end
|
34
|
+
|
35
|
+
children.each do |child|
|
36
|
+
child => {property:}
|
37
|
+
v, e = Model.read(child, val.fetch(property))
|
38
|
+
|
39
|
+
val = val.merge({ property => v })
|
40
|
+
err[property] = e
|
41
|
+
end
|
42
|
+
|
43
|
+
[val, err]
|
44
|
+
end
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/data_model.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "zeitwerk"
|
2
|
+
|
3
|
+
loader = Zeitwerk::Loader.for_gem
|
4
|
+
loader.setup
|
5
|
+
|
6
|
+
module DataModel
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def validate(schema, data, registry: Registry.instance)
|
10
|
+
_, err = read(schema, data, registry:)
|
11
|
+
err.empty? || err.values.all?(&:empty?)
|
12
|
+
end
|
13
|
+
|
14
|
+
def read(schema, data, registry: Registry.instance)
|
15
|
+
Model.read(compiled(schema, registry:), data)
|
16
|
+
end
|
17
|
+
|
18
|
+
def write(schema, data, registry: Registry.instance)
|
19
|
+
Model.write(compiled(schema, registry:), data)
|
20
|
+
end
|
21
|
+
|
22
|
+
def compiled(model, registry: Registry.instance)
|
23
|
+
if model in Array
|
24
|
+
model = Scanner.scan(model, registry)
|
25
|
+
end
|
26
|
+
model
|
27
|
+
end
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: data_model
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matt Briggs
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-10-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: zeitwerk
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sus
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.14'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.14'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: solargraph
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: A framework for describing and validating data.
|
56
|
+
email:
|
57
|
+
- matt@mattbriggs.net
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".editorconfig"
|
63
|
+
- ".rubocop.yml"
|
64
|
+
- Gemfile
|
65
|
+
- Gemfile.lock
|
66
|
+
- config/sus.rb
|
67
|
+
- fixtures/schema.rb
|
68
|
+
- lib/data_model.rb
|
69
|
+
- lib/data_model/model.rb
|
70
|
+
- lib/data_model/registry.rb
|
71
|
+
- lib/data_model/scanner.rb
|
72
|
+
- lib/data_model/type.rb
|
73
|
+
- lib/data_model/version.rb
|
74
|
+
homepage: https://github.com/mbriggs/data_model
|
75
|
+
licenses:
|
76
|
+
- MIT
|
77
|
+
metadata:
|
78
|
+
homepage_uri: https://github.com/mbriggs/data_model
|
79
|
+
source_code_uri: https://github.com/mbriggs/data_model
|
80
|
+
changelog_uri: https://github.com/mbriggs/data_model/releases
|
81
|
+
funding_uri: https://github.com/sponsors/mbriggs
|
82
|
+
rubygems_mfa_required: 'true'
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '2.7'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubygems_version: 3.3.7
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: Define a model for your data
|
102
|
+
test_files: []
|