sober_swag 0.1.0 → 0.2.0
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/.github/config/rubocop_linter_action.yml +5 -0
 - data/.github/workflows/lint.yml +15 -0
 - data/.github/workflows/ruby.yml +23 -1
 - data/.gitignore +3 -0
 - data/.rubocop.yml +73 -1
 - data/.ruby-version +1 -1
 - data/Gemfile.lock +29 -5
 - data/README.md +109 -0
 - data/bin/console +15 -14
 - data/docs/serializers.md +203 -0
 - data/example/.rspec +1 -0
 - data/example/.ruby-version +1 -1
 - data/example/Gemfile +10 -6
 - data/example/Gemfile.lock +96 -76
 - data/example/app/controllers/people_controller.rb +37 -21
 - data/example/app/controllers/posts_controller.rb +102 -0
 - data/example/app/models/application_record.rb +3 -0
 - data/example/app/models/person.rb +6 -0
 - data/example/app/models/post.rb +9 -0
 - data/example/app/output_objects/person_errors_output_object.rb +5 -0
 - data/example/app/output_objects/person_output_object.rb +15 -0
 - data/example/app/output_objects/post_output_object.rb +10 -0
 - data/example/bin/bundle +24 -20
 - data/example/bin/rails +1 -1
 - data/example/bin/rake +1 -1
 - data/example/config/application.rb +11 -7
 - data/example/config/environments/development.rb +0 -1
 - data/example/config/environments/production.rb +3 -3
 - data/example/config/puma.rb +5 -5
 - data/example/config/routes.rb +3 -0
 - data/example/config/spring.rb +4 -4
 - data/example/db/migrate/20200311152021_create_people.rb +0 -1
 - data/example/db/migrate/20200603172347_create_posts.rb +11 -0
 - data/example/db/schema.rb +16 -7
 - data/example/spec/rails_helper.rb +64 -0
 - data/example/spec/requests/people/create_spec.rb +52 -0
 - data/example/spec/requests/people/get_spec.rb +35 -0
 - data/example/spec/requests/people/index_spec.rb +69 -0
 - data/example/spec/spec_helper.rb +94 -0
 - data/lib/sober_swag.rb +6 -3
 - data/lib/sober_swag/compiler/error.rb +2 -0
 - data/lib/sober_swag/compiler/path.rb +2 -5
 - data/lib/sober_swag/compiler/paths.rb +0 -1
 - data/lib/sober_swag/compiler/type.rb +28 -15
 - data/lib/sober_swag/controller.rb +16 -11
 - data/lib/sober_swag/controller/route.rb +18 -21
 - data/lib/sober_swag/controller/undefined_body_error.rb +3 -0
 - data/lib/sober_swag/controller/undefined_path_error.rb +3 -0
 - data/lib/sober_swag/controller/undefined_query_error.rb +3 -0
 - data/lib/sober_swag/input_object.rb +28 -0
 - data/lib/sober_swag/nodes/array.rb +1 -1
 - data/lib/sober_swag/nodes/base.rb +2 -4
 - data/lib/sober_swag/nodes/binary.rb +2 -1
 - data/lib/sober_swag/nodes/enum.rb +4 -2
 - data/lib/sober_swag/nodes/list.rb +0 -1
 - data/lib/sober_swag/nodes/primitive.rb +6 -5
 - data/lib/sober_swag/output_object.rb +102 -0
 - data/lib/sober_swag/output_object/definition.rb +30 -0
 - data/lib/sober_swag/{blueprint → output_object}/field.rb +14 -4
 - data/lib/sober_swag/{blueprint → output_object}/field_syntax.rb +1 -1
 - data/lib/sober_swag/{blueprint → output_object}/view.rb +15 -6
 - data/lib/sober_swag/parser.rb +5 -3
 - data/lib/sober_swag/serializer.rb +5 -2
 - data/lib/sober_swag/serializer/array.rb +12 -0
 - data/lib/sober_swag/serializer/base.rb +50 -1
 - data/lib/sober_swag/serializer/conditional.rb +15 -2
 - data/lib/sober_swag/serializer/field_list.rb +29 -6
 - data/lib/sober_swag/serializer/mapped.rb +12 -2
 - data/lib/sober_swag/serializer/meta.rb +35 -0
 - data/lib/sober_swag/serializer/optional.rb +17 -2
 - data/lib/sober_swag/serializer/primitive.rb +4 -1
 - data/lib/sober_swag/server.rb +83 -0
 - data/lib/sober_swag/types.rb +3 -0
 - data/lib/sober_swag/version.rb +1 -1
 - data/sober_swag.gemspec +6 -4
 - metadata +77 -44
 - data/example/person.json +0 -4
 - data/example/test/controllers/.keep +0 -0
 - data/example/test/fixtures/.keep +0 -0
 - data/example/test/fixtures/files/.keep +0 -0
 - data/example/test/fixtures/people.yml +0 -11
 - data/example/test/integration/.keep +0 -0
 - data/example/test/models/.keep +0 -0
 - data/example/test/models/person_test.rb +0 -7
 - data/example/test/test_helper.rb +0 -13
 - data/lib/sober_swag/blueprint.rb +0 -113
 - data/lib/sober_swag/path.rb +0 -8
 - data/lib/sober_swag/path/integer.rb +0 -21
 - data/lib/sober_swag/path/lit.rb +0 -41
 - data/lib/sober_swag/path/literal.rb +0 -29
 - data/lib/sober_swag/path/param.rb +0 -33
 
