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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: aee851ee5134074ba0f9278e9dca6df0cdeb4efb
4
- data.tar.gz: 4a8e6e5a36521355828fd9b096dea9b320bb0c20
3
+ metadata.gz: 65f2898bcc0d29121f1ef94ae8a947be45b0e184
4
+ data.tar.gz: fc3fb4d684aa8b2112187ef42a9566688b4efd65
5
5
  SHA512:
6
- metadata.gz: 9e9ce1e2dabd2da2c3dd8353ebf7aaa854c33a0450a9594cd623750ba4ab7379cc60532436d69d519f71055aab4e5d945c55bd2a20cd528176a77a80c8bf7fa2
7
- data.tar.gz: ecda373413a557e0c343bd9a6dce2efe9b95a6cf7edbc7a0f49e4b5a47aaf8395898b0de68095696a969cbb5c158d12cdb2abf73594295b21bcbb59698666073
6
+ metadata.gz: cffa125c73f7ee00ea837b2abf781bc3d96ba3c175d0a2285ac2515db473fc39869e0b7bf56e81bd3d95f881240cc63a1a6acd85b250f58cd7524980dc538df4
7
+ data.tar.gz: 1e5e30e6ae417759bd6f508fdcb00100a86028375dcb160c2a7441f606aaa6de530d35d59476eeedb408ccf9dbab3fe1f7c8649ef2420e397d918d1680a9bd83
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in dry-doc.gemspec
4
- gemspec
4
+ gemspec
data/README.md CHANGED
@@ -1,8 +1,78 @@
1
1
  # Dry::Doc
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/dry/doc`. To experiment with that code, run `bin/console` for an interactive prompt.
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
- TODO: Write usage instructions here
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/value'
42
+ require 'dry/doc/object'
12
43
  require 'dry/doc/schema'
13
44
  require 'dry/doc/namespace'
14
45
 
@@ -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::Value do |c|
9
- @ref = name
40
+ klass = Class.new ::Dry::Doc::Object do |c|
10
41
  class_exec &config
11
42
  end
12
- klass.define_singleton_method(:name) { name }
43
+ register name, klass
44
+ end
13
45
 
14
- const_set name, klass
15
- definitions.push klass
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::Value::Types
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
- definitions: defs
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
@@ -1,11 +1,10 @@
1
1
  class Dry::Doc::Schema
2
2
  ::Dry::Doc::UnknownPrimitive = Class.new ::Dry::Doc::NotImplemented
3
3
 
4
- Nullable = :"x-nullable"
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::Value::Types::Bool.to_ast[1][0 .. -2]
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
- # FIXME: note `is?` constraints
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 T.inline? primitive
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[:'x-nullable'] = true
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
 
@@ -1,5 +1,5 @@
1
1
  module Dry
2
2
  module Doc
3
- VERSION = '0.0.1'.freeze
3
+ VERSION = '0.0.2'.freeze
4
4
  end
5
5
  end
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.1
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-10 00:00:00.000000000 Z
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