rschema 3.0.1.pre5 → 3.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/README.md +188 -164
- data/lib/rschema.rb +35 -20
- data/lib/rschema/dsl.rb +89 -82
- data/lib/rschema/options.rb +4 -0
- data/lib/rschema/schemas/anything.rb +10 -0
- data/lib/rschema/schemas/boolean.rb +10 -0
- data/lib/rschema/schemas/coercer.rb +8 -0
- data/lib/rschema/schemas/convenience.rb +91 -6
- data/lib/rschema/schemas/enum.rb +10 -0
- data/lib/rschema/schemas/fixed_hash.rb +57 -10
- data/lib/rschema/schemas/fixed_length_array.rb +12 -0
- data/lib/rschema/schemas/maybe.rb +12 -1
- data/lib/rschema/schemas/pipeline.rb +15 -0
- data/lib/rschema/schemas/predicate.rb +11 -0
- data/lib/rschema/schemas/set.rb +10 -0
- data/lib/rschema/schemas/sum.rb +11 -0
- data/lib/rschema/schemas/type.rb +17 -0
- data/lib/rschema/schemas/variable_hash.rb +10 -0
- data/lib/rschema/schemas/variable_length_array.rb +10 -0
- data/lib/rschema/version.rb +1 -1
- metadata +18 -4
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: e971b0b2b3d6a20e7d3a4d30c0426ea3f58e02f7
         | 
| 4 | 
            +
              data.tar.gz: a1cacce7540b7a25698c48e5aa1f57847dc55a85
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 05194c41bc61d36b6ebacc94583ab20fa693e8ad3d3cd69858fcdcd3ac110b73e5953180f8fbc9f5f18b3fa9099f210c925136feb08a90cf27efa4c28463e17f
         | 
| 7 | 
            +
              data.tar.gz: 2f446b9473c06e7ad75ee426a872ec6843271a86b594d87dc07a282b50b51878ac8cbd9ec512cf1a010c55a0d36faf64c517506ea83ba21c7deb17edd9893fd9
         | 
    
        data/README.md
    CHANGED
    
    | @@ -22,7 +22,7 @@ First you create a schema: | |
| 22 22 |  | 
| 23 23 | 
             
                blog_post_schema = RSchema.define_hash {{
         | 
| 24 24 | 
             
                  title: _String,
         | 
| 25 | 
            -
                  tags:  | 
| 25 | 
            +
                  tags: array(_Symbol),
         | 
| 26 26 | 
             
                  body: _String,
         | 
| 27 27 | 
             
                }}
         | 
| 28 28 |  | 
| @@ -33,59 +33,58 @@ Then you can use the schema to validate data: | |
| 33 33 | 
             
                  tags: [:trick, :developers, :unbeleivable],
         | 
| 34 34 | 
             
                  body: '<p>blah blah</p>'
         | 
| 35 35 | 
             
                }
         | 
| 36 | 
            -
                blog_post_schema. | 
| 36 | 
            +
                blog_post_schema.valid?(input) #=> true
         | 
| 37 37 |  | 
| 38 38 | 
             
            What Is A Schema?
         | 
| 39 39 | 
             
            -----------------
         | 
| 40 40 |  | 
| 41 41 | 
             
            Schemas are objects that _describe and validate a value_.
         | 
| 42 42 |  | 
| 43 | 
            -
            The simplest schemas are `Type` schemas, which just  | 
| 43 | 
            +
            The simplest schemas are `Type` schemas, which just check the type of a value.
         | 
| 44 44 |  | 
| 45 45 | 
             
                schema = RSchema.define { _Integer }
         | 
| 46 46 | 
             
                schema.class #=> RSchema::Schemas::Type
         | 
| 47 47 |  | 
| 48 | 
            -
                schema. | 
| 49 | 
            -
                schema. | 
| 48 | 
            +
                schema.valid?(1234) #=> true
         | 
| 49 | 
            +
                schema.valid?('hi') #=> false
         | 
| 50 50 |  | 
| 51 51 | 
             
            Then there are composite schemas, which are schemas composed of subschemas.
         | 
| 52 52 |  | 
| 53 | 
            -
             | 
| 53 | 
            +
            Array schemas are composite schemas:
         | 