| 
         @@ -1,14 +1,16 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module SoberSwag
         
     | 
| 
       2 
     | 
    
         
            -
              class  
     | 
| 
      
 2 
     | 
    
         
            +
              class OutputObject
         
     | 
| 
      
 3 
     | 
    
         
            +
                ##
         
     | 
| 
      
 4 
     | 
    
         
            +
                # DSL for defining a view.
         
     | 
| 
      
 5 
     | 
    
         
            +
                # Used in `view` blocks within {SoberSwag::OutputObject.define}.
         
     | 
| 
       3 
6 
     | 
    
         
             
                class View
         
     | 
| 
       4 
     | 
    
         
            -
             
     | 
| 
       5 
7 
     | 
    
         
             
                  def self.define(name, base_fields, &block)
         
     | 
| 
       6 
     | 
    
         
            -
                     
     | 
| 
      
 8 
     | 
    
         
            +
                    new(name, base_fields).tap do |view|
         
     | 
| 
       7 
9 
     | 
    
         
             
                      view.instance_eval(&block)
         
     | 
| 
       8 
10 
     | 
    
         
             
                    end
         
     | 
| 
       9 
11 
     | 
    
         
             
                  end
         
     | 
| 
       10 
12 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
                  class NestingError < Error; end 
     | 
| 
      
 13 
     | 
    
         
            +
                  class NestingError < Error; end
         
     | 
| 
       12 
14 
     | 
    
         | 
| 
       13 
15 
     | 
    
         
             
                  include FieldSyntax
         
     | 
| 
       14 
16 
     | 
    
         | 
| 
         @@ -19,8 +21,16 @@ module SoberSwag 
     | 
|
| 
       19 
21 
     | 
    
         | 
| 
       20 
22 
     | 
    
         
             
                  attr_reader :name, :fields
         
     | 
| 
       21 
23 
     | 
    
         | 
| 
      
 24 
     | 
    
         
            +
                  def serialize(obj, opts = {})
         
     | 
| 
      
 25 
     | 
    
         
            +
                    serializer.serialize(obj, opts)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  def type
         
     | 
| 
      
 29 
     | 
    
         
            +
                    serializer.type
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
       22 
32 
     | 
    
         
             
                  def except!(name)
         
     | 
| 
       23 
     | 
    
         
            -
                    @fields. 
     | 
| 
      
 33 
     | 
    
         
            +
                    @fields.reject! { |f| f.name == name }
         
     | 
| 
       24 
34 
     | 
    
         
             
                  end
         
     | 
| 
       25 
35 
     | 
    
         | 
| 
       26 
36 
     | 
    
         
             
                  def view(*)
         
     | 
| 
         @@ -38,7 +48,6 @@ module SoberSwag 
     | 
|
| 
       38 
48 
     | 
    
         
             
                    @serializer ||=
         
     | 
| 
       39 
49 
     | 
    
         
             
                      SoberSwag::Serializer::FieldList.new(fields)
         
     | 
| 
       40 
50 
     | 
    
         
             
                  end
         
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
       42 
51 
     | 
    
         
             
                end
         
     | 
| 
       43 
52 
     | 
    
         
             
              end
         
     | 
| 
       44 
53 
     | 
    
         
             
            end
         
     | 
    
        data/lib/sober_swag/parser.rb
    CHANGED
    
    | 
         @@ -1,11 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module SoberSwag
         
     | 
| 
      
 2 
     | 
    
         
            +
              ##
         
     | 
| 
      
 3 
     | 
    
         
            +
              # Parses a *Dry-Types Schema* into a set of nodes we can use to compile.
         
     | 
| 
      
 4 
     | 
    
         
            +
              # This is mostly because the vistior pattern sucks and catamorphisms are nice.
         
     | 
| 
       2 
5 
     | 
    
         
             
              class Parser
         
     | 
| 
       3 
6 
     | 
    
         
             
                def initialize(node)
         
     | 
| 
       4 
7 
     | 
    
         
             
                  @node = node
         
     | 
| 
       5 
8 
     | 
    
         
             
                  @found = Set.new
         
     | 
| 
       6 
9 
     | 
    
         
             
                end
         
     | 
| 
       7 
10 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
                def to_syntax
         
     | 
| 
      
 11 
     | 
    
         
            +
                def to_syntax # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
         
     | 
| 
       9 
