data_model 0.0.1
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/.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: []
|