dry-doc 0.0.1 → 0.0.2
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 +4 -4
- data/Gemfile +1 -1
- data/README.md +73 -3
- data/lib/dry/doc.rb +32 -1
- data/lib/dry/doc/namespace.rb +51 -8
- data/lib/dry/doc/object.rb +52 -0
- data/lib/dry/doc/schema.rb +45 -9
- data/lib/dry/doc/version.rb +1 -1
- metadata +3 -3
- data/lib/dry/doc/value.rb +0 -79
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65f2898bcc0d29121f1ef94ae8a947be45b0e184
|
4
|
+
data.tar.gz: fc3fb4d684aa8b2112187ef42a9566688b4efd65
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cffa125c73f7ee00ea837b2abf781bc3d96ba3c175d0a2285ac2515db473fc39869e0b7bf56e81bd3d95f881240cc63a1a6acd85b250f58cd7524980dc538df4
|
7
|
+
data.tar.gz: 1e5e30e6ae417759bd6f508fdcb00100a86028375dcb160c2a7441f606aaa6de530d35d59476eeedb408ccf9dbab3fe1f7c8649ef2420e397d918d1680a9bd83
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,78 @@
|
|
1
1
|
# Dry::Doc
|
2
2
|
|
3
|
-
|
3
|
+
```
|
4
|
+
module MyApi
|
5
|
+
extend Dry::Doc::Namespace
|
6
|
+
|
7
|
+
define :User do
|
8
|
+
# dry-struct definitions
|
9
|
+
attribute :name, types::String
|
10
|
+
attribute :age, types::Int.optional
|
11
|
+
end
|
12
|
+
|
13
|
+
define :Post do
|
14
|
+
attribute :author, types.instance(User)
|
15
|
+
attribute :title, types::String
|
16
|
+
attribute :body, types::String
|
17
|
+
attribute :created_at, types::DateTime
|
18
|
+
end
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
Get serializable objects that check their input
|
23
|
+
|
24
|
+
```
|
25
|
+
u = MyApi::User.new(name: 'James', age: nil)
|
26
|
+
u.as_json
|
27
|
+
# => { name: "James", age: nil}
|
28
|
+
|
29
|
+
v = MyApi::User.new(name: 0, age: '')
|
30
|
+
# => Dry::Struct::Error: [MyApi::User.new] :name is missing in Hash input
|
31
|
+
|
32
|
+
p = MyApi::Post.new(author: u, title: '...', body: '...', created_at: DateTime.now).as_json
|
33
|
+
# => { author: { name: "James", age: null }, title: "...", body: "...", created_at: "..." }
|
34
|
+
|
35
|
+
```
|
36
|
+
|
37
|
+
and provide an open api definition (with e.g. `JSON.pretty_generate MyApi.as_open_api`)
|
38
|
+
|
39
|
+
```
|
40
|
+
{
|
41
|
+
"definitions": {
|
42
|
+
"User": {
|
43
|
+
"type": "object",
|
44
|
+
"properties": {
|
45
|
+
"name": {
|
46
|
+
"type": "string"
|
47
|
+
},
|
48
|
+
"age": {
|
49
|
+
"nullable": true,
|
50
|
+
"type": "integer"
|
51
|
+
}
|
52
|
+
}
|
53
|
+
},
|
54
|
+
"Post": {
|
55
|
+
"type": "object",
|
56
|
+
"properties": {
|
57
|
+
"author": {
|
58
|
+
"ref": "#/definitions/User"
|
59
|
+
},
|
60
|
+
"title": {
|
61
|
+
"type": "string"
|
62
|
+
},
|
63
|
+
"body": {
|
64
|
+
"type": "string"
|
65
|
+
},
|
66
|
+
"created_at": {
|
67
|
+
"type": "string",
|
68
|
+
"format": "date-time"
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
```
|
4
75
|
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
6
76
|
|
7
77
|
## Installation
|
8
78
|
|
@@ -22,7 +92,7 @@ Or install it yourself as:
|
|
22
92
|
|
23
93
|
## Usage
|
24
94
|
|
25
|
-
|
95
|
+
See the specs for further examples. This currently implements just enough to get by, but the eventual hope is to be fully compatible with the OpenAPI spec.
|
26
96
|
|
27
97
|
## Development
|
28
98
|
|
data/lib/dry/doc.rb
CHANGED
@@ -5,10 +5,41 @@ require 'dry/doc/version'
|
|
5
5
|
module Dry
|
6
6
|
module Doc
|
7
7
|
NotImplemented = Class.new ::NotImplementedError
|
8
|
+
|
9
|
+
T = ::Dry::Types.module
|
10
|
+
Types = ::Dry::Doc::T::Strict
|
11
|
+
|
12
|
+
module Types
|
13
|
+
def self.instance klass
|
14
|
+
::Dry::Doc::T::Constructor klass
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.constant value
|
18
|
+
::Dry::Doc::T::Constant value
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.sum left, right
|
22
|
+
left = instance left unless left.respond_to? :to_ast
|
23
|
+
right = instance right unless right.respond_to? :to_ast
|
24
|
+
::Dry::Types::Sum.new left, right
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.[] type
|
28
|
+
::Dry::Doc::Types::Array.member type
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
Inlines = Set.new
|
33
|
+
def self.inline klass
|
34
|
+
Inlines.add klass
|
35
|
+
end
|
36
|
+
def self.inline? klass
|
37
|
+
Inlines.include? klass
|
38
|
+
end
|
8
39
|
end
|
9
40
|
end
|
10
41
|
|
11
|
-
require 'dry/doc/
|
42
|
+
require 'dry/doc/object'
|
12
43
|
require 'dry/doc/schema'
|
13
44
|
require 'dry/doc/namespace'
|
14
45
|
|
data/lib/dry/doc/namespace.rb
CHANGED
@@ -1,23 +1,55 @@
|
|
1
1
|
module Dry::Doc
|
2
|
+
class Type
|
3
|
+
attr_accessor :ref
|
4
|
+
|
5
|
+
def initialize inner
|
6
|
+
@inner = inner
|
7
|
+
end
|
8
|
+
|
9
|
+
def call *args
|
10
|
+
@inner.call *args
|
11
|
+
end
|
12
|
+
|
13
|
+
def as_open_api
|
14
|
+
# TODO: this smells
|
15
|
+
::Dry::Doc::Schema::Field.new(
|
16
|
+
type: @inner,
|
17
|
+
description: nil
|
18
|
+
).as_json
|
19
|
+
end
|
20
|
+
|
21
|
+
def name
|
22
|
+
ref
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_ast *args
|
26
|
+
@inner.to_ast *args
|
27
|
+
end
|
28
|
+
|
29
|
+
def inspect
|
30
|
+
"<#{name}>"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
2
34
|
module Namespace
|
3
35
|
def definitions
|
4
36
|
@_definitions ||= []
|
5
37
|
end
|
6
38
|
|
7
39
|
def define name, &config
|
8
|
-
klass = Class.new ::Dry::Doc::
|
9
|
-
@ref = name
|
40
|
+
klass = Class.new ::Dry::Doc::Object do |c|
|
10
41
|
class_exec &config
|
11
42
|
end
|
12
|
-
|
43
|
+
register name, klass
|
44
|
+
end
|
13
45
|
|
14
|
-
|
15
|
-
|
16
|
-
klass
|
46
|
+
def type name, inner
|
47
|
+
klass = ::Dry::Doc::Type.new inner
|
48
|
+
register name, klass
|
17
49
|
end
|
18
50
|
|
19
51
|
def types
|
20
|
-
::Dry::Doc::
|
52
|
+
::Dry::Doc::Types
|
21
53
|
end
|
22
54
|
|
23
55
|
def as_open_api
|
@@ -28,8 +60,19 @@ module Dry::Doc
|
|
28
60
|
end
|
29
61
|
|
30
62
|
{
|
31
|
-
|
63
|
+
schemas: defs
|
32
64
|
}
|
33
65
|
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def register name, klass
|
70
|
+
klass.ref = name
|
71
|
+
klass.freeze
|
72
|
+
|
73
|
+
const_set name, klass
|
74
|
+
definitions.push klass
|
75
|
+
klass
|
76
|
+
end
|
34
77
|
end
|
35
78
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class Dry::Doc::Object < ::Dry::Struct
|
2
|
+
class << self
|
3
|
+
attr_accessor :ref
|
4
|
+
|
5
|
+
def doc
|
6
|
+
@_doc ||= ::Dry::Doc::Schema.new self
|
7
|
+
end
|
8
|
+
|
9
|
+
def types
|
10
|
+
::Dry::Doc::Types
|
11
|
+
end
|
12
|
+
|
13
|
+
def as_open_api
|
14
|
+
doc.as_json
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
ref
|
19
|
+
end
|
20
|
+
|
21
|
+
def attribute name, type=nil, opts={}, &nested
|
22
|
+
if nested
|
23
|
+
opts = type || {}
|
24
|
+
inline_class = build_type :"#{self.name}::#{name}", &nested
|
25
|
+
::Dry::Doc.inline inline_class
|
26
|
+
type = types.instance inline_class
|
27
|
+
type = type.optional if opts[:optional]
|
28
|
+
end
|
29
|
+
|
30
|
+
doc.register name, type,
|
31
|
+
description: opts.delete(:description)
|
32
|
+
super name, type
|
33
|
+
end
|
34
|
+
|
35
|
+
def inspect
|
36
|
+
"<#{name}>"
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def build_type name, &config
|
42
|
+
Class.new ::Dry::Doc::Object do |klass|
|
43
|
+
class_exec &config
|
44
|
+
define_singleton_method(:name) { name }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def as_json *_
|
50
|
+
to_h
|
51
|
+
end
|
52
|
+
end
|
data/lib/dry/doc/schema.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
class Dry::Doc::Schema
|
2
2
|
::Dry::Doc::UnknownPrimitive = Class.new ::Dry::Doc::NotImplemented
|
3
3
|
|
4
|
-
|
5
|
-
T = ::Dry::Doc::Value::Types
|
4
|
+
T = ::Dry::Doc::Types
|
6
5
|
|
7
6
|
class Field < ::Dry::Struct
|
8
|
-
attribute :type, Object
|
7
|
+
attribute :type, ::Object
|
9
8
|
attribute :description, T::String.optional
|
10
9
|
|
11
10
|
def as_json
|
@@ -17,7 +16,43 @@ class Dry::Doc::Schema
|
|
17
16
|
|
18
17
|
private
|
19
18
|
|
20
|
-
BOOL_AST = ::Dry::Doc::
|
19
|
+
BOOL_AST = ::Dry::Doc::Types::Bool.to_ast[1][0 .. -2]
|
20
|
+
|
21
|
+
def apply_constraint acc, con
|
22
|
+
if con.first == :and
|
23
|
+
con[1].reduce(acc) { |a, c| apply_constraint a, c}
|
24
|
+
elsif con.first == :predicate
|
25
|
+
apply_predicate acc, con[1]
|
26
|
+
else
|
27
|
+
# TODO: ignore?
|
28
|
+
acc
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def restrict_enum acc, list
|
33
|
+
if acc[:enum]
|
34
|
+
acc[:enum] = acc[:enum] & list
|
35
|
+
else
|
36
|
+
acc[:enum] = list
|
37
|
+
end
|
38
|
+
acc
|
39
|
+
end
|
40
|
+
|
41
|
+
def apply_predicate acc, pred
|
42
|
+
case pred.first
|
43
|
+
when :type?
|
44
|
+
# These are (presumably) handled by `definition`
|
45
|
+
acc
|
46
|
+
when :included_in?
|
47
|
+
list = pred[1][0][1] # TODO: is this always true?
|
48
|
+
restrict_enum acc, list
|
49
|
+
when :is?
|
50
|
+
list = [pred[1][0][1]] # TODO: is this always true?
|
51
|
+
restrict_enum acc, list
|
52
|
+
else
|
53
|
+
acc
|
54
|
+
end
|
55
|
+
end
|
21
56
|
|
22
57
|
def walk_ast ast, acc
|
23
58
|
kind, data = ast
|
@@ -34,7 +69,9 @@ class Dry::Doc::Schema
|
|
34
69
|
|
35
70
|
when :constrained
|
36
71
|
type, *constraints, _meta = data
|
37
|
-
|
72
|
+
acc = constraints.reduce(acc) do |a, con|
|
73
|
+
apply_constraint a, con
|
74
|
+
end
|
38
75
|
return walk_ast type, acc
|
39
76
|
|
40
77
|
when :constructor
|
@@ -44,10 +81,10 @@ class Dry::Doc::Schema
|
|
44
81
|
when :definition
|
45
82
|
primitive, _meta = data
|
46
83
|
|
47
|
-
if
|
84
|
+
if ::Dry::Doc.inline? primitive
|
48
85
|
return acc.merge primitive.as_open_api
|
49
86
|
elsif primitive.respond_to?(:ref) && primitive.ref
|
50
|
-
return acc.merge ref: primitive.ref
|
87
|
+
return acc.merge '$ref': "#/components/schemas/#{primitive.ref}"
|
51
88
|
elsif primitive == Integer
|
52
89
|
return acc.merge type: :integer
|
53
90
|
elsif primitive == String
|
@@ -75,7 +112,7 @@ class Dry::Doc::Schema
|
|
75
112
|
types = nodes.map { |i| walk_ast i, {} }
|
76
113
|
nils, non_nils = types.partition { |t| t.key?(:type) && t[:type].nil? }
|
77
114
|
if nils.length == 1 && non_nils.length == 1
|
78
|
-
acc[:
|
115
|
+
acc[:nullable] = true
|
79
116
|
acc = acc.merge non_nils.first
|
80
117
|
else
|
81
118
|
acc = acc.merge oneOf: non_nils
|
@@ -85,7 +122,6 @@ class Dry::Doc::Schema
|
|
85
122
|
when :enum
|
86
123
|
inner, _meta = data
|
87
124
|
acc = walk_ast inner, acc
|
88
|
-
acc[:values] = self.type.values # FIXME: this needs to look at the AST
|
89
125
|
return acc
|
90
126
|
end
|
91
127
|
|
data/lib/dry/doc/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dry-doc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Dabbs
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-04-
|
11
|
+
date: 2018-04-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -114,8 +114,8 @@ files:
|
|
114
114
|
- dry-doc.gemspec
|
115
115
|
- lib/dry/doc.rb
|
116
116
|
- lib/dry/doc/namespace.rb
|
117
|
+
- lib/dry/doc/object.rb
|
117
118
|
- lib/dry/doc/schema.rb
|
118
|
-
- lib/dry/doc/value.rb
|
119
119
|
- lib/dry/doc/version.rb
|
120
120
|
homepage: https://github.com/jamesdabbs/dry-doc
|
121
121
|
licenses:
|
data/lib/dry/doc/value.rb
DELETED
@@ -1,79 +0,0 @@
|
|
1
|
-
class Dry::Doc::Value < ::Dry::Struct
|
2
|
-
T = ::Dry::Types.module
|
3
|
-
Types = ::Dry::Doc::Value::T::Strict
|
4
|
-
|
5
|
-
module Types
|
6
|
-
Inlines = Set.new
|
7
|
-
|
8
|
-
def self.instance klass
|
9
|
-
::Dry::Doc::Value::T::Constructor klass
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.constant value
|
13
|
-
::Dry::Doc::Value::T::Constant value
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.sum left, right
|
17
|
-
left = instance left unless left.respond_to? :to_ast
|
18
|
-
right = instance right unless right.respond_to? :to_ast
|
19
|
-
::Dry::Types::Sum.new left, right
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.[] type
|
23
|
-
::Dry::Doc::Value::Types::Array.member type
|
24
|
-
end
|
25
|
-
|
26
|
-
def self.inline? klass
|
27
|
-
::Dry::Doc::Value::Types::Inlines.include? klass
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
class << self
|
32
|
-
def doc
|
33
|
-
@_doc ||= ::Dry::Doc::Schema.new self
|
34
|
-
end
|
35
|
-
|
36
|
-
def types
|
37
|
-
::Dry::Doc::Value::Types
|
38
|
-
end
|
39
|
-
|
40
|
-
def as_open_api
|
41
|
-
doc.as_json
|
42
|
-
end
|
43
|
-
|
44
|
-
def ref
|
45
|
-
"#/definitions/#{@ref}" if @ref
|
46
|
-
end
|
47
|
-
|
48
|
-
def attribute name, type=nil, opts={}, &nested
|
49
|
-
if nested
|
50
|
-
opts = type || {}
|
51
|
-
inline_class = build_type :"#{self.name}::#{name}", &nested
|
52
|
-
::Dry::Doc::Value::Types::Inlines.add inline_class
|
53
|
-
type = types.instance inline_class
|
54
|
-
type = type.optional if opts[:optional]
|
55
|
-
end
|
56
|
-
|
57
|
-
doc.register name, type,
|
58
|
-
description: opts.delete(:description)
|
59
|
-
super name, type
|
60
|
-
end
|
61
|
-
|
62
|
-
def inspect
|
63
|
-
"<#{name}>"
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
|
-
def build_type name, &config
|
69
|
-
Class.new ::Dry::Doc::Value do |klass|
|
70
|
-
class_exec &config
|
71
|
-
define_singleton_method(:name) { name }
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
def as_json *_
|
77
|
-
to_h
|
78
|
-
end
|
79
|
-
end
|