12 
     | 
    
         
             
                  case @node
         
     | 
| 
       10 
13 
     | 
    
         
             
                  when Dry::Types::Array::Member
         
     | 
| 
       11 
14 
     | 
    
         
             
                    Nodes::List.new(bind(Parser.new(@node.member)))
         
     | 
| 
         @@ -41,7 +44,7 @@ module SoberSwag 
     | 
|
| 
       41 
44 
     | 
    
         
             
                    bind(Parser.new(@node.type))
         
     | 
| 
       42 
45 
     | 
    
         
             
                  when Dry::Types::Nominal
         
     | 
| 
       43 
46 
     | 
    
         
             
                    # start off with the moral equivalent of NodeTree[String]
         
     | 
| 
       44 
     | 
    
         
            -
                    Nodes::Primitive.new(@node.primitive)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    Nodes::Primitive.new(@node.primitive, @node.meta)
         
     | 
| 
       45 
48 
     | 
    
         
             
                  else
         
     | 
| 
       46 
49 
     | 
    
         
             
                    # Inside of this case we have a class that is some user-defined type
         
     | 
| 
       47 
50 
     | 
    
         
             
                    # We put it in our array of found types, and consider it a primitive
         
     | 
| 
         @@ -68,6 +71,5 @@ module SoberSwag 
     | 
|
| 
       68 
71 
     | 
    
         
             
                  @found += other.found
         
     | 
| 
       69 
72 
     | 
    
         
             
                  result
         
     | 
| 
       70 
73 
     | 
    
         
             
                end
         
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
       72 
74 
     | 
    
         
             
              end
         
     | 
| 
       73 
75 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,4 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module SoberSwag
         
     | 
| 
      
 2 
     | 
    
         
            +
              ##
         
     | 
| 
      
 3 
     | 
    
         
            +
              # Container module for serializers.
         
     | 
| 
      
 4 
     | 
    
         
            +
              # The interface for these is described in {SoberSwag::Serializer::Base}.
         
     | 
| 
       2 
5 
     | 
    
         
             
              module Serializer
         
     | 
| 
       3 
6 
     | 
    
         
             
                autoload(:Base, 'sober_swag/serializer/base')
         
     | 
| 
       4 
7 
     | 
    
         
             
                autoload(:Primitive, 'sober_swag/serializer/primitive')
         
     | 
| 
         @@ -7,6 +10,7 @@ module SoberSwag 
     | 
|
| 
       7 
10 
     | 
    
         
             
                autoload(:Mapped, 'sober_swag/serializer/mapped')
         
     | 
| 
       8 
11 
     | 
    
         
             
                autoload(:Optional, 'sober_swag/serializer/optional')
         
     | 
| 
       9 
12 
     | 
    
         
             
                autoload(:FieldList, 'sober_swag/serializer/field_list')
         
     | 
| 
      
 13 
     | 
    
         
            +
                autoload(:Meta, 'sober_swag/serializer/meta')
         
     | 
| 
       10 
14 
     | 
    
         | 
| 
       11 
15 
     | 
    
         
             
                class << self
         
     | 
| 
       12 
16 
     | 
    
         
             
                  ##
         
     | 
| 
         @@ -14,10 +18,9 @@ module SoberSwag 
     | 
|
| 
       14 
18 
     | 
    
         
             
                  # in values raw.
         
     | 
| 
       15 
19 
     | 
    
         
             
                  #
         
     | 
| 
       16 
20 
     | 
    
         
             
                  # @param contained {Class} Dry::Type to use
         
     | 
| 
       17 
     | 
    
         
            -
                  def Primitive(contained)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  def Primitive(contained) # rubocop:disable Naming/MethodName
         
     | 
| 
       18 
22 
     | 
    
         
             
                    SoberSwag::Serializer::Primitive.new(contained)
         
     | 
| 
       19 
23 
     | 
    
         
             
                  end
         
     | 
| 
       20 
24 
     | 
    
         
             
                end
         
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
25 
     | 
    
         
             
              end
         
     | 
| 
       23 
26 
     | 
    
         
             
            end
         
     | 
| 
         @@ -7,6 +7,18 @@ module SoberSwag 
     | 
|
| 
       7 
7 
     | 
    
         
             
                    @element_serializer = element_serializer
         
     | 
| 
       8 
8 
     | 
    
         
             
                  end
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
      
 10 
     | 
    
         
            +
                  def lazy_type?
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @element_serializer.lazy_type?
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  def lazy_type
         
     | 
| 
      
 15 
     | 
    
         
            +
                    SoberSwag::Types::Array.of(@element_serializer.lazy_type)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  def finalize_lazy_type!
         
     | 
| 
      
 19 
     | 
    
         
            +
                    @element_serializer.finalize_lazy_type!
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
       10 
22 
     | 
    
         
             
                  attr_reader :element_serializer
         
     | 
| 
       11 
23 
     | 
    
         | 
| 
       12 
