dry-doc 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|