| 54 54 |  | 
| 55 | 
            -
                schema = RSchema.define {  | 
| 56 | 
            -
                schema. | 
| 57 | 
            -
                schema. | 
| 55 | 
            +
                schema = RSchema.define { array(_Integer) }
         | 
| 56 | 
            +
                schema.valid?([10, 11, 12])  #=> true
         | 
| 57 | 
            +
                schema.valid?([10, 11, :hi]) #=> false
         | 
| 58 58 |  | 
| 59 | 
            -
            And so are  | 
| 59 | 
            +
            And so are hash schemas:
         | 
| 60 60 |  | 
| 61 | 
            -
                schema = RSchema. | 
| 62 | 
            -
                   | 
| 63 | 
            -
             | 
| 61 | 
            +
                schema = RSchema.define_hash {{
         | 
| 62 | 
            +
                  fname: _String,
         | 
| 63 | 
            +
                  age: _Integer,
         | 
| 64 | 
            +
                }}
         | 
| 64 65 |  | 
| 65 | 
            -
                schema. | 
| 66 | 
            -
                schema. | 
| 66 | 
            +
                schema.valid?({ fname: 'Jane', age: 27 }) #=> true
         | 
| 67 | 
            +
                schema.valid?({ fname: 'Johnny no age' }) #=> false
         | 
| 67 68 |  | 
| 68 69 | 
             
            Schema objects are composable – they are designed to be combined.
         | 
| 69 70 | 
             
            This allows schemas to describe complex, nested data structures.
         | 
| 70 71 |  | 
| 71 | 
            -
                schema = RSchema.define_hash  | 
| 72 | 
            -
                   | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 76 | 
            -
                  }
         | 
| 77 | 
            -
                end
         | 
| 72 | 
            +
                schema = RSchema.define_hash {{
         | 
| 73 | 
            +
                  fname: maybe(_String),
         | 
| 74 | 
            +
                  favourite_foods: set(_Symbol),
         | 
| 75 | 
            +
                  children_by_age: variable_hash(_Integer => _String)
         | 
| 76 | 
            +
                }}
         | 
| 78 77 |  | 
| 79 78 | 
             
                input = {
         | 
| 80 | 
            -
                  fname:  | 
| 81 | 
            -
                  favourite_foods: Set | 
| 79 | 
            +
                  fname: nil,
         | 
| 80 | 
            +
                  favourite_foods: Set[:bacon, :cheese, :onion],
         | 
| 82 81 | 
             
                  children_by_age: {
         | 
| 83 82 | 
             
                    7 => 'Jenny',
         | 
| 84 83 | 
             
                    5 => 'Simon',
         | 
| 85 84 | 
             
                  },
         | 
| 86 85 | 
             
                }
         | 
| 87 86 |  | 
| 88 | 
            -
                schema. | 
| 87 | 
            +
                schema.valid?(input) #=> true
         | 
| 89 88 |  | 
| 90 89 | 
             
            RSchema provides many different kinds of schema classes for common tasks, but
         | 
| 91 90 | 
             
            you can also write custom schema classes if you need to.
         | 
| @@ -95,34 +94,35 @@ The DSL | |
| 95 94 | 
             
            -------
         | 
| 96 95 |  | 
| 97 96 | 
             
            Schemas are usually created and composed via a DSL using `RSchema.define`.
         | 
| 98 | 
            -
             | 
| 97 | 
            +
            Schemas can be created without the DSL, but the DSL is much more succinct.
         | 
| 99 98 |  | 
| 100 99 | 
             
            For example, the following two schemas are identical. `schema1` is created via the
         | 
| 101 100 | 
             
            DSL, and `schema2` is created manually.
         | 