24 
     | 
    
         
             
                  def serialize(object, options = {})
         
     | 
| 
         @@ -1,7 +1,10 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module SoberSwag
         
     | 
| 
       2 
2 
     | 
    
         
             
              module Serializer
         
     | 
| 
      
 3 
     | 
    
         
            +
                ##
         
     | 
| 
      
 4 
     | 
    
         
            +
                # Base interface class that all other serializers are subclasses of.
         
     | 
| 
      
 5 
     | 
    
         
            +
                # This also defines methods as stubs, which is sort of bad ruby style, but makes documentation
         
     | 
| 
      
 6 
     | 
    
         
            +
                # easier to generate.
         
     | 
| 
       3 
7 
     | 
    
         
             
                class Base
         
     | 
| 
       4 
     | 
    
         
            -
             
     | 
| 
       5 
8 
     | 
    
         
             
                  ##
         
     | 
| 
       6 
9 
     | 
    
         
             
                  # Return a new serializer that is an *array* of elements of this serializer.
         
     | 
| 
       7 
10 
     | 
    
         
             
                  def array
         
     | 
| 
         @@ -14,6 +17,46 @@ module SoberSwag 
     | 
|
| 
       14 
17 
     | 
    
         
             
                    SoberSwag::Serializer::Optional.new(self)
         
     | 
| 
       15 
18 
     | 
    
         
             
                  end
         
     | 
| 
       16 
19 
     | 
    
         | 
| 
      
 20 
     | 
    
         
            +
                  ##
         
     | 
| 
      
 21 
     | 
    
         
            +
                  # Is this type lazily defined?
         
     | 
| 
      
 22 
     | 
    
         
            +
                  #
         
     | 
| 
      
 23 
     | 
    
         
            +
                  # Used for mutual recursion.
         
     | 
| 
      
 24 
     | 
    
         
            +
                  def lazy_type?
         
     | 
| 
      
 25 
     | 
    
         
            +
                    false
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  ##
         
     | 
| 
      
 29 
     | 
    
         
            +
                  # The lazy version of this type, for mutual recursion.
         
     | 
| 
      
 30 
     | 
    
         
            +
                  def lazy_type
         
     | 
| 
      
 31 
     | 
    
         
            +
                    type
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  ##
         
     | 
| 
      
 35 
     | 
    
         
            +
                  # Finalize a lazy type.
         
     | 
| 
      
 36 
     | 
    
         
            +
                  #
         
     | 
| 
      
 37 
     | 
    
         
            +
                  # Should be idempotent: call it once, and it does nothing on subsequent calls (but returns the type).
         
     | 
| 
      
 38 
     | 
    
         
            +
                  def finalize_lazy_type!
         
     | 
| 
      
 39 
     | 
    
         
            +
                    type
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  ##
         
     | 
| 
      
 43 
     | 
    
         
            +
                  # Serialize an object.
         
     | 
| 
      
 44 
     | 
    
         
            +
                  def serialize(_object, _options = {})
         
     | 
| 
      
 45 
     | 
    
         
            +
                    raise ArgumentError, 'not implemented!'
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                  ##
         
     | 
| 
      
 49 
     | 
    
         
            +
                  # Get the type that we serialize to.
         
     | 
| 
      
 50 
     | 
    
         
            +
                  def type
         
     | 
| 
      
 51 
     | 
    
         
            +
                    raise ArgumentError, 'not implemented!'
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                  ##
         
     | 
| 
      
 55 
     | 
    
         
            +
                  # Add metadata onto the *type* of a serializer.
         
     | 
| 
      
 56 
     | 
    
         
            +
                  def meta(hash)
         
     | 
| 
      
 57 
     | 
    
         
            +
                    SoberSwag::Serializer::Meta.new(self, hash)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
       17 
60 
     | 
    
         
             
                  ##
         
     | 
| 
       18 
61 
     | 
    
         
             
                  # If I am a serializer for type 'a', and you give me a way to turn 'a's into 'b's,
         
     | 
| 
       19 
62 
     | 
    
         
             
                  # I can give you a serializer for type 'b' by running the funciton you gave.
         
     | 
| 
         @@ -33,6 +76,12 @@ module SoberSwag 
     | 
|
| 
       33 
76 
     | 
    
         
             
                    self
         
     | 
| 
       34 
77 
     | 
    
         
             
                  end
         
     | 
| 
       35 
78 
     | 
    
         | 
| 
      
 79 
     | 
    
         
            +
                  ##
         
     | 
| 
      
 80 
     | 
    
         
            +
                  # Get the type name of this to be used externally, or set it if an argument is provided
         
     | 
| 
      
 81 
     | 
    
         
            +
                  def identifier(arg = nil)
         
     | 
| 
      
 82 
     | 
    
         
            +
                    @identifier = arg if arg
         
     | 
| 
      
 83 
     | 
    
         
            +
                    @identifier
         
     | 
| 
      
 84 
     | 
    
         
            +
                  end
         
     | 
| 
       36 
85 
     | 
    
         
             
                end
         
     | 
| 
       37 
86 
     | 
    
         
             
              end
         
     | 
| 
       38 
87 
     | 
    
         
             
            end
         
     | 
| 
         @@ -25,9 +25,10 @@ module SoberSwag 
     | 
|
| 
       25 
25 
     | 
    
         | 
| 
       26 
26 
     | 
    
         
             
                  def serialize(object, options = {})
         
     | 
| 
       27 
27 
     | 
    
         
             
                    tag, val = chooser.call(object, options)
         
     | 
| 
       28 
     | 
    
         
            -
                     
     | 
| 
      
 28 
     | 
    
         
            +
                    case tag
         
     | 
| 
      
 29 
     | 
    
         
            +
                    when :left
         
     | 
| 
       29 
30 
     | 
    
         
             
                      left.serialize(val, options)
         
     | 
| 
       30 
     | 
    
         
            -
                     
     | 
| 
      
 31 
     | 
    
         
            +
                    when :right
         
     | 
| 
       31 
32 
     | 
    
         
             
                      right.serialize(val, options)
         
     | 
| 
       32 
33 
     | 
    
         
             
                    else
         
     | 
| 
       33 
34 
     | 
    
         
             
                      raise BadChoiceError, "result of chooser proc was not a left or right, but a #{val.class}"
         
     | 
| 
         @@ -44,6 +45,18 @@ module SoberSwag 
     | 
|
| 
       44 
45 
     | 
    
         
             
                      left.type | right.type
         
     | 
| 
       45 
46 
     | 
    
         
             
                    end
         
     | 
| 
       46 
47 
     | 
    
         
             
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  def lazy_type
         
     | 
| 
      
 50 
     | 
    
         
            +
                    left.lazy_type | right.lazy_type
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                  def lazy_type?
         
     | 
| 
      
 54 
     | 
    
         
            +
                    left.lazy_type? || right.lazy_type?
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  def finalize_lazy_type!
         
     | 
| 
      
 58 
     | 
    
         
            +
                    [left, right].each(&:finalize_lazy_type!)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
       47 
60 
     | 
    
         
             
                end
         
     | 
| 
       48 
61 
     | 
    
         
             
              end
         
     | 
| 
       49 
62 
     | 
    
         
             
            end
         
     | 
| 
         @@ -4,7 +4,6 @@ module SoberSwag 
     | 
|
| 
       4 
4 
     | 
    
         
             
                # Extract out a hash from a list of
         
     | 
| 
       5 
5 
     | 
    
         
             
                # name/serializer pairs.
         
     | 
| 
       6 
6 
     | 
    
         
             
                class FieldList < Base
         
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
       8 
7 
     | 
    
         
             
                  def initialize(field_list)
         
     | 
| 
       9 
8 
     | 
    
         
             
                    @field_list = field_list
         
     | 
| 
       10 
9 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -17,8 +16,7 @@ module SoberSwag 
     | 
|
| 
       17 
16 
     | 
    
         
             
                    SoberSwag::Serializer.Primitive(SoberSwag::Types.const_get(symbol))
         
     | 
| 
       18 
17 
     | 
    
         
             
                  end
         
     | 
| 
       19 
18 
     | 
    
         | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
                  def serialize(object, options)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  def serialize(object, options = {})
         
     | 
| 
       22 
20 
     | 
    
         
             
                    field_list.map { |field|
         
     | 
| 
       23 
21 
     | 
    
         
             
                      [field.name, field.serializer.serialize(object, options)]
         
     | 
| 
       24 
22 
     | 
    
         
             
                    }.to_h
         
     | 
| 
         @@ -28,17 +26,42 @@ module SoberSwag 
     | 
|
| 
       28 
26 
     | 
    
         
             
                    @type ||= make_struct_type!
         
     | 
| 
       29 
27 
     | 
    
         
             
                  end
         
     | 
| 
       30 
28 
     | 
    
         | 
| 
      
 29 
     | 
    
         
            +
                  def lazy_type?
         
     | 
| 
      
 30 
     | 
    
         
            +
                    true
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  def lazy_type
         
     | 
| 
      
 34 
     | 
    
         
            +
                    struct_class
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  def finalize_lazy_type!
         
     | 
| 
      
 38 
     | 
    
         
            +
                    make_struct_type!
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
       31 
41 
     | 
    
         
             
                  private
         
     | 
| 
       32 
42 
     | 
    
         | 
| 
       33 
     | 
    
         
            -
                  def make_struct_type!
         
     | 
| 
      
 43 
     | 
    
         
            +
                  def make_struct_type! # rubocop:disable Metrics/MethodLength
         
     | 
| 
      
 44 
     | 
    
         
            +
                    # mutual recursion makes this really, really annoying.
         
     | 
| 
      
 45 
     | 
    
         
            +
                    return struct_class if @made_struct_type
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
       34 
47 
     | 
    
         
             
                    f = field_list
         
     | 
| 
       35 
     | 
    
         
            -
                     
     | 
| 
      
 48 
     | 
    
         
            +
                    s = identifier
         
     | 
| 
      
 49 
     | 
    
         
            +
                    struct_class.instance_eval do
         
     | 
| 
      
 50 
     | 
    
         
            +
                      identifier(s)
         
     | 
| 
       36 
51 
     | 
    
         
             
                      f.each do |field|
         
     | 
| 
       37 
     | 
    
         
            -
                        attribute field.name, field.serializer. 
     | 
| 
      
 52 
     | 
    
         
            +
                        attribute field.name, field.serializer.lazy_type
         
     | 
| 
       38 
53 
     | 
    
         
             
                      end
         
     | 
| 
       39 
54 
     | 
    
         
             
                    end
         
     | 
| 
      
 55 
     | 
    
         
            +
                    @made_struct_type = true
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                    field_list.map(&:serializer).each(&:finalize_lazy_type!)
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                    struct_class
         
     | 
| 
       40 
60 
     | 
    
         
             
                  end
         
     | 
| 
       41 
61 
     | 
    
         | 
| 
      
 62 
     | 
    
         
            +
                  def struct_class
         
     | 
| 
      
 63 
     | 
    
         
            +
                    @struct_class ||= Class.new(SoberSwag::InputObject)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  end
         
     | 
| 
       42 
65 
     | 
    
         
             
                end
         
     | 
| 
       43 
66 
     | 
    
         
             
              end
         
     | 
| 
       44 
67 
     | 
    
         
             
            end
         
     | 
| 
         @@ -3,7 +3,6 @@ module SoberSwag 
     | 
|
| 
       3 
3 
     | 
    
         
             
                ##
         
     | 
| 
       4 
4 
     | 
    
         
             
                # A new serializer by mapping over the serialization function
         
     | 
| 
       5 
5 
     | 
    
         
             
                class Mapped < Base
         
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
6 
     | 
    
         
             
                  def initialize(base, map_f)
         
     | 
| 
       8 
7 
     | 
    
         
             
                    @base = base
         
     | 
| 
       9 
8 
     | 
    
         
             
                    @map_f = map_f
         
     | 
| 
         @@ -13,6 +12,18 @@ module SoberSwag 
     | 
|
| 
       13 
12 
     | 
    
         
             
                    @base.serialize(@map_f.call(object), options)
         
     | 
| 
       14 
13 
     | 
    
         
             
                  end
         
     | 
| 
       15 
14 
     | 
    
         | 
| 
      
 15 
     | 
    
         
            +
                  def lazy_type?
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @base.lazy_type?
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def lazy_type
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @base.lazy_type
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def finalize_lazy_type!
         
     | 
| 
      
 24 
     | 
    
         
            +
                    @base.finalize_lazy_type!
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
       16 
27 
     | 
    
         
             
                  def type
         
     | 
| 
       17 
28 
     | 
    
         
             
                    @base.type
         
     | 
| 
       18 
29 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -23,7 +34,6 @@ module SoberSwag 
     | 
|
| 
       23 
34 
     | 
    
         
             
                  def via_map(&block)
         
     | 
| 
       24 
35 
     | 
    
         
             
                    SoberSwag::Serializer::Mapped.new(@base, @map_f >> block)
         
     | 
| 
       25 
36 
     | 
    
         
             
                  end
         
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
37 
     | 
    
         
             
                end
         
     | 
| 
       28 
38 
     | 
    
         
             
              end
         
     | 
| 
       29 
39 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,35 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module SoberSwag
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Serializer
         
     | 
| 
      
 3 
     | 
    
         
            +
                ##
         
     | 
| 
      
 4 
     | 
    
         
            +
                # Provides metadata on a serializer.
         
     | 
| 
      
 5 
     | 
    
         
            +
                # All actions delegate to the base.
         
     | 
| 
      
 6 
     | 
    
         
            +
                class Meta < Base
         
     | 
| 
      
 7 
     | 
    
         
            +
                  def initialize(base, meta)
         
     | 
| 
      
 8 
     | 
    
         
            +
                    @base = base
         
     | 
| 
      
 9 
     | 
    
         
            +
                    @meta = meta
         
     | 
| 
      
 10 
     | 
    
         
            +
                  end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  attr_reader :base, :meta
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  def serialize(args, opts = {})
         
     | 
| 
      
 15 
     | 
    
         
            +
                    base.serialize(args, opts)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  def lazy_type
         
     | 
| 
      
 19 
     | 
    
         
            +
                    @base.lazy_type.meta(**meta)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  def type
         
     | 
| 
      
 23 
     | 
    
         
            +
                    @base.type.meta(**meta)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  def finalize_lazy_type!
         
     | 
| 
      
 27 
     | 
    
         
            +
                    @base.finalize_lazy_type!
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  def lazy_type?
         
     | 