| 102 101 |  | 
| 103 | 
            -
                schema1 = RSchema.define {  | 
| 102 | 
            +
                schema1 = RSchema.define { array(_Symbol) }
         | 
| 104 103 |  | 
| 105 | 
            -
                schema2 = RSchema::Schemas:: | 
| 106 | 
            -
                  RSchema::Schemas:: | 
| 104 | 
            +
                schema2 = RSchema::Schemas::Convenience.wrap(
         | 
| 105 | 
            +
                  RSchema::Schemas::VariableLengthArray.new(
         | 
| 106 | 
            +
                    RSchema::Schemas::Type.new(Symbol)
         | 
| 107 | 
            +
                  )
         | 
| 107 108 | 
             
                )
         | 
| 108 109 |  | 
| 109 | 
            -
            You will probably  | 
| 110 | 
            -
            something advanced | 
| 110 | 
            +
            You will probably not need to create schemas manually unless you are doing
         | 
| 111 | 
            +
            something advanced.
         | 
| 111 112 |  | 
| 112 | 
            -
             | 
| 113 | 
            -
            default DSL, or create a separate, custom DSL to suite your needs.
         | 
| 113 | 
            +
            For a full list of DSL methods, see the API documentation for `RSchema::DSL`.
         | 
| 114 114 |  | 
| 115 115 |  | 
| 116 | 
            -
            When Validation Fails
         | 
| 117 | 
            -
             | 
| 116 | 
            +
            Errors (When Validation Fails)
         | 
| 117 | 
            +
            ------------------------------
         | 
| 118 118 |  | 
| 119 | 
            -
            When  | 
| 119 | 
            +
            When something fails validation, it is often important to know exactly _which
         | 
| 120 120 | 
             
            values_ were invalid, and _why_. RSchema provides details about every
         | 
| 121 121 | 
             
            failure within a result object.
         | 
| 122 122 |  | 
| 123 123 | 
             
                schema = RSchema.define do
         | 
| 124 | 
            -
                   | 
| 125 | 
            -
                     | 
| 124 | 
            +
                  array(
         | 
| 125 | 
            +
                    fixed_hash(
         | 
| 126 126 | 
             
                      name: _String,
         | 
| 127 127 | 
             
                      hair: enum([:red, :brown, :blonde, :black])
         | 
| 128 128 | 
             
                    )
         | 
| @@ -136,13 +136,11 @@ failure within a result object. | |
| 136 136 | 
             
                  { name: 'Chris', hair: :red },
         | 
| 137 137 | 
             
                ]
         | 
| 138 138 |  | 
| 139 | 
            -
                result = schema. | 
| 139 | 
            +
                result = schema.validate(input)
         | 
| 140 140 |  | 
| 141 | 
            -
                result.class | 
| 141 | 
            +
                result.class  #=> RSchema::Result
         | 
| 142 142 | 
             
                result.valid? #=> false
         | 
| 143 | 
            -
                result.error | 
| 144 | 
            -
                result.error[2][:hair].to_s
         | 
| 145 | 
            -
                  #=> "Error RSchema::Schemas::Enum/not_a_member for value: :blond"
         | 
| 143 | 
            +
                result.error  #=> { 2 => { :hair => #<RSchema::Error> } }
         | 
| 146 144 |  | 
| 147 145 | 
             
            The error above says that the value `:blond`, which exists at location
         | 
| 148 146 | 
             
            `input[2][:hair]`, is not a valid enum member. Looking back at the schema, we
         | 
| @@ -156,13 +154,11 @@ error messages for developers or users. | |
| 156 154 |  | 
| 157 155 | 
             
                error.value #=> :blond
         | 
| 158 156 | 
             
                error.symbolic_name #=> :not_a_member
         | 
| 159 | 
            -
                error.schema #=> #<RSchema::Schemas::Enum | 
| 160 | 
            -
                error. | 
| 161 | 
            -
                error.to_s | 
| 162 | 
            -
             | 
| 163 | 
            -
                   | 
| 164 | 
            -
                  # Value: :blond
         | 
| 165 | 
            -
                  # Vars: nil
         | 
| 157 | 
            +
                error.schema #=> #<RSchema::Schemas::Enum ...>
         | 
| 158 | 
            +
                error.schema.members #=> [:red, :brown, :blonde, :black]
         | 
| 159 | 
            +
                error.to_s #=> "RSchema::Schemas::Enum/not_a_member"
         | 
| 160 | 
            +
                error.inspect
         | 
| 161 | 
            +
                  #=> "<RSchema::Error RSchema::Schemas::Enum/not_a_member value=:blond>"
         | 
| 166 162 |  | 
| 167 163 |  | 
| 168 164 | 
             
            Type Schemas
         | 
| @@ -172,8 +168,8 @@ The most basic kind of schema is a `Type` schema. | |
| 172 168 | 
             
            Type schemas validate the class of a value using `is_a?`.
         | 
| 173 169 |  | 
| 174 170 | 
             
                schema = RSchema.define { type(String) }
         | 
| 175 | 
            -
                schema. | 
| 176 | 
            -
                schema. | 
| 171 | 
            +
                schema.valid?('hi') #=> true
         | 
| 172 | 
            +
                schema.valid?(1234) #=> false
         | 
| 177 173 |  | 
| 178 174 | 
             
            Type schemas are so common that the RSchema DSL provides a shorthand way to
         | 
| 179 175 | 
             
            create them, using an underscore prefix:
         | 
| @@ -186,22 +182,21 @@ Because type schemas use `is_a?`, they handle subclasses, and can also be used | |
| 186 182 | 
             
            to check for `include`d modules like `Enumerable`:
         | 
| 187 183 |  | 
| 188 184 | 
             
                schema = RSchema.define { _Enumerable }
         | 
| 189 | 
            -
                schema. | 
| 190 | 
            -
                schema. | 
| 185 | 
            +
                schema.valid?([1, 2, 3]) #=> true
         | 
| 186 | 
            +
                schema.valid?({ a: 12 }) #=> true
         | 
| 191 187 |  | 
| 192 188 | 
             
            Variable-length Array Schemas
         | 
| 193 189 | 
             
            -----------------------------
         | 
| 194 190 |  | 
| 195 191 | 
             
            There are two types of array schemas.
         | 
| 196 | 
            -
            The first type are `VariableLengthArray` schemas,  | 
| 197 | 
            -
            array  | 
| 192 | 
            +
            The first type are `VariableLengthArray` schemas, which check that all elements
         | 
| 193 | 
            +
            in the array conform to a single subschema:
         | 
| 198 194 |  | 
| 199 | 
            -
                schema = RSchema.define {  | 
| 200 | 
            -
                schema.class #=> RSchema::Schemas::VariableLengthArray
         | 
| 195 | 
            +
                schema = RSchema.define { array(_Symbol) }
         | 
| 201 196 |  | 
| 202 | 
            -
                schema. | 
| 203 | 
            -
                schema. | 
| 204 | 
            -
                schema. | 
| 197 | 
            +
                schema.valid?([:a, :b, :c]) #=> true
         | 
| 198 | 
            +
                schema.valid?([:a]) #=> true
         | 
| 199 | 
            +
                schema.valid?([]) #=> true
         | 
| 205 200 |  | 
| 206 201 | 
             
            Fixed-length Array Schemas
         | 
| 207 202 | 
             
            --------------------------
         | 
| @@ -209,37 +204,38 @@ Fixed-length Array Schemas | |
| 209 204 | 
             
            There are also `FixedLengthArray` schemas, where the array must have a specific
         | 
| 210 205 | 
             
            length, and each element of the array has a separate subschema:
         | 
| 211 206 |  | 
| 212 | 
            -
                schema = RSchema.define{  | 
| 213 | 
            -
                schema.class #=> RSchema::Schemas::FixedLengthArray
         | 
| 207 | 
            +
                schema = RSchema.define { array(_Integer, _String) }
         | 
| 214 208 |  | 
| 215 | 
            -
                schema. | 
| 216 | 
            -
                schema. | 
| 217 | 
            -
                schema.call(['heyoo', 33]).valid? #=> false
         | 
| 209 | 
            +
                schema.valid?([10, 'hello']) #=> true
         | 
| 210 | 
            +
                schema.valid?(['heyoo', 33]) #=> false
         | 
| 218 211 |  | 
| 219 212 | 
             
            Fixed Hash Schemas
         | 
| 220 213 | 
             
            ------------------
         | 
| 221 214 |  | 
| 222 215 | 
             
            There are also two kinds of hash schemas.
         | 
| 223 216 |  | 
| 224 | 
            -
            `FixedHash` schemas  | 
| 217 | 
            +
            `FixedHash` schemas describe hashes where they keys are known constants:
         | 
| 225 218 |  | 
| 226 219 | 
             
                schema = RSchema.define do
         | 
| 227 | 
            -
                   | 
| 220 | 
            +
                  fixed_hash(
         | 
| 221 | 
            +
                    name: _String,
         | 
| 222 | 
            +
                    age: _Integer,
         | 
| 223 | 
            +
                  )
         | 
| 228 224 | 
             
                end
         | 
| 229 225 |  | 
| 230 | 
            -
                schema. | 
| 226 | 
            +
                schema.valid?({ name: 'George', age: 2 }) #=> true
         | 
| 231 227 |  | 
| 232 228 | 
             
            Elements can be optional:
         | 
| 233 229 |  | 
| 234 230 | 
             
                schema = RSchema.define do
         | 
| 235 | 
            -
                   | 
| 231 | 
            +
                  fixed_hash(
         | 
| 236 232 | 
             
                    name: _String,
         | 
| 237 233 | 
             
                    optional(:age) => _Integer,
         | 
| 238 234 | 
             
                  )
         | 
| 239 235 | 
             
                end
         | 
| 240 236 |  | 
| 241 | 
            -
                schema. | 
| 242 | 
            -
                schema. | 
| 237 | 
            +
                schema.valid?({ name: 'Lucy', age: 21 }) #=> true
         | 
| 238 | 
            +
                schema.valid?({ name: 'Ageless Tommy' }) #=> true
         | 
| 243 239 |  | 
| 244 240 | 
             
            `FixedHash` schemas are common, so the `RSchema.define_hash` method exists
         | 
| 245 241 | 
             
            to make their creation more convenient:
         | 
| @@ -252,13 +248,13 @@ to make their creation more convenient: | |
| 252 248 | 
             
            Variable Hash Schemas
         | 
| 253 249 | 
             
            ---------------------
         | 
| 254 250 |  | 
| 255 | 
            -
            `VariableHash` schemas are for hashes where the keys are _not_ known  | 
| 251 | 
            +
            `VariableHash` schemas are for hashes where the keys are _not_ known ahead of time.
         | 
| 256 252 | 
             
            They contain one subschema for keys, and another subschema for values.
         | 
| 257 253 |  | 
| 258 | 
            -
                schema = RSchema.define {  | 
| 259 | 
            -
                schema. | 
| 260 | 
            -
                schema. | 
| 261 | 
            -
                schema. | 
| 254 | 
            +
                schema = RSchema.define { variable_hash(_Symbol => _Integer) }
         | 
| 255 | 
            +
                schema.valid?({}) #=> true
         | 
| 256 | 
            +
                schema.valid?({ a: 1 }) #=> true
         | 
| 257 | 
            +
                schema.valid?({ a: 1, b: 2 }) #=> true
         | 
| 262 258 |  | 
| 263 259 | 
             
            Other Schema Types
         | 
| 264 260 | 
             
            ------------------
         | 
| @@ -266,40 +262,40 @@ Other Schema Types | |
| 266 262 | 
             
            RSchema provides a few other schema types through its DSL:
         | 
| 267 263 |  | 
| 268 264 | 
             
                # boolean (only true or false)
         | 
| 269 | 
            -
                boolean_schema = RSchema.define {  | 
| 270 | 
            -
                boolean_schema. | 
| 271 | 
            -
                boolean_schema. | 
| 272 | 
            -
                boolean_schema. | 
| 265 | 
            +
                boolean_schema = RSchema.define { boolean }
         | 
| 266 | 
            +
                boolean_schema.valid?(true)  #=> true
         | 
| 267 | 
            +
                boolean_schema.valid?(false) #=> true
         | 
| 268 | 
            +
                boolean_schema.valid?(nil)   #=> false
         | 
| 273 269 |  | 
| 274 270 | 
             
                # anything (literally any value)
         | 
| 275 271 | 
             
                anything_schema = RSchema.define { anything }
         | 
| 276 | 
            -
                anything_schema. | 
| 277 | 
            -
                anything_schema. | 
| 278 | 
            -
                anything_schema. | 
| 279 | 
            -
                anything_schema. | 
| 272 | 
            +
                anything_schema.valid?('Hi')  #=> true
         | 
| 273 | 
            +
                anything_schema.valid?(true)  #=> true
         | 
| 274 | 
            +
                anything_schema.valid?(1234)  #=> true
         | 
| 275 | 
            +
                anything_schema.valid?(nil)   #=> true
         | 
| 280 276 |  | 
| 281 277 | 
             
                # either (sum types)
         | 
| 282 278 | 
             
                either_schema = RSchema.define { either(_String, _Integer, _Float) }
         | 
| 283 | 
            -
                either_schema. | 
| 284 | 
            -
                either_schema. | 
| 285 | 
            -
                either_schema. | 
| 279 | 
            +
                either_schema.valid?('hi') #=> true
         | 
| 280 | 
            +
                either_schema.valid?(5555) #=> true
         | 
| 281 | 
            +
                either_schema.valid?(77.2) #=> true
         | 
| 286 282 |  | 
| 287 283 | 
             
                # maybe (allows nil)
         | 
| 288 284 | 
             
                maybe_schema = RSchema.define { maybe(_Integer) }
         | 
| 289 | 
            -
                maybe_schema. | 
| 290 | 
            -
                maybe_schema. | 
| 285 | 
            +
                maybe_schema.valid?(5)   #=> true
         | 
| 286 | 
            +
                maybe_schema.valid?(nil) #=> true
         | 
| 291 287 |  | 
| 292 288 | 
             
                # enum (a set of valid values)
         | 
| 293 289 | 
             
                enum_schema = RSchema.define { enum([:a, :b, :c]) }
         | 
| 294 | 
            -
                enum_schema. | 
| 295 | 
            -
                enum_schema. | 
| 290 | 
            +
                enum_schema.valid?(:a) #=> true
         | 
| 291 | 
            +
                enum_schema.valid?(:z) #=> false
         | 
| 296 292 |  | 
| 297 293 | 
             
                # predicate (block returns true for valid values)
         | 
| 298 294 | 
             
                predicate_schema = RSchema.define do
         | 
| 299 295 | 
             
                  predicate { |x| x.even? }
         | 
| 300 296 | 
             
                end
         | 
| 301 | 
            -
                predicate_schema. | 
| 302 | 
            -
                predicate_schema. | 
| 297 | 
            +
                predicate_schema.valid?(4) #=> true
         | 
| 298 | 
            +
                predicate_schema.valid?(5) #=> false
         | 
| 303 299 |  | 
| 304 300 | 
             
                # pipeline (apply multiple schemas to a single value, in order)
         | 
| 305 301 | 
             
                pipeline_schema = RSchema.define do
         | 
| @@ -308,46 +304,12 @@ RSchema provides a few other schema types through its DSL: | |
| 308 304 | 
             
                    predicate { |x| x.positive? },
         | 
| 309 305 | 
             
                  )
         | 
| 310 306 | 
             
                end
         | 
| 311 | 
            -
                pipeline_schema. | 
| 312 | 
            -
                pipeline_schema. | 
| 313 | 
            -
                pipeline_schema. | 
| 314 | 
            -
             | 
| 315 | 
            -
             | 
| 316 | 
            -
            Coercion
         | 
| 317 | 
            -
            --------
         | 
| 318 | 
            -
             | 
| 319 | 
            -
            Coercers convert invalid data into valid data where possible, according to a
         | 
| 320 | 
            -
            schema.
         | 
| 321 | 
            -
             | 
| 322 | 
            -
            Take HTTP params as an example. Web forms often contain database IDs, which
         | 
| 323 | 
            -
            are integers, but are submitted as strings by the browser. Param hash keys
         | 
| 324 | 
            -
            are often expected to be `Symbol`s, but are also strings. The `HTTPCoercer`
         | 
| 325 | 
            -
            can automatically convert these strings into the appropriate type, based on a
         | 
| 326 | 
            -
            schema.
         | 
| 327 | 
            -
             | 
| 328 | 
            -
                # Input keys and values are all strings.
         | 
| 329 | 
            -
                input_params = {
         | 
| 330 | 
            -
                  'whatever_id' => '5',
         | 
| 331 | 
            -
                  'amount' => '123.45',
         | 
| 332 | 
            -
                }
         | 
| 333 | 
            -
             | 
| 334 | 
            -
                # The schema expects symbol keys, an integer value, and a float value.
         | 
| 335 | 
            -
                param_schema = RSchema.define_hash {{
         | 
| 336 | 
            -
                  whatever_id: _Integer,
         | 
| 337 | 
            -
                  amount: _Float,
         | 
| 338 | 
            -
                }}
         | 
| 339 | 
            -
             | 
| 340 | 
            -
                # The schema is wrapped in a HTTPCoercer.
         | 
| 341 | 
            -
                coercer = RSchema::HTTPCoercer.wrap(param_schema)
         | 
| 342 | 
            -
             | 
| 343 | 
            -
                # Use the coercer like a normal schema object.
         | 
| 344 | 
            -
                result = coercer.call(input_params)
         | 
| 345 | 
            -
             | 
| 346 | 
            -
                # The result object contains the coerced value
         | 
| 347 | 
            -
                result.valid? #=> true
         | 
| 348 | 
            -
                result.value #=> { :whatever_id => 5, :amount => 123.45 }
         | 
| 307 | 
            +
                pipeline_schema.valid?(123) #=> true
         | 
| 308 | 
            +
                pipeline_schema.valid?(5.1) #=> true
         | 
| 309 | 
            +
                pipeline_schema.valid?(-24) #=> false
         | 
| 349 310 |  | 
| 350 | 
            -
             | 
| 311 | 
            +
            For a full list of built-in schema types, see the API documentation for all
         | 
| 312 | 
            +
            classes in the `RSchema::Schemas` module.
         | 
| 351 313 |  | 
| 352 314 | 
             
            Extending The DSL
         | 
| 353 315 | 
             
            -----------------
         | 
| @@ -371,26 +333,25 @@ And your methods will be available via `RSchema.define`: | |
| 371 333 |  | 
| 372 334 | 
             
                schema = RSchema.define { palendrome }
         | 
| 373 335 |  | 
| 374 | 
            -
                schema. | 
| 375 | 
            -
                schema. | 
| 376 | 
            -
             | 
| 377 | 
            -
            This is the preferred way for other gems to extend RSchema with new kinds
         | 
| 378 | 
            -
            of schema classes.
         | 
| 336 | 
            +
                schema.valid?('racecar') #=> true
         | 
| 337 | 
            +
                schema.valid?('ferrari') #=> false
         | 
| 379 338 |  | 
| 339 | 
            +
            This is the preferred way for you, and other gems, to extend RSchema with new
         | 
| 340 | 
            +
            DSL methods.
         | 
| 380 341 |  | 
| 381 342 | 
             
            Creating Your Own DSL
         | 
| 382 343 | 
             
            ---------------------
         | 
| 383 344 |  | 
| 384 | 
            -
            The default DSL is designed to be extended (i.e. modified) by  | 
| 385 | 
            -
            If you want a DSL that isn't affected by external factors, | 
| 386 | 
            -
            yourself.
         | 
| 345 | 
            +
            The default DSL is designed to be extended (i.e. modified) by you, and
         | 
| 346 | 
            +
            third-party gems. If you want a DSL that isn't affected by external factors,
         | 
| 347 | 
            +
            you can create one yourself.
         | 
| 387 348 |  | 
| 388 | 
            -
            Create a new class, and include `RSchema::DSL`  | 
| 389 | 
            -
            methods that come built-in to RSchema. You can define your own | 
| 390 | 
            -
            on this class.
         | 
| 349 | 
            +
            Create a new class, and include `RSchema::DSL` if you want have all the
         | 
| 350 | 
            +
            standard DSL methods that come built-in to RSchema. You can define your own
         | 
| 351 | 
            +
            custom methods on this class.
         | 
| 391 352 |  | 
| 392 353 | 
             
                class MyCustomDSL
         | 
| 393 | 
            -
                  include RSchema::DSL
         | 
| 354 | 
            +
                  include RSchema::DSL # this is optional
         | 
| 394 355 |  | 
| 395 356 | 
             
                  def palendrome
         | 
| 396 357 | 
             
                    pipeline(
         | 
| @@ -400,22 +361,76 @@ on this class. | |
| 400 361 | 
             
                  end
         | 
| 401 362 | 
             
                end
         | 
| 402 363 |  | 
| 403 | 
            -
            Then  | 
| 364 | 
            +
            Then pass an instance of your DSL class into `RSchema.define`:
         | 
| 404 365 |  | 
| 405 | 
            -
                 | 
| 406 | 
            -
                schema. | 
| 366 | 
            +
                dsl = MyCustomDSL.new
         | 
| 367 | 
            +
                schema = RSchema.define(dsl) { palendrome }
         | 
| 368 | 
            +
                schema.valid?('racecar') #=> true
         | 
| 407 369 |  | 
| 408 | 
            -
             | 
| 370 | 
            +
            Coercion
         | 
| 371 | 
            +
            --------
         | 
| 409 372 |  | 
| 373 | 
            +
            Coercers convert invalid data into valid data where possible, according to a
         | 
| 374 | 
            +
            schema.
         | 
| 375 | 
            +
             | 
| 376 | 
            +
            Take HTTP params as an example. Web forms often contain database IDs, which
         | 
| 377 | 
            +
            are integers, but are submitted as strings by the browser. Param hash keys
         | 
| 378 | 
            +
            are often expected to be `Symbol`s, but are also strings. RSchema can
         | 
| 379 | 
            +
            automatically convert these strings into the appropriate type, based on a
         | 
| 380 | 
            +
            schema.
         | 
| 410 381 |  | 
| 411 | 
            -
             | 
| 412 | 
            -
             | 
| 382 | 
            +
                # Input keys and values are all strings
         | 
| 383 | 
            +
                input_params = {
         | 
| 384 | 
            +
                  'whatever_id' => '5',
         | 
| 385 | 
            +
                  'amount' => '123.45',
         | 
| 386 | 
            +
                }
         | 
| 387 | 
            +
             | 
| 388 | 
            +
                # The schema expects symbol keys, an integer value, and a float value
         | 
| 389 | 
            +
                schema = RSchema.define_hash {{
         | 
| 390 | 
            +
                  whatever_id: _Integer,
         | 
| 391 | 
            +
                  amount: _Float,
         | 
| 392 | 
            +
                }}
         | 
| 393 | 
            +
             | 
| 394 | 
            +
                # A coercer is created by wrapping the schema
         | 
| 395 | 
            +
                coercer = RSchema::CoercionWrapper::RACK_PARAMS.wrap(schema)
         | 
| 396 | 
            +
             | 
| 397 | 
            +
                # Use the coercer like a normal schema object
         | 
| 398 | 
            +
                result = coercer.validate(input_params)
         | 
| 399 | 
            +
             | 
| 400 | 
            +
                # The result object contains the coerced value
         | 
| 401 | 
            +
                result.valid? #=> true
         | 
| 402 | 
            +
                result.value #=> { :whatever_id => 5, :amount => 123.45 }
         | 
| 403 | 
            +
             | 
| 404 | 
            +
            Custom Coercion
         | 
| 405 | 
            +
            ---------------
         | 
| 406 | 
            +
             | 
| 407 | 
            +
            Coercion is designed to be totally extensible. You'll have to take my word
         | 
| 408 | 
            +
            for it, because there isn't much documentation at the moment.
         | 
| 409 | 
            +
             | 
| 410 | 
            +
            See `lib/rschema/coercion_wrapper/rack_params.rb` for an example of how to
         | 
| 411 | 
            +
            make a coercion wrapper.
         | 
| 412 | 
            +
             | 
| 413 | 
            +
            See all the classes in `lib/rschema/coercers/` for examples of how to make
         | 
| 414 | 
            +
            individual coercers.
         | 
| 415 | 
            +
             | 
| 416 | 
            +
            If you have any problems or questions about implementing custom coercion, feel
         | 
| 417 | 
            +
            free to contact me (Tom Dalling).
         | 
| 418 | 
            +
             | 
| 419 | 
            +
             | 
| 420 | 
            +
            Implementing Your Own Schema Types
         | 
| 421 | 
            +
            ----------------------------------
         | 
| 413 422 |  | 
| 414 423 | 
             
            Schemas are objects that conform to a certain interface (i.e. a duck type).
         | 
| 415 424 | 
             
            To create your own schema types, you just need to implement this interface.
         | 
| 416 425 |  | 
| 426 | 
            +
            The interface consists of two methods: `call` and `with_wrapped_subschemas`.
         | 
| 427 | 
            +
            The `call` method is the interface for validating values.
         | 
| 428 | 
            +
            The `with_wrapped_subschemas` is necessary for coercion to work.
         | 
| 429 | 
            +
            If you have any problems or questions regarding this schema interface, feel
         | 
| 430 | 
            +
            free to contact me (Tom Dalling).
         | 
| 431 | 
            +
             | 
| 417 432 | 
             
            Below is a custom schema for pairs – arrays with two elements of the same type.
         | 
| 418 | 
            -
            This is already possible using existing schemas (e.g. ` | 
| 433 | 
            +
            This is already possible using existing schemas (e.g. `array(_String, _String)`),
         | 
| 419 434 | 
             
            and is only shown here for the purpose of demonstration.
         | 
| 420 435 |  | 
| 421 436 | 
             
                class PairSchema
         | 
| @@ -423,19 +438,31 @@ and is only shown here for the purpose of demonstration. | |
| 423 438 | 
             
                    @subschema = subschema
         | 
| 424 439 | 
             
                  end
         | 
| 425 440 |  | 
| 426 | 
            -
                   | 
| 441 | 
            +
                  #
         | 
| 442 | 
            +
                  # This method is mandatory.
         | 
| 443 | 
            +
                  #
         | 
| 444 | 
            +
                  # `pair` is the value to validate.
         | 
| 445 | 
            +
                  # `options` is an `RSchema::Options` object.
         | 
| 446 | 
            +
                  # This method must return a `RSchema::Result` object
         | 
| 447 | 
            +
                  #
         | 
| 448 | 
            +
                  def call(pair, options)
         | 
| 427 449 | 
             
                    return not_an_array_failure(pair) unless pair.is_a?(Array)
         | 
| 428 450 | 
             
                    return not_a_pair_failure(pair) unless pair.size == 2
         | 
| 429 451 |  | 
| 452 | 
            +
                    # pass both array elements to `@subschema.call`
         | 
| 430 453 | 
             
                    subresults = pair.map { |x| @subschema.call(x, options) }
         | 
| 431 454 |  | 
| 455 | 
            +
                    # check if both elements are valid according to @subschema
         | 
| 432 456 | 
             
                    if subresults.all?(&:valid?)
         | 
| 433 | 
            -
                      RSchema::Result.success(subresults.map(&:value) | 
| 457 | 
            +
                      RSchema::Result.success(subresults.map(&:value))
         | 
| 434 458 | 
             
                    else
         | 
| 435 459 | 
             
                      RSchema::Result.failure(subschema_error(subresults))
         | 
| 436 460 | 
             
                    end
         | 
| 437 461 | 
             
                  end
         | 
| 438 462 |  | 
| 463 | 
            +
                  #
         | 
| 464 | 
            +
                  # This method is necessary for coercion to work
         | 
| 465 | 
            +
                  #
         | 
| 439 466 | 
             
                  def with_wrapped_subschemas(wrapper)
         | 
| 440 467 | 
             
                    PairSchema.new(wrapper.wrap(@subschema))
         | 
| 441 468 | 
             
                  end
         | 
| @@ -458,25 +485,22 @@ and is only shown here for the purpose of demonstration. | |
| 458 485 | 
             
                          symbolic_name: :not_a_pair,
         | 
| 459 486 | 
             
                          schema: self,
         | 
| 460 487 | 
             
                          value: pair,
         | 
| 461 | 
            -
                          vars: {
         | 
| 462 | 
            -
                            expected_size: 2,
         | 
| 463 | 
            -
                            actual_size: pair.size,
         | 
| 464 | 
            -
                          }
         | 
| 465 488 | 
             
                        )
         | 
| 466 489 | 
             
                      )
         | 
| 467 490 | 
             
                    end
         | 
| 468 491 |  | 
| 492 | 
            +
                    #
         | 
| 493 | 
            +
                    # Returns a hash of index => error
         | 
| 494 | 
            +
                    #
         | 
| 469 495 | 
             
                    def subschema_error(subresults)
         | 
| 470 496 | 
             
                      subresults
         | 
| 471 497 | 
             
                        .each_with_index
         | 
| 472 498 | 
             
                        .select { |(result, idx)| result.invalid? }
         | 
| 473 | 
            -
                        .map( | 
| 499 | 
            +
                        .map { |(result, idx)| [idx, result.error] }
         | 
| 474 500 | 
             
                        .to_h
         | 
| 475 501 | 
             
                    end
         | 
| 476 502 | 
             
                end
         | 
| 477 503 |  | 
| 478 | 
            -
            TODO: need to explain how to implement `#call` and `#with_wrapped_subschemas`
         | 
| 479 | 
            -
             | 
| 480 504 | 
             
            Add your new schema class to the default DSL:
         | 
| 481 505 |  | 
| 482 506 | 
             
                module PairSchemaDSL
         | 
| @@ -490,13 +514,13 @@ Add your new schema class to the default DSL: | |
| 490 514 | 
             
            Then your schema is accessible from `RSchema.define`:
         | 
| 491 515 |  | 
| 492 516 | 
             
                gps_coordinate_schema = RSchema.define { pair(_Float) }
         | 
| 493 | 
            -
                gps_coordinate_schema. | 
| 517 | 
            +
                gps_coordinate_schema.valid?([1.2, 3.4]) #=> true
         | 
| 494 518 |  | 
| 495 519 | 
             
            Coercion should work, as long as `#with_wrapped_subschemas` was implemented
         | 
| 496 520 | 
             
            correctly.
         | 
| 497 521 |  | 
| 498 | 
            -
                coercer = RSchema:: | 
| 499 | 
            -
                result = coercer. | 
| 522 | 
            +
                coercer = RSchema::CoercionWrapper::RACK_PARAMS.wrap(gps_coordinate_schema)
         | 
| 523 | 
            +
                result = coercer.validate(["1", "2"])
         | 
| 500 524 | 
             
                result.valid? #=> true
         | 
| 501 525 | 
             
                result.value #=> [1.0, 2.0]
         | 
| 502 526 |  |