| 
      
 31 
     | 
    
         
            +
                    @base.lazy_type?
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -1,13 +1,29 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module SoberSwag
         
     | 
| 
       2 
2 
     | 
    
         
             
              module Serializer
         
     | 
| 
      
 3 
     | 
    
         
            +
                ##
         
     | 
| 
      
 4 
     | 
    
         
            +
                # Given something that serializes a type 'A',
         
     | 
| 
      
 5 
     | 
    
         
            +
                # this can be used to make a serializer of type 'A | nil'.
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                # Or, put another way, makes serializers not crash on nil values.
         
     | 
| 
       3 
8 
     | 
    
         
             
                class Optional < Base
         
     | 
| 
       4 
     | 
    
         
            -
             
     | 
| 
       5 
9 
     | 
    
         
             
                  def initialize(inner)
         
     | 
| 
       6 
10 
     | 
    
         
             
                    @inner = inner
         
     | 
| 
       7 
11 
     | 
    
         
             
                  end
         
     | 
| 
       8 
12 
     | 
    
         | 
| 
       9 
13 
     | 
    
         
             
                  attr_reader :inner
         
     | 
| 
       10 
14 
     | 
    
         | 
| 
      
 15 
     | 
    
         
            +
                  def lazy_type?
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @inner.lazy_type?
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def lazy_type
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @inner.lazy_type.optional
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def finalize_lazy_type!
         
     | 
| 
      
 24 
     | 
    
         
            +
                    @inner.finalize_lazy_type!
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
       11 
27 
     | 
    
         
             
                  def serialize(object, options = {})
         
     | 
| 
       12 
28 
     | 
    
         
             
                    if object.nil?
         
     | 
| 
       13 
29 
     | 
    
         
             
                      object
         
     | 
| 
         @@ -23,7 +39,6 @@ module SoberSwag 
     | 
|
| 
       23 
39 
     | 
    
         
             
                  def optional(*)
         
     | 
| 
       24 
40 
     | 
    
         
             
                    raise ArgumentError, 'no nesting optionals please'
         
     | 
| 
       25 
41 
     | 
    
         
             
                  end
         
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
42 
     | 
    
         
             
                end
         
     | 
| 
       28 
43 
     | 
    
         
             
              end
         
     | 
| 
       29 
44 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,5 +1,8 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module SoberSwag
         
     | 
| 
       2 
2 
     | 
    
         
             
              module Serializer
         
     | 
| 
      
 3 
     | 
    
         
            +
                ##
         
     | 
| 
      
 4 
     | 
    
         
            +
                # A class that does *no* serialization: you give it a type,
         
     | 
| 
      
 5 
     | 
    
         
            +
                # and it will pass any serialized input on verbatim.
         
     | 
| 
       3 
6 
     | 
    
         
             
                class Primitive < Base
         
     | 
| 
       4 
7 
     | 
    
         
             
                  def initialize(type)
         
     | 
| 
       5 
8 
     | 
    
         
             
                    @type = type
         
     | 
| 
         @@ -7,7 +10,7 @@ module SoberSwag 
     | 
|
| 
       7 
10 
     | 
    
         | 
| 
       8 
11 
     | 
    
         
             
                  attr_reader :type
         
     | 
| 
       9 
12 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
                  def serialize(object,  
     | 
| 
      
 13 
     | 
    
         
            +
                  def serialize(object, _options = {})
         
     | 
| 
       11 
14 
     | 
    
         
             
                    object
         
     | 
| 
       12 
15 
     | 
    
         
             
                  end
         
     | 
| 
       13 
16 
     | 
    
         
             
                end
         
     | 
| 
         @@ -0,0 +1,83 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'set'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module SoberSwag
         
     | 
| 
      
 4 
     | 
    
         
            +
              ##
         
     | 
| 
      
 5 
     | 
    
         
            +
              # A basic, rack-only server to serve up swagger definitions.
         
     | 
| 
      
 6 
     | 
    
         
            +
              # By default it is configured to work with rails, but you can pass stuff to initialize.
         
     | 
| 
      
 7 
     | 
    
         
            +
              class Server
         
     | 
| 
      
 8 
     | 
    
         
            +
                RAILS_CONTROLLER_PROC = proc do
         
     | 
| 
      
 9 
     | 
    
         
            +
                  Rails.application.routes.routes.map { |route|
         
     | 
| 
      
 10 
     | 
    
         
            +
                    route.defaults[:controller]
         
     | 
| 
      
 11 
     | 
    
         
            +
                  }.to_set.reject(&:nil?).map { |controller|
         
     | 
| 
      
 12 
     | 
    
         
            +
                    "#{controller}_controller".classify.constantize
         
     | 
| 
      
 13 
     | 
    
         
            +
                  }.filter { |controller| controller.ancestors.include?(SoberSwag::Controller) }
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                ##
         
     | 
| 
      
 17 
     | 
    
         
            +
                # Start up.
         
     | 
| 
      
 18 
     | 
    
         
            +
                #
         
     | 
| 
      
 19 
     | 
    
         
            +
                # @param controller_proc [Proc] a proc that, when called, gives a list of {SoberSwag::Controller}s to document
         
     | 
| 
      
 20 
     | 
    
         
            +
                # @param cache [Bool | Proc] if we should cache our defintions (default false)
         
     | 
| 
      
 21 
     | 
    
         
            +
                def initialize(
         
     | 
| 
      
 22 
     | 
    
         
            +
                  controller_proc: RAILS_CONTROLLER_PROC,
         
     | 
| 
      
 23 
     | 
    
         
            +
                  cache: false
         
     | 
| 
      
 24 
     | 
    
         
            +
                )
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @controller_proc = controller_proc
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @cache = cache
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                EFFECT_HTML = <<~HTML.freeze
         
     | 
| 
      
 30 
     | 
    
         
            +
                  <!DOCTYPE html>
         
     | 
| 
      
 31 
     | 
    
         
            +
                  <html>
         
     | 
| 
      
 32 
     | 
    
         
            +
                    <head>
         
     | 
| 
      
 33 
     | 
    
         
            +
                      <title>Swagger-UI</title>
         
     | 
| 
      
 34 
     | 
    
         
            +
                      <script src="https://unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
         
     | 
| 
      
 35 
     | 
    
         
            +
                      <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@3.23.4/swagger-ui.css"></link>
         
     | 
| 
      
 36 
     | 
    
         
            +
                    </head>
         
     | 
| 
      
 37 
     | 
    
         
            +
                    <body>
         
     | 
| 
      
 38 
     | 
    
         
            +
                      <div id="swagger">
         
     | 
| 
      
 39 
     | 
    
         
            +
                      </div>
         
     | 
| 
      
 40 
     | 
    
         
            +
                      <script>
         
     | 
| 
      
 41 
     | 
    
         
            +
                        SwaggerUIBundle({url: 'SCRIPT_NAME', dom_id: '#swagger'})
         
     | 
| 
      
 42 
     | 
    
         
            +
                      </script>
         
     | 
| 
      
 43 
     | 
    
         
            +
                    </body>
         
     | 
| 
      
 44 
     | 
    
         
            +
                  </html>
         
     | 
| 
      
 45 
     | 
    
         
            +
                HTML
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                def call(env)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  req = Rack::Request.new(env)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  if req.path_info&.match?(/json/si) || req.get_header('Accept')&.match?(/json/si)
         
     | 
| 
      
 50 
     | 
    
         
            +
                    [200, { 'Content-Type' => 'application/json' }, [generate_json_string]]
         
     | 
| 
      
 51 
     | 
    
         
            +
                  else
         
     | 
| 
      
 52 
     | 
    
         
            +
                    [200, { 'Content-Type' => 'text/html' }, [EFFECT_HTML.gsub(/SCRIPT_NAME/, env['SCRIPT_NAME'] + '.json')]]
         
     | 
| 
      
 53 
     | 
    
         
            +
                  end
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                def generate_json_string
         
     | 
| 
      
 57 
     | 
    
         
            +
                  if cache?
         
     | 
| 
      
 58 
     | 
    
         
            +
                    @json_string ||= JSON.dump(generate_swagger)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  else
         
     | 
| 
      
 60 
     | 
    
         
            +
                    JSON.dump(generate_swagger)
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                def cache?
         
     | 
| 
      
 65 
     | 
    
         
            +
                  @cache.respond_to?(:call) ? @cache.call : @cache
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                def generate_swagger
         
     | 
| 
      
 69 
     | 
    
         
            +
                  routes = sober_controllers.flat_map(&:defined_routes).reduce(SoberSwag::Compiler.new) { |c, r| c.add_route(r) }
         
     | 
| 
      
 70 
     | 
    
         
            +
                  {
         
     | 
| 
      
 71 
     | 
    
         
            +
                    openapi: '3.0.0',
         
     | 
| 
      
 72 
     | 
    
         
            +
                    info: {
         
     | 
| 
      
 73 
     | 
    
         
            +
                      version: '1',
         
     | 
| 
      
 74 
     | 
    
         
            +
                      title: 'SoberSwag Swagger'
         
     | 
| 
      
 75 
     | 
    
         
            +
                    }
         
     | 
| 
      
 76 
     | 
    
         
            +
                  }.merge(routes.to_swagger)
         
     | 
| 
      
 77 
     | 
    
         
            +
                end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                def sober_controllers
         
     | 
| 
      
 80 
     | 
    
         
            +
                  @controller_proc.call
         
     | 
| 
      
 81 
     | 
    
         
            +
                end
         
     | 
| 
      
 82 
     | 
    
         
            +
              end
         
     | 
| 
      
 83 
     | 
    
         
            +
            end
         
     |