[
  {
    "path": ".coveralls.yml",
    "content": "service_name: github\n"
  },
  {
    "path": ".dockerignore",
    "content": "## MAC OS\n.DS_Store\n.com.apple.timemachine.supported\n\n## TEXTMATE\n*.tmproj\ntmtags\n\n## EMACS\n*~\n\\#*\n.\\#*\n\n## REDCAR\n.redcar\n\n## VIM\n*.swp\n*.swo\n\n## RUBYMINE\n.idea\n\n## PROJECT::GENERAL\ncoverage\ndoc\npkg\n.rvmrc\n.ruby-version\n.ruby-gemset\n.rspec_status\n.bundle\n.byebug_history\ndist\nGemfile.lock\ngemfiles/*.lock\ntmp\n.yardoc\n\n## Rubinius\n.rbx\n\n## Bundler binstubs\nbin\n\n## ripper-tags and gem-ctags\ntags\n\n## PROJECT::SPECIFIC\n.project"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "tidelift: \"rubygems/grape\"\ngithub: [dblock, ericproulx]\n"
  },
  {
    "path": ".github/workflows/danger-comment.yml",
    "content": "name: Danger Comment\n\non:\n  workflow_run:\n    workflows: [Danger]\n    types: [completed]\n\njobs:\n  comment:\n    uses: numbata/danger-pr-comment/.github/workflows/danger-comment.yml@v0.1.0\n    secrets: inherit\n"
  },
  {
    "path": ".github/workflows/danger.yml",
    "content": "name: Danger\n\non:\n  pull_request:\n    types: [opened, reopened, edited, synchronize]\n\njobs:\n  danger:\n    uses: numbata/danger-pr-comment/.github/workflows/danger-run.yml@v0.1.0\n    secrets: inherit\n    with:\n      ruby-version: '3.4'\n      bundler-cache: true\n"
  },
  {
    "path": ".github/workflows/edge.yml",
    "content": "---\nname: edge\non: workflow_dispatch\njobs:\n  test:\n    strategy:\n      fail-fast: false\n      matrix:\n        ruby: ['3.2', '3.3', '3.4', '4.0', ruby-head, truffleruby-head, jruby-head]\n        gemfile: [rails_edge, rack_edge]\n    runs-on: ubuntu-latest\n    continue-on-error: true\n    env:\n      BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Ruby\n      uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: ${{ matrix.ruby }}\n        bundler-cache: true\n\n    - name: Run tests\n      run: \"RUBYOPT='--enable=frozen-string-literal' bundle exec rspec\"\n\n    - name: Coveralls\n      uses: coverallsapp/github-action@v2\n      with:\n        github-token: ${{ secrets.GITHUB_TOKEN }}\n        flag-name: run-${{ matrix.ruby }}-${{ matrix.gemfile }}\n        parallel: true\n\n  finish:\n    needs: test\n    runs-on: ubuntu-latest\n    steps:\n    - name: Coveralls Finished\n      uses: coverallsapp/github-action@v2\n      with:\n        github-token: ${{ secrets.github_token }}\n        parallel-finished: true\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: test\n\non: [push, pull_request]\n\njobs:\n  lint:\n    name: RuboCop\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Ruby\n      uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: 3.4\n        bundler-cache: true\n        rubygems: latest\n\n    - name: Run RuboCop\n      run: bundle exec rubocop\n\n  test:\n    strategy:\n      fail-fast: false\n      matrix:\n        ruby: ['3.2', '3.3', '3.4', '4.0']\n        gemfile:\n          - Gemfile\n          - gemfiles/rack_2_2.gemfile\n          - gemfiles/rack_3_0.gemfile\n          - gemfiles/rack_3_1.gemfile\n          - gemfiles/rack_3_2.gemfile\n          - gemfiles/rails_7_2.gemfile\n          - gemfiles/rails_8_0.gemfile\n          - gemfiles/rails_8_1.gemfile\n        specs: ['spec --exclude-pattern=spec/integration/{grape_entity,hashie,dry_validation,multi_*}/*_spec.rb']\n        include:\n          - ruby: '4.0'\n            gemfile: gemfiles/grape_entity.gemfile\n            specs: 'spec/integration/grape_entity'\n          - ruby: '4.0'\n            gemfile: gemfiles/hashie.gemfile\n            specs: 'spec/integration/hashie'\n          - ruby: '4.0'\n            gemfile: gemfiles/dry_validation.gemfile\n            specs: 'spec/integration/dry_validation'\n          - ruby: '4.0'\n            gemfile: gemfiles/multi_json.gemfile\n            specs: 'spec/integration/multi_json'\n          - ruby: '4.0'\n            gemfile: gemfiles/multi_xml.gemfile\n            specs: 'spec/integration/multi_xml'\n    runs-on: ubuntu-latest\n    env:\n      BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Ruby\n      uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: ${{ matrix.ruby }}\n        bundler-cache: true\n\n    - name: Run Tests (${{ matrix.specs }})\n      run: \"RUBYOPT='--enable=frozen-string-literal' bundle exec rspec ${{ matrix.specs }}\"\n\n    - name: Coveralls\n      uses: coverallsapp/github-action@v2\n      with:\n        github-token: ${{ secrets.GITHUB_TOKEN }}\n        flag-name: run-${{ matrix.ruby }}-${{ matrix.gemfile }}\n        parallel: true\n\n  finish:\n    needs: test\n    runs-on: ubuntu-latest\n    steps:\n    - name: Coveralls Finished\n      uses: coverallsapp/github-action@v2\n      with:\n        github-token: ${{ secrets.github_token }}\n        parallel-finished: true\n"
  },
  {
    "path": ".gitignore",
    "content": "## MAC OS\n.DS_Store\n.com.apple.timemachine.supported\n\n## TEXTMATE\n*.tmproj\ntmtags\n\n## EMACS\n*~\n\\#*\n.\\#*\n\n## REDCAR\n.redcar\n\n## VIM\n*.swp\n*.swo\n\n## RUBYMINE\n.idea\n\n## PROJECT::GENERAL\ncoverage\ndoc\npkg\n.rvmrc\n.ruby-version\n.ruby-gemset\n.rspec_status\n.bundle\n.byebug_history\ndist\nGemfile.lock\ngemfiles/*.lock\ntmp\n.yardoc\n\n## Rubinius\n.rbx\n\n## Bundler binstubs\nbin\n\n## ripper-tags and gem-ctags\ntags\n\n## PROJECT::SPECIFIC\n.project\n"
  },
  {
    "path": ".rspec",
    "content": "--require spec_helper\n--color\n--format=documentation\n--order=rand\n--warnings\n"
  },
  {
    "path": ".rubocop.yml",
    "content": "AllCops:\n  NewCops: enable\n  TargetRubyVersion: 3.2\n  SuggestExtensions: false\n  Exclude:\n    - vendor/**/*\n    - bin/**/*\n\nplugins:\n  - rubocop-performance\n  - rubocop-rspec\n\ninherit_from: .rubocop_todo.yml\n\nLayout/LineLength:\n  Max: 215\n\nLint/EmptyBlock:\n  Exclude:\n    - spec/**/*_spec.rb\n\nStyle/Documentation:\n  Enabled: false\n\nStyle/MultilineIfModifier:\n  Enabled: false\n\nStyle/RaiseArgs:\n  Enabled: false\n\nStyle/RedundantArrayConstructor:\n  Enabled: false  # doesn't work well with params definition\n\nStyle/Send:\n  Enabled: true\n\nMetrics/AbcSize:\n  Max: 80 # TODO: revert to 50 once the refactor of public api is done.\n\nMetrics/BlockLength:\n  Max: 30\n  Exclude:\n    - spec/**/*_spec.rb\n\nMetrics/ClassLength:\n  Max: 305\n\nMetrics/CyclomaticComplexity:\n  Max: 15\n\nMetrics/ParameterLists:\n  CountKeywordArgs: false\n  MaxOptionalParameters: 4\n\nMetrics/MethodLength:\n  Max: 32\n\nMetrics/ModuleLength:\n  Max: 220\n\nMetrics/PerceivedComplexity:\n  Max: 15\n\nRSpec/ExampleLength:\n  Max: 60\n\nRSpec/NestedGroups:\n  Max: 6\n\nRSpec/SpecFilePathFormat:\n  Enabled: false\n\nRSpec/SpecFilePathSuffix:\n  Enabled: true\n\nRSpec/MultipleExpectations:\n  Enabled: false\n\nRSpec/NamedSubject:\n  Enabled: false\n\nRSpec/MultipleMemoizedHelpers:\n  Max: 11\n\nRSpec/ContextWording:\n  Enabled: false\n\nRSpec/MessageSpies:\n  EnforcedStyle: receive\n"
  },
  {
    "path": ".rubocop_todo.yml",
    "content": "# This configuration was generated by\n# `rubocop --auto-gen-config`\n# on 2026-01-31 18:13:50 UTC using RuboCop version 1.84.0.\n# The point is for the user to remove these configuration records\n# one by one as the offenses are removed from the code base.\n# Note that changes in the inspected code, or installation of new\n# versions of RuboCop, may require this file to be generated again.\n\n# Offense count: 1\n# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.\nMetrics/MethodLength:\n  Exclude:\n    - 'lib/grape/endpoint.rb'\n\n# Offense count: 18\n# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.\n# SupportedStyles: snake_case, normalcase, non_integer\n# AllowedIdentifiers: TLS1_1, TLS1_2, capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64\nNaming/VariableNumber:\n  Exclude:\n    - 'spec/grape/dsl/settings_spec.rb'\n    - 'spec/grape/exceptions/validation_errors_spec.rb'\n    - 'spec/grape/validations_spec.rb'\n\n# Offense count: 2\n# This cop supports unsafe autocorrection (--autocorrect-all).\n# Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants.\n# SupportedStyles: described_class, explicit\nRSpec/DescribedClass:\n  Exclude:\n    - 'spec/grape/middleware/exception_spec.rb'\n\n# Offense count: 3\n# This cop supports safe autocorrection (--autocorrect).\n# Configuration parameters: CustomTransform, IgnoredWords, DisallowedExamples.\n# DisallowedExamples: works\nRSpec/ExampleWording:\n  Exclude:\n    - 'spec/grape/integration/global_namespace_function_spec.rb'\n    - 'spec/grape/validations_spec.rb'\n\n# Offense count: 2\n# This cop supports safe autocorrection (--autocorrect).\nRSpec/ExpectActual:\n  Exclude:\n    - '**/spec/routing/**/*'\n    - 'spec/grape/middleware/exception_spec.rb'\n\n# Offense count: 1\nRSpec/ExpectInHook:\n  Exclude:\n    - 'spec/grape/validations/validators/values_validator_spec.rb'\n\n# Offense count: 6\n# Configuration parameters: Max, AllowedIdentifiers, AllowedPatterns.\nRSpec/IndexedLet:\n  Exclude:\n    - 'spec/grape/exceptions/validation_errors_spec.rb'\n    - 'spec/grape/presenters/presenter_spec.rb'\n    - 'spec/shared/versioning_examples.rb'\n\n# Offense count: 40\n# Configuration parameters: AssignmentOnly.\nRSpec/InstanceVariable:\n  Exclude:\n    - 'spec/grape/api_spec.rb'\n    - 'spec/grape/endpoint_spec.rb'\n    - 'spec/grape/middleware/base_spec.rb'\n    - 'spec/grape/middleware/versioner/accept_version_header_spec.rb'\n    - 'spec/grape/middleware/versioner/header_spec.rb'\n\n# Offense count: 1\nRSpec/LeakyLocalVariable:\n  Exclude:\n    - 'spec/grape/api_spec.rb'\n\n# Offense count: 1\nRSpec/MessageChain:\n  Exclude:\n    - 'spec/grape/middleware/formatter_spec.rb'\n\n# Offense count: 10\nRSpec/MissingExampleGroupArgument:\n  Exclude:\n    - 'spec/grape/middleware/exception_spec.rb'\n\n# Offense count: 12\nRSpec/RepeatedDescription:\n  Exclude:\n    - 'spec/grape/api_spec.rb'\n    - 'spec/grape/endpoint_spec.rb'\n    - 'spec/grape/validations/validators/allow_blank_validator_spec.rb'\n    - 'spec/grape/validations/validators/values_validator_spec.rb'\n\n# Offense count: 6\nRSpec/RepeatedExample:\n  Exclude:\n    - 'spec/grape/middleware/versioner/accept_version_header_spec.rb'\n    - 'spec/grape/validations/validators/allow_blank_validator_spec.rb'\n\n# Offense count: 8\nRSpec/RepeatedExampleGroupDescription:\n  Exclude:\n    - 'spec/grape/api_spec.rb'\n    - 'spec/grape/endpoint_spec.rb'\n    - 'spec/grape/util/inheritable_setting_spec.rb'\n\n# Offense count: 2\nRSpec/StubbedMock:\n  Exclude:\n    - 'spec/grape/dsl/inside_route_spec.rb'\n    - 'spec/grape/middleware/formatter_spec.rb'\n\n# Offense count: 32\nRSpec/SubjectStub:\n  Exclude:\n    - 'spec/grape/api_spec.rb'\n    - 'spec/grape/dsl/inside_route_spec.rb'\n    - 'spec/grape/dsl/parameters_spec.rb'\n    - 'spec/grape/dsl/routing_spec.rb'\n    - 'spec/grape/middleware/base_spec.rb'\n    - 'spec/grape/middleware/formatter_spec.rb'\n    - 'spec/grape/middleware/globals_spec.rb'\n    - 'spec/grape/middleware/stack_spec.rb'\n\n# Offense count: 20\n# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.\nRSpec/VerifiedDoubles:\n  Exclude:\n    - 'spec/grape/api_spec.rb'\n    - 'spec/grape/dsl/inside_route_spec.rb'\n    - 'spec/grape/integration/rack_sendfile_spec.rb'\n    - 'spec/grape/middleware/formatter_spec.rb'\n\n# Offense count: 2\nRSpec/VoidExpect:\n  Exclude:\n    - 'spec/grape/api_spec.rb'\n    - 'spec/grape/dsl/headers_spec.rb'\n\n# Offense count: 1\n# This cop supports unsafe autocorrection (--autocorrect-all).\nStyle/CombinableLoops:\n  Exclude:\n    - 'spec/grape/endpoint_spec.rb'\n\n# Offense count: 10\n# Configuration parameters: AllowedMethods.\n# AllowedMethods: respond_to_missing?\nStyle/OptionalBooleanParameter:\n  Exclude:\n    - 'lib/grape/dsl/parameters.rb'\n    - 'lib/grape/endpoint.rb'\n    - 'lib/grape/serve_stream/sendfile_response.rb'\n    - 'lib/grape/validations/params_scope.rb'\n    - 'lib/grape/validations/types/array_coercer.rb'\n    - 'lib/grape/validations/types/custom_type_collection_coercer.rb'\n    - 'lib/grape/validations/types/dry_type_coercer.rb'\n    - 'lib/grape/validations/types/primitive_coercer.rb'\n    - 'lib/grape/validations/types/set_coercer.rb'\n"
  },
  {
    "path": ".simplecov",
    "content": "# frozen_string_literal: true\n\nif ENV['GITHUB_USER'] # only when running CI\n  require 'simplecov-lcov'\n  SimpleCov::Formatter::LcovFormatter.config do |c|\n    c.report_with_single_file = true\n    c.single_report_path = 'coverage/lcov.info'\n  end\n\n  SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter\nend\n\nSimpleCov.start do\n  enable_coverage :branch\n  add_filter '/spec/'\nend\n"
  },
  {
    "path": ".yardopts",
    "content": "--asset grape.png\n--markup markdown\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "### 3.2.0 (Next)\n\n#### Features\n\n* [#2662](https://github.com/ruby-grape/grape/pull/2662): Extract `Grape::Util::Translation` for shared I18n fallback logic - [@ericproulx](https://github.com/ericproulx).\n* [#2656](https://github.com/ruby-grape/grape/pull/2656): Remove useless instance_variable_defined? checks - [@ericproulx](https://github.com/ericproulx).\n* [#2619](https://github.com/ruby-grape/grape/pull/2619): Remove TOC from README.md and danger-toc check - [@alexanderadam](https://github.com/alexanderadam).\n* [#2663](https://github.com/ruby-grape/grape/pull/2663): Refactor `ParamsScope` and `Parameters` DSL to use named kwargs - [@ericproulx](https://github.com/ericproulx).\n* [#2664](https://github.com/ruby-grape/grape/pull/2664): Drop `test-prof` dependency - [@ericproulx](https://github.com/ericproulx).\n* [#2665](https://github.com/ruby-grape/grape/pull/2665): Pass `attrs` directly to `AttributesIterator` instead of `validator` - [@ericproulx](https://github.com/ericproulx).\n* Your contribution here.\n\n#### Fixes\n\n* [#2655](https://github.com/ruby-grape/grape/pull/2655): Fix `before_each` method to handle `nil` parameter correctly - [@ericproulx](https://github.com/ericproulx).\n* [#2660](https://github.com/ruby-grape/grape/pull/2660): Fix thread safety: move mutable `ParamsScope` state (`index`, `params_meeting_dependency`) into a per-request `ParamScopeTracker` stored in `Fiber[]` - [@ericproulx](https://github.com/ericproulx).\n* Your contribution here.\n\n### 3.1.0 (2026-01-25)\n\n#### Features\n\n* [#2629](https://github.com/ruby-grape/grape/pull/2629): Refactor Router Architecture - [@ericproulx](https://github.com/ericproulx).\n* [#2633](https://github.com/ruby-grape/grape/pull/2633): Refactor API::Instance and reorganize DSL modules - [@ericproulx](https://github.com/ericproulx).\n* [#2636](https://github.com/ruby-grape/grape/pull/2636): Refactor router to simplify method signatures and reduce duplication - [@ericproulx](https://github.com/ericproulx).\n* [#2640](https://github.com/ruby-grape/grape/pull/2640): Compute available_media_types once - [@ericproulx](https://github.com/ericproulx).\n* [#2637](https://github.com/ruby-grape/grape/pull/2637): Refactor declared method - [@ericproulx](https://github.com/ericproulx).\n* [#2639](https://github.com/ruby-grape/grape/pull/2639): Refactor mime_types_for - [@ericproulx](https://github.com/ericproulx).\n* [#2638](https://github.com/ruby-grape/grape/pull/2638): Remove unnecessary path string duplication - [@ericproulx](https://github.com/ericproulx).\n* [#2643](https://github.com/ruby-grape/grape/pull/2638): Remove `try` method in codebase - [@ericproulx](https://github.com/ericproulx).\n* [#2646](https://github.com/ruby-grape/grape/pull/2646): Call `valid_encoding?` before scrub - [@ericproulx](https://github.com/ericproulx).\n* [#2644](https://github.com/ruby-grape/grape/pull/2644): Clean useless/not valuable dependencies - [@ericproulx](https://github.com/ericproulx).\n* [#2649](https://github.com/ruby-grape/grape/pull/2644): Drop support Ruby 3.0 and ActiveSupport 7.0 - [@ericproulx](https://github.com/ericproulx).\n* [#2648](https://github.com/ruby-grape/grape/pull/2648): Remove deprecated ParamsBuilders extensions - [@ericproulx](https://github.com/ericproulx).\n* [#2645](https://github.com/ruby-grape/grape/pull/2645): Endpoints are compiled when API is compiled - [@ericproulx](https://github.com/ericproulx).\n* [#2647](https://github.com/ruby-grape/grape/pull/2647): Explicit kwargs for `namespace` and `route_param` - [@ericproulx](https://github.com/ericproulx).\n* [#2651](https://github.com/ruby-grape/grape/pull/2651): Migrate Danger to use danger-pr-comment workflow - [@dblock](https://github.com/dblock).\n\n#### Fixes\n    \n* [#2633](https://github.com/ruby-grape/grape/pull/2633): Fix cascade reading - [@ericproulx](https://github.com/ericproulx).\n* [#2641](https://github.com/ruby-grape/grape/pull/2641): Restore support for `return` in endpoint blocks - [@ericproulx](https://github.com/ericproulx).\n* [#2642](https://github.com/ruby-grape/grape/pull/2642): Fix array allocation in base_route.rb - [@ericproulx](https://github.com/ericproulx).\n* Fix `before_each` method to handle `nil` parameter correctly - [@ericproulx](https://github.com/ericproulx).\n\n### 3.0.1 (2025-11-24)\n\n#### Features\n\n* [#2625](https://github.com/ruby-grape/grape/pull/2625): Update rubocop to 1.81.7 and fix style offenses - [@ericproulx](https://github.com/ericproulx).\n* [#2626](https://github.com/ruby-grape/grape/pull/2626): Add rails 8.1 to CI test matrix - [@ericproulx](https://github.com/ericproulx).\n\n#### Fixes\n\n* [#2628](https://github.com/ruby-grape/grape/pull/2628): Fix helpers inheritance - [@giorni](https://github.com/giorni).\n\n### 3.0.0 (2025-11-15)\n\n#### Features\n\n* [#2572](https://github.com/ruby-grape/grape/pull/2572): Drop support ruby 2.7 and active_support 6.1 - [@ericproulx](https://github.com/ericproulx).\n* [#2573](https://github.com/ruby-grape/grape/pull/2573): Clean up deprecated code - [@ericproulx](https://github.com/ericproulx).\n* [#2575](https://github.com/ruby-grape/grape/pull/2575): Refactor Api description class - [@ericproulx](https://github.com/ericproulx).\n* [#2577](https://github.com/ruby-grape/grape/pull/2577): Deprecate `return` in endpoint execution - [@ericproulx](https://github.com/ericproulx).\n* [#2580](https://github.com/ruby-grape/grape/pull/2580): Refactor endpoint helpers and error middleware integration - [@ericproulx](https://github.com/ericproulx).\n* [#2581](https://github.com/ruby-grape/grape/pull/2581): Delegate `to_s` in Grape::API::Instance - [@ericproulx](https://github.com/ericproulx).\n* [#2582](https://github.com/ruby-grape/grape/pull/2582): Fix leaky slash when normalizing - [@ericproulx](https://github.com/ericproulx).\n* [#2583](https://github.com/ruby-grape/grape/pull/2583): Optimize api parameter documentation and memory usage - [@ericproulx](https://github.com/ericproulx).\n* [#2589](https://github.com/ruby-grape/grape/pull/2589): Replace `send` by `__send__` in codebase - [@ericproulx](https://github.com/ericproulx).\n* [#2598](https://github.com/ruby-grape/grape/pull/2598): Refactor settings DSL to use explicit methods instead of dynamic generation - [@ericproulx](https://github.com/ericproulx).\n* [#2599](https://github.com/ruby-grape/grape/pull/2599): Simplify settings DSL get_or_set method and optimize logger implementation - [@ericproulx](https://github.com/ericproulx).\n* [#2600](https://github.com/ruby-grape/grape/pull/2600): Refactor versioner middleware: simplify base class and improve consistency - [@ericproulx](https://github.com/ericproulx).\n* [#2601](https://github.com/ruby-grape/grape/pull/2601): Refactor route_setting internal usage to use inheritable_setting.route for improved consistency and performance - [@ericproulx](https://github.com/ericproulx).\n* [#2602](https://github.com/ruby-grape/grape/pull/2602): Remove `namespace_reverse_stackable` from public DSL interface and use direct inheritable_setting access - [@ericproulx](https://github.com/ericproulx).\n* [#2603](https://github.com/ruby-grape/grape/pull/2603): Remove `namespace_stackable_with_hash` from public interface and move to internal InheritableSetting - [@ericproulx](https://github.com/ericproulx).\n* [#2604](https://github.com/ruby-grape/grape/pull/2604): Enable branch coverage  - [@ericproulx](https://github.com/ericproulx).\n* [#2605](https://github.com/ruby-grape/grape/pull/2605): Add Rack 3.2 support with new gemfile and CI integration - [@ericproulx](https://github.com/ericproulx).\n* [#2607](https://github.com/ruby-grape/grape/pull/2607): Remove namespace_stackable and namespace_inheritable from public API - [@ericproulx](https://github.com/ericproulx).\n* [#2615](https://github.com/ruby-grape/grape/pull/2615): Remove manual toc and tod danger check - [@alexanderadam](https://github.com/alexanderadam).\n* [#2612](https://github.com/ruby-grape/grape/pull/2612): Avoid multiple mount pollution - [@alexanderadam](https://github.com/alexanderadam).\n* [#2617](https://github.com/ruby-grape/grape/pull/2617): Migrate from `ActiveSupport::Configurable` to `Dry::Configurable` - [@ericproulx](https://github.com/ericproulx).\n* [#2618](https://github.com/ruby-grape/grape/pull/2618): Modernize argument delegation for Ruby 3+ compatibility - [@ericproulx](https://github.com/ericproulx).\n* [#2623](https://github.com/ruby-grape/grape/pull/2623): Refactor coercer caching to use `Grape::Util::Cache` - [@ericproulx](https://github.com/ericproulx).\n\n#### Fixes\n\n* [#2586](https://github.com/ruby-grape/grape/pull/2586): Limit helpers DSL public scope - [@ericproulx](https://github.com/ericproulx).\n* [#2588](https://github.com/ruby-grape/grape/pull/2588): Fix defaut format regression on */* - [@ericproulx](https://github.com/ericproulx).\n* [#2593](https://github.com/ruby-grape/grape/pull/2593): Fix warning message when overriding global registry key - [@ericproulx](https://github.com/ericproulx).\n* [#2594](https://github.com/ruby-grape/grape/pull/2594): Fix routes memoization - [@ericproulx](https://github.com/ericproulx).\n* [#2595](https://github.com/ruby-grape/grape/pull/2595): Keep `within_namespace` as part of our internal api - [@ericproulx](https://github.com/ericproulx).\n* [#2596](https://github.com/ruby-grape/grape/pull/2596): Remove `namespace_reverse_stackable_with_hash` from public scope - [@ericproulx](https://github.com/ericproulx).\n* [#2621](https://github.com/ruby-grape/grape/pull/2621): Update upgrading notes regarding `return` usage and simplify endpoint execution - [@ericproulx](https://github.com/ericproulx).\n* [#2622](https://github.com/ruby-grape/grape/pull/2622): Use `require_relative` instead of `$LOAD_PATH` in gemspec - [@ericproulx](https://github.com/ericproulx).\n\n### 2.4.0 (2025-06-18)\n\n#### Features\n\n* [#2532](https://github.com/ruby-grape/grape/pull/2532): Update RuboCop 1.71.2 - [@ericproulx](https://github.com/ericproulx).\n* [#2535](https://github.com/ruby-grape/grape/pull/2535): Delegate calls to inner objects - [@ericproulx](https://github.com/ericproulx).\n* [#2537](https://github.com/ruby-grape/grape/pull/2537): Use activesupport `try` pattern - [@ericproulx](https://github.com/ericproulx).\n* [#2536](https://github.com/ruby-grape/grape/pull/2536): Update normalize_path like Rails - [@ericproulx](https://github.com/ericproulx).\n* [#2540](https://github.com/ruby-grape/grape/pull/2540): Introduce params builder with symbolized short name - [@ericproulx](https://github.com/ericproulx).\n* [#2550](https://github.com/ruby-grape/grape/pull/2550): Drop ActiveSupport 6.0 - [@ericproulx](https://github.com/ericproulx).\n* [#2549](https://github.com/ruby-grape/grape/pull/2549): Delegate cookies management to `Grape::Request` - [@ericproulx](https://github.com/ericproulx).\n* [#2554](https://github.com/ruby-grape/grape/pull/2554): Remove `Grape::Http::Headers` and `Grape::Util::Lazy::Object` - [@ericproulx](https://github.com/ericproulx).\n* [#2556](https://github.com/ruby-grape/grape/pull/2556): Remove unused `Grape::Request::DEFAULT_PARAMS_BUILDER` constant - [@eriklovmo](https://github.com/eriklovmo).\n* [#2558](https://github.com/ruby-grape/grape/pull/2558): Add Ruby's option `enable_frozen_string_literal` in CI - [@ericproulx](https://github.com/ericproulx).\n* [#2557](https://github.com/ruby-grape/grape/pull/2557): Add `lint!` - [@ericproulx](https://github.com/ericproulx).\n* [#2561](https://github.com/ruby-grape/grape/pull/2561): Optimize hash alloc for middleware's default options - [@ericproulx](https://github.com/ericproulx).\n* [#2563](https://github.com/ruby-grape/grape/pull/2563): Update `Grape::Middleware::Auth::Base` - [@ericproulx](https://github.com/ericproulx).\n* [#2571](https://github.com/ruby-grape/grape/pull/2571): Update RuboCop 1.75.8 - [@pieterocp](https://github.com/pieterocp).\n\n#### Fixes\n\n* [#2538](https://github.com/ruby-grape/grape/pull/2538): Fix validating nested json array params - [@mohammednasser-32](https://github.com/mohammednasser-32).\n* [#2543](https://github.com/ruby-grape/grape/pull/2543): Fix array allocation on mount - [@ericproulx](https://github.com/ericproulx).\n* [#2546](https://github.com/ruby-grape/grape/pull/2546): Fix middleware with keywords - [@ericproulx](https://github.com/ericproulx).\n* [#2547](https://github.com/ruby-grape/grape/pull/2547): Remove jsonapi related code - [@ericproulx](https://github.com/ericproulx).\n* [#2548](https://github.com/ruby-grape/grape/pull/2548): Formatting from header acts like versioning from header - [@ericproulx](https://github.com/ericproulx).\n* [#2552](https://github.com/ruby-grape/grape/pull/2552): Fix declared params optional array - [@ericproulx](https://github.com/ericproulx).\n* [#2553](https://github.com/ruby-grape/grape/pull/2553): Improve performance of query params parsing - [@ericproulx](https://github.com/ericproulx).\n\n### 2.3.0 (2025-02-08)\n\n#### Features\n\n* [#2497](https://github.com/ruby-grape/grape/pull/2497): Update RuboCop to 1.66.1 - [@ericproulx](https://github.com/ericproulx).\n* [#2500](https://github.com/ruby-grape/grape/pull/2500): Remove deprecated `file` method - [@ericproulx](https://github.com/ericproulx).\n* [#2501](https://github.com/ruby-grape/grape/pull/2501): Remove deprecated `except` and `proc` options in values validator - [@ericproulx](https://github.com/ericproulx).\n* [#2502](https://github.com/ruby-grape/grape/pull/2502): Remove deprecation `options` in `desc` - [@ericproulx](https://github.com/ericproulx).\n* [#2512](https://github.com/ruby-grape/grape/pull/2512): Optimize hash alloc - [@ericproulx](https://github.com/ericproulx).\n* [#2513](https://github.com/ruby-grape/grape/pull/2513): Optimize Grape::Path - [@ericproulx](https://github.com/ericproulx).\n* [#2514](https://github.com/ruby-grape/grape/pull/2514): Add rails 8.0 to CI - [@ericproulx](https://github.com/ericproulx).\n* [#2516](https://github.com/ruby-grape/grape/pull/2516): Dynamic registration for parsers, formatters, versioners - [@ericproulx](https://github.com/ericproulx).\n* [#2518](https://github.com/ruby-grape/grape/pull/2518): Add ruby 3.4 to CI - [@ericproulx](https://github.com/ericproulx).\n\n#### Fixes\n\n* [#2504](https://github.com/ruby-grape/grape/pull/2504): Fix leaky modules in specs - [@ericproulx](https://github.com/ericproulx).\n* [#2506](https://github.com/ruby-grape/grape/pull/2506): Fix fetch_formatter api_format - [@ericproulx](https://github.com/ericproulx).\n* [#2507](https://github.com/ruby-grape/grape/pull/2507): Fix type: Set with values - [@nikolai-b](https://github.com/nikolai-b).\n* [#2510](https://github.com/ruby-grape/grape/pull/2510): Fix ContractScope's validator inheritance - [@ericproulx](https://github.com/ericproulx).\n* [#2521](https://github.com/ruby-grape/grape/pull/2521): Fixed typo in README - [@datpmt](https://github.com/datpmt).\n* [#2525](https://github.com/ruby-grape/grape/pull/2525): Require logger before active_support - [@ericproulx](https://github.com/ericproulx).\n* [#2524](https://github.com/ruby-grape/grape/pull/2524): Fix validators bad encoding - [@ericproulx](https://github.com/ericproulx).\n* [#2530](https://github.com/ruby-grape/grape/pull/2530): Fix endpoint's status when rescue_from without a block - [@ericproulx](https://github.com/ericproulx).\n* [#2529](https://github.com/ruby-grape/grape/pull/2529): Fix missing settings on mounted routes (when settings are identical) - [@Haerezis](https://github.com/Haerezis).\n\n### 2.2.0 (2024-09-14)\n\n#### Features\n\n* [#2475](https://github.com/ruby-grape/grape/pull/2475): Remove Grape::Util::Registrable - [@ericproulx](https://github.com/ericproulx).\n* [#2484](https://github.com/ruby-grape/grape/pull/2484): Refactor versioner middlewares - [@ericproulx](https://github.com/ericproulx).\n* [#2489](https://github.com/ruby-grape/grape/pull/2489): Add Rails 7.2 in CI workflow - [@ericproulx](https://github.com/ericproulx).\n* [#2493](https://github.com/ruby-grape/grape/pull/2493): MFA required when releasing - [@ericproulx](https://github.com/ericproulx).\n\n#### Fixes\n\n* [#2471](https://github.com/ruby-grape/grape/pull/2471): Fix absence of original_exception and/or backtrace even if passed in error! - [@numbata](https://github.com/numbata).\n* [#2478](https://github.com/ruby-grape/grape/pull/2478): Fix rescue_from with invalid response - [@ericproulx](https://github.com/ericproulx).\n* [#2480](https://github.com/ruby-grape/grape/pull/2480): Fix rescue_from ValidationErrors exception - [@numbata](https://github.com/numbata).\n* [#2464](https://github.com/ruby-grape/grape/pull/2464): The `length` validator only takes effect for parameters with types that support `#length` method - [@OuYangJinTing](https://github.com/OuYangJinTing).\n* [#2485](https://github.com/ruby-grape/grape/pull/2485): Add `is:` param to length validator - [@dakad](https://github.com/dakad).\n* [#2492](https://github.com/ruby-grape/grape/pull/2492): Fix `Grape::Endpoint#inspect` method - [@ericproulx](https://github.com/ericproulx).\n* [#2496](https://github.com/ruby-grape/grape/pull/2496): Reduce object allocation when compiling - [@ericproulx](https://github.com/ericproulx).\n\n### 2.1.3 (2024-07-13)\n\n#### Fixes\n\n* [#2467](https://github.com/ruby-grape/grape/pull/2467): Fix repo coverage - [@ericproulx](https://github.com/ericproulx).\n* [#2468](https://github.com/ruby-grape/grape/pull/2468): Align `error!` method signatures across different places - [@numbata](https://github.com/numbata).\n* [#2469](https://github.com/ruby-grape/grape/pull/2469): Fix full path building for lateral scopes - [@numbata](https://github.com/numbata).\n\n### 2.1.2 (2024-06-28)\n\n#### Fixes\n\n* [#2459](https://github.com/ruby-grape/grape/pull/2459): Autocorrect cops - [@ericproulx](https://github.com/ericproulx).\n* [#3458](https://github.com/ruby-grape/grape/pull/2458): Remove unused Grape::Util::Accept::Header - [@ericproulx](https://github.com/ericproulx).\n* [#2463](https://github.com/ruby-grape/grape/pull/2463): Fix error message indices - [@ericproulx](https://github.com/ericproulx).\n\n### 2.1.1 (2024-06-22)\n\n#### Features\n\n* [#2450](https://github.com/ruby-grape/grape/pull/2450): Update RuboCop to 1.64.1 - [@ericproulx](https://github.com/ericproulx).\n\n#### Fixes\n\n* [#2453](https://github.com/ruby-grape/grape/pull/2453): Fix context in rescue_from - [@ericproulx](https://github.com/ericproulx).\n* [#2455](https://github.com/ruby-grape/grape/pull/2455): Fix default response headers to work with Rack 3 - [@ericproulx](https://github.com/ericproulx).\n\n### 2.1.0 (2024/06/15)\n\n#### Features\n\n* [#2432](https://github.com/ruby-grape/grape/pull/2432): Deep merge for group parameter attributes - [@numbata](https://github.com/numbata).\n* [#2419](https://github.com/ruby-grape/grape/pull/2419): Add the `contract` DSL - [@dgutov](https://github.com/dgutov).\n* [#2371](https://github.com/ruby-grape/grape/pull/2371): Use a param value as the `default` value of other param - [@jcagarcia](https://github.com/jcagarcia).\n* [#2377](https://github.com/ruby-grape/grape/pull/2377): Allow to use instance variables values inside `rescue_from` - [@jcagarcia](https://github.com/jcagarcia).\n* [#2379](https://github.com/ruby-grape/grape/pull/2379): Take into account the `route_param` type in `recognize_path` - [@jcagarcia](https://github.com/jcagarcia).\n* [#2383](https://github.com/ruby-grape/grape/pull/2383): Use regex block instead of if - [@ericproulx](https://github.com/ericproulx).\n* [#2384](https://github.com/ruby-grape/grape/pull/2384): Allow to use `before/after/rescue_from` methods in any order when using `mount` - [@jcagarcia](https://github.com/jcagarcia).\n* [#2390](https://github.com/ruby-grape/grape/pull/2390): Drop support for Ruby 2.6 and Rails 5 - [@ericproulx](https://github.com/ericproulx).\n* [#2393](https://github.com/ruby-grape/grape/pull/2393): Optimize AttributeTranslator - [@ericproulx](https://github.com/ericproulx).\n* [#2395](https://github.com/ruby-grape/grape/pull/2395): Set `max-age` to 0 when `cookies.delete` - [@ericproulx](https://github.com/ericproulx).\n* [#2397](https://github.com/ruby-grape/grape/pull/2397): Add support for ruby 3.3 - [@ericproulx](https://github.com/ericproulx).\n* [#2399](https://github.com/ruby-grape/grape/pull/2399): Update `rubocop` to 1.59.0, `rubocop-performance` to 1.20.1 and `rubocop-rspec` to 2.25.0 - [@ericproulx](https://github.com/ericproulx).\n* [#2402](https://github.com/ruby-grape/grape/pull/2402): Grape::Deprecations will be raised when running specs  - [@ericproulx](https://github.com/ericproulx).\n* [#2406](https://github.com/ruby-grape/grape/pull/2406): Remove mime-types dependency in specs - [@ericproulx](https://github.com/ericproulx).\n* [#2408](https://github.com/ruby-grape/grape/pull/2408): Fix params method redefined warnings - [@ericproulx](https://github.com/ericproulx).\n* [#2410](https://github.com/ruby-grape/grape/pull/2410): Gem deprecations will raise a DeprecationWarning in specs - [@ericproulx](https://github.com/ericproulx).\n* [#2389](https://github.com/ruby-grape/grape/pull/2389): Remove rack-accept dependency - [@ericproulx](https://github.com/ericproulx).\n* [#2426](https://github.com/ruby-grape/grape/pull/2426): Drop support for rack 1.x series - [@ericproulx](https://github.com/ericproulx).\n* [#2427](https://github.com/ruby-grape/grape/pull/2427): Use `rack-contrib` jsonp instead of rack-jsonp - [@ericproulx](https://github.com/ericproulx).\n* [#2363](https://github.com/ruby-grape/grape/pull/2363): Replace autoload by zeitwerk - [@ericproulx](https://github.com/ericproulx).\n* [#2425](https://github.com/ruby-grape/grape/pull/2425): Replace `{}` with `Rack::Header` or `Rack::Utils::HeaderHash` - [@dhruvCW](https://github.com/dhruvCW).\n* [#2430](https://github.com/ruby-grape/grape/pull/2430): Isolate extensions within specific gemfile - [@ericproulx](https://github.com/ericproulx).\n* [#2431](https://github.com/ruby-grape/grape/pull/2431): Drop appraisals in favor of eval_gemfile - [@ericproulx](https://github.com/ericproulx).\n* [#2435](https://github.com/ruby-grape/grape/pull/2435): Use rack constants - [@ericproulx](https://github.com/ericproulx).\n* [#2436](https://github.com/ruby-grape/grape/pull/2436): Update coverallsapp github-action - [@ericproulx](https://github.com/ericproulx).\n* [#2434](https://github.com/ruby-grape/grape/pull/2434): Implement nested `with` support in parameter dsl - [@numbata](https://github.com/numbata).\n* [#2438](https://github.com/ruby-grape/grape/pull/2438): Fix some Rack::Lint - [@ericproulx](https://github.com/ericproulx).\n* [#2437](https://github.com/ruby-grape/grape/pull/2437): Add length validator - [@dhruvCW](https://github.com/dhruvCW).\n* [#2445](https://github.com/ruby-grape/grape/pull/2445): Remove builder as a dependency - [@ericproulx](https://github.com/ericproulx).\n\n#### Fixes\n\n* [#2375](https://github.com/ruby-grape/grape/pull/2375): Fix setter methods for `Grape::Router::AttributeTranslator` - [@Jell](https://github.com/Jell).\n* [#2370](https://github.com/ruby-grape/grape/pull/2370): Remove route_xyz method_missing deprecation - [@ericproulx](https://github.com/ericproulx).\n* [#2372](https://github.com/ruby-grape/grape/pull/2372): Fix `declared` method for hash params with overlapping names - [@jcagarcia](https://github.com/jcagarcia).\n* [#2373](https://github.com/ruby-grape/grape/pull/2373): Fix markdown files for following 1-line format - [@jcagarcia](https://github.com/jcagarcia).\n* [#2382](https://github.com/ruby-grape/grape/pull/2382): Fix values validator for params wrapped in `with` block - [@numbata](https://github.com/numbata).\n* [#2387](https://github.com/ruby-grape/grape/pull/2387): Fix rubygems version within workflows - [@ericproulx](https://github.com/ericproulx).\n* [#2405](https://github.com/ruby-grape/grape/pull/2405): Fix edge workflow - [@ericproulx](https://github.com/ericproulx).\n* [#2414](https://github.com/ruby-grape/grape/pull/2414): Fix Rack::Lint missing content-type - [@ericproulx](https://github.com/ericproulx).\n* [#2378](https://github.com/ruby-grape/grape/pull/2378): Do not overwrite `route_param` with a regular one if they share same name - [@arg](https://github.com/arg).\n* [#2444](https://github.com/ruby-grape/grape/pull/2444): Replace method_missing in endpoint - [@ericproulx](https://github.com/ericproulx).\n* [#2441](https://github.com/ruby-grape/grape/pull/2441): Optimize memory alloc and retained - [@ericproulx](https://github.com/ericproulx).\n* [#2449](https://github.com/ruby-grape/grape/pull/2449): Rack 3.1 fixes - [@ericproulx](https://github.com/ericproulx).\n\n### 2.0.0 (2023/11/11)\n\n#### Features\n\n* [#2353](https://github.com/ruby-grape/grape/pull/2353): Added Rails 7.1 support - [@ericproulx](https://github.com/ericproulx).\n* [#2355](https://github.com/ruby-grape/grape/pull/2355): Set response headers based on Rack version - [@schinery](https://github.com/schinery).\n* [#2360](https://github.com/ruby-grape/grape/pull/2360): Reduce gem size by removing specs - [@ericproulx](https://github.com/ericproulx).\n* [#2361](https://github.com/ruby-grape/grape/pull/2361): Remove `Rack::Auth::Digest` - [@ninoseki](https://github.com/ninoseki).\n\n#### Fixes\n\n* [#2364](https://github.com/ruby-grape/grape/pull/2364): Add missing requires - [@ericproulx](https://github.com/ericproulx).\n* [#2366](https://github.com/ruby-grape/grape/pull/2366): Default quality to 1.0 in the `Accept` header when omitted - [@hiddewie](https://github.com/hiddewie).\n* [#2368](https://github.com/ruby-grape/grape/pull/2368): Stripping the internals of `Grape::Endpoint` when `NoMethodError` is raised - [@jcagarcia](https://github.com/jcagarcia).\n\n### 1.8.0 (2023/08/30)\n\n#### Features\n\n* [#2326](https://github.com/ruby-grape/grape/pull/2326): Use ActiveSupport extensions - [@ericproulx](https://github.com/ericproulx).\n* [#2327](https://github.com/ruby-grape/grape/pull/2327): Use ActiveSupport deprecation - [@ericproulx](https://github.com/ericproulx).\n* [#2330](https://github.com/ruby-grape/grape/pull/2330): Use ActiveSupport inflector - [@ericproulx](https://github.com/ericproulx).\n* [#2331](https://github.com/ruby-grape/grape/pull/2331): Memory optimization when running validators - [@ericproulx](https://github.com/ericproulx).\n* [#2332](https://github.com/ruby-grape/grape/pull/2332): Use ActiveSupport configurable - [@ericproulx](https://github.com/ericproulx).\n* [#2333](https://github.com/ruby-grape/grape/pull/2333): Use custom messages in parameter validation with arity 1 - [@thedevjoao](https://github.com/TheDevJoao).\n* [#2341](https://github.com/ruby-grape/grape/pull/2341): Stop yielding skip value - [@ericproulx](https://github.com/ericproulx).\n* [#2342](https://github.com/ruby-grape/grape/pull/2342): Allow specifying a handler for grape_exceptions - [@mscrivo](https://github.com/mscrivo).\n* [#2338](https://github.com/ruby-grape/grape/pull/2338): Fix unknown validator when using requires/optional with entity - [@mscrivo](https://github.com/mscrivo).\n\n#### Fixes\n\n* [#2339](https://github.com/ruby-grape/grape/pull/2339): Documentation and specs for remountable configuration in params - [@myxoh](https://github.com/myxoh).\n* [#2328](https://github.com/ruby-grape/grape/pull/2328): Don't cache Class.instance_methods - [@byroot](https://github.com/byroot).\n* [#2337](https://github.com/ruby-grape/grape/pull/2337): Fix: allow custom validators that do not end with _validator - [@ericproulx](https://github.com/ericproulx).\n* [#2346](https://github.com/ruby-grape/grape/pull/2346): Adjust test expectations to conform to rack 3 - [@kbarrette](https://github.com/kbarrette).\n\n## 1.7.1 (2023/05/14)\n\n#### Features\n\n* [#2288](https://github.com/ruby-grape/grape/pull/2288): Dropped support for Ruby 2.5 - [@ericproulx](https://github.com/ericproulx).\n* [#2288](https://github.com/ruby-grape/grape/pull/2288): Updated rubocop to 1.41.0 - [@ericproulx](https://github.com/ericproulx).\n* [#2296](https://github.com/ruby-grape/grape/pull/2296): Fix cops and enables some - [@ericproulx](https://github.com/ericproulx).\n* [#2302](https://github.com/ruby-grape/grape/pull/2302): Rack < 3 and update rack-test - [@ericproulx](https://github.com/ericproulx).\n* [#2303](https://github.com/ruby-grape/grape/pull/2302): Rack >= 1.3.0 - [@ericproulx](https://github.com/ericproulx).\n* [#2301](https://github.com/ruby-grape/grape/pull/2301): Revisit GH workflows - [@ericproulx](https://github.com/ericproulx).\n* [#2311](https://github.com/ruby-grape/grape/pull/2311): Fix tests by pinning rack-test to < 2.1 - [@duffn](https://github.com/duffn).\n* [#2310](https://github.com/ruby-grape/grape/pull/2310): Fix YARD docs markdown rendering - [@duffn](https://github.com/duffn).\n* [#2317](https://github.com/ruby-grape/grape/pull/2317): Remove maruku and rubocop-ast as direct development/testing dependencies - [@ericproulx](https://github.com/ericproulx).\n* [#2292](https://github.com/ruby-grape/grape/pull/2292): Introduce Docker to local development - [@ericproulx](https://github.com/ericproulx).\n* [#2325](https://github.com/ruby-grape/grape/pull/2325): Change edge test workflows only run on demand - [@dblock](https://github.com/dblock).\n* [#2324](https://github.com/ruby-grape/grape/pull/2324): Expose default in the description dsl - [@dhruvCW](https://github.com/dhruvCW).\n\n#### Fixes\n\n* [#2299](https://github.com/ruby-grape/grape/pull/2299): Fix, do not use kwargs for empty args  - [@dm1try](https://github.com/dm1try).\n* [#2307](https://github.com/ruby-grape/grape/pull/2307): Fixed autoloading of InvalidValue - [@fixlr](https://github.com/fixlr).\n* [#2315](https://github.com/ruby-grape/grape/pull/2315): Update rspec - [@ericproulx](https://github.com/ericproulx).\n* [#2319](https://github.com/ruby-grape/grape/pull/2319): Update rubocop - [@ericproulx](https://github.com/ericproulx).\n* [#2323](https://github.com/ruby-grape/grape/pull/2323): Fix using endless ranges for values parameter - [@dhruvCW](https://github.com/dhruvCW).\n\n### 1.7.0 (2022/12/20)\n\n#### Features\n\n* [#2233](https://github.com/ruby-grape/grape/pull/2233): Added `do_not_document!` for disabling documentation to internal APIs - [@dnesteryuk](https://github.com/dnesteryuk).\n* [#2235](https://github.com/ruby-grape/grape/pull/2235): Add support for Ruby 3.1 - [@petergoldstein](https://github.com/petergoldstein).\n* [#2248](https://github.com/ruby-grape/grape/pull/2248): Upgraded to rspec 3.11.0 - [@dblock](https://github.com/dblock).\n* [#2249](https://github.com/ruby-grape/grape/pull/2249): Split CI matrix, extract edge - [@dblock](https://github.com/dblock).\n* [#2249](https://github.com/ruby-grape/grape/pull/2251): Upgraded to RuboCop 1.25.1 - [@dblock](https://github.com/dblock).\n* [#2271](https://github.com/ruby-grape/grape/pull/2271): Fixed validation regression on Numeric type introduced in 1.3 - [@vasfed](https://github.com/Vasfed).\n* [#2267](https://github.com/ruby-grape/grape/pull/2267): Standardized English error messages - [@dblock](https://github.com/dblock).\n* [#2272](https://github.com/ruby-grape/grape/pull/2272): Added error on param init when provided type does not have `[]` coercion method, previously validation silently failed for any value - [@vasfed](https://github.com/Vasfed).\n* [#2274](https://github.com/ruby-grape/grape/pull/2274): Error middleware support using rack util's symbols as status - [@dhruvCW](https://github.com/dhruvCW).\n* [#2276](https://github.com/ruby-grape/grape/pull/2276): Fix exception super - [@ericproulx](https://github.com/ericproulx).\n* [#2285](https://github.com/ruby-grape/grape/pull/2285), [#2287](https://github.com/ruby-grape/grape/pull/2287): Added :evaluate_given to declared(params) - [@zysend](https://github.com/zysend).\n\n#### Fixes\n\n* [#2263](https://github.com/ruby-grape/grape/pull/2263): Explicitly require `bigdecimal` and `date` - [@dblock](https://github.com/dblock).\n* [#2222](https://github.com/ruby-grape/grape/pull/2222): Autoload types and validators - [@ericproulx](https://github.com/ericproulx).\n* [#2232](https://github.com/ruby-grape/grape/pull/2232): Fix kwargs support in shared params definition - [@dm1try](https://github.com/dm1try).\n* [#2229](https://github.com/ruby-grape/grape/pull/2229): Do not collect params in route settings - [@dnesteryuk](https://github.com/dnesteryuk).\n* [#2234](https://github.com/ruby-grape/grape/pull/2234): Remove non-UTF8 characters from format before generating JSON error - [@bschmeck](https://github.com/bschmeck).\n* [#2227](https://github.com/ruby-grape/grape/pull/2222): Rename `MissingGroupType` and `UnsupportedGroupType` exceptions - [@ericproulx](https://github.com/ericproulx).\n* [#2244](https://github.com/ruby-grape/grape/pull/2244): Fix a breaking change in `Grape::Validations` provided in 1.6.1 - [@dm1try](https://github.com/dm1try).\n* [#2250](https://github.com/ruby-grape/grape/pull/2250): Add deprecation warning for `UnsupportedGroupTypeError` and `MissingGroupTypeError` - [@ericproulx](https://github.com/ericproulx).\n* [#2256](https://github.com/ruby-grape/grape/pull/2256): Raise `Grape::Exceptions::MultipartPartLimitError` from Rack when too many files are uploaded - [@bschmeck](https://github.com/bschmeck).\n* [#2266](https://github.com/ruby-grape/grape/pull/2266): Fix code coverage - [@duffn](https://github.com/duffn).\n* [#2284](https://github.com/ruby-grape/grape/pull/2284): Fix an unexpected backtick - [@zysend](https://github.com/zysend).\n\n### 1.6.2 (2021/12/30)\n\n#### Fixes\n\n* [#2219](https://github.com/ruby-grape/grape/pull/2219): Revert the changes for autoloading provided in 1.6.1 - [@dm1try](https://github.com/dm1try).\n\n### 1.6.1 (2021/12/28)\n\n#### Features\n\n* [#2196](https://github.com/ruby-grape/grape/pull/2196): Add support for `passwords_hashed` param for `digest_auth` - [@lHydra](https://github.com/lhydra).\n* [#2208](https://github.com/ruby-grape/grape/pull/2208): Added Rails 7 support - [@ericproulx](https://github.com/ericproulx).\n\n#### Fixes\n\n* [#2206](https://github.com/ruby-grape/grape/pull/2206): Require main active_support lib before any of its extension definitions - [@annih](https://github.com/Annih).\n* [#2193](https://github.com/ruby-grape/grape/pull/2193): Fixed the broken ruby-head NoMethodError spec - [@Jack12816](https://github.com/Jack12816).\n* [#2192](https://github.com/ruby-grape/grape/pull/2192): Memoize the result of Grape::Middleware::Base#response - [@Jack12816](https://github.com/Jack12816).\n* [#2200](https://github.com/ruby-grape/grape/pull/2200): Add validators module to all validators - [@ericproulx](https://github.com/ericproulx).\n* [#2202](https://github.com/ruby-grape/grape/pull/2202): Fix random mock spec error - [@ericproulx](https://github.com/ericproulx).\n* [#2203](https://github.com/ruby-grape/grape/pull/2203): Add rubocop-rspec - [@ericproulx](https://github.com/ericproulx).\n* [#2207](https://github.com/ruby-grape/grape/pull/2207): Autoload Validations/Validators - [@ericproulx](https://github.com/ericproulx).\n* [#2209](https://github.com/ruby-grape/grape/pull/2209): Autoload Validations/Types - [@ericproulx](https://github.com/ericproulx).\n\n### 1.6.0 (2021/10/04)\n\n#### Features\n\n* [#2190](https://github.com/ruby-grape/grape/pull/2190): Upgrade dev deps & drop Ruby 2.4.x support - [@dnesteryuk](https://github.com/dnesteryuk).\n\n#### Fixes\n\n* [#2176](https://github.com/ruby-grape/grape/pull/2176): Fix: OPTIONS fails if matching all routes - [@myxoh](https://github.com/myxoh).\n* [#2177](https://github.com/ruby-grape/grape/pull/2177): Fix: `default` validator fails if preceded by `as` validator - [@Catsuko](https://github.com/Catsuko).\n* [#2180](https://github.com/ruby-grape/grape/pull/2180): Call `super` in `API.inherited` - [@yogeshjain999](https://github.com/yogeshjain999).\n* [#2189](https://github.com/ruby-grape/grape/pull/2189): Fix: rename parameters when using `:as` (behaviour and grape-swagger documentation) - [@Jack12816](https://github.com/Jack12816).\n\n### 1.5.3 (2021/03/07)\n\n#### Fixes\n\n* [#2161](https://github.com/ruby-grape/grape/pull/2157): Handle EOFError from Rack when given an empty multipart body - [@bschmeck](https://github.com/bschmeck).\n* [#2162](https://github.com/ruby-grape/grape/pull/2162): Corrected a hash modification while iterating issue - [@Jack12816](https://github.com/Jack12816).\n* [#2164](https://github.com/ruby-grape/grape/pull/2164): Fix: `coerce_with` is now called for params with `nil` value - [@braktar](https://github.com/braktar).\n\n### 1.5.2 (2021/02/06)\n\n#### Features\n\n* [#2157](https://github.com/ruby-grape/grape/pull/2157): Custom types can set a message to be used in the response when invalid - [@dnesteryuk](https://github.com/dnesteryuk).\n* [#2145](https://github.com/ruby-grape/grape/pull/2145): Ruby 3.0 compatibility - [@ericproulx](https://github.com/ericproulx).\n* [#2143](https://github.com/ruby-grape/grape/pull/2143): Enable GitHub Actions with updated RuboCop and Danger - [@anakinj](https://github.com/anakinj).\n\n#### Fixes\n\n* [#2144](https://github.com/ruby-grape/grape/pull/2144): Fix compatibility issue with activesupport 6.1 and XML serialization of arrays - [@anakinj](https://github.com/anakinj).\n* [#2137](https://github.com/ruby-grape/grape/pull/2137): Fix typos - [@johnny-miyake](https://github.com/johnny-miyake).\n* [#2131](https://github.com/ruby-grape/grape/pull/2131): Fix Ruby 2.7 keyword deprecation warning in validators/coerce - [@K0H205](https://github.com/K0H205).\n* [#2132](https://github.com/ruby-grape/grape/pull/2132): Use #ruby2_keywords for correct delegation on Ruby <= 2.6, 2.7 and 3 - [@eregon](https://github.com/eregon).\n* [#2152](https://github.com/ruby-grape/grape/pull/2152): Fix configuration method inside namespaced params - [@fsainz](https://github.com/fsainz).\n\n### 1.5.1 (2020/11/15)\n\n#### Fixes\n\n* [#2129](https://github.com/ruby-grape/grape/pull/2129): Fix validation error when Required Array nested inside an optional array, for Multiparam validators - [@dwhenry](https://github.com/dwhenry).\n* [#2128](https://github.com/ruby-grape/grape/pull/2128): Fix validation error when Required Array nested inside an optional array - [@dwhenry](https://github.com/dwhenry).\n* [#2127](https://github.com/ruby-grape/grape/pull/2127): Fix a performance issue with dependent params - [@dnesteryuk](https://github.com/dnesteryuk).\n* [#2126](https://github.com/ruby-grape/grape/pull/2126): Fix warnings about redefined attribute accessors in `AttributeTranslator` - [@samsonjs](https://github.com/samsonjs).\n* [#2121](https://github.com/ruby-grape/grape/pull/2121): Fix 2.7 deprecation warning in validator_factory - [@Legogris](https://github.com/Legogris).\n* [#2115](https://github.com/ruby-grape/grape/pull/2115): Fix declared_params regression with multiple allowed types - [@stanhu](https://github.com/stanhu).\n* [#2123](https://github.com/ruby-grape/grape/pull/2123): Fix 2.7 deprecation warning in middleware/stack - [@Legogris](https://github.com/Legogris).\n\n### 1.5.0 (2020/10/05)\n\n#### Fixes\n\n* [#2104](https://github.com/ruby-grape/grape/pull/2104): Fix Ruby 2.7 keyword deprecation warning - [@stanhu](https://github.com/stanhu).\n* [#2103](https://github.com/ruby-grape/grape/pull/2103): Ensure complete declared params structure is present - [@tlconnor](https://github.com/tlconnor).\n* [#2099](https://github.com/ruby-grape/grape/pull/2099): Added truffleruby to Travis-CI - [@gogainda](https://github.com/gogainda).\n* [#2089](https://github.com/ruby-grape/grape/pull/2089): Specify order of mounting Grape with Rack::Cascade in README - [@jonmchan](https://github.com/jonmchan).\n* [#2088](https://github.com/ruby-grape/grape/pull/2088): Set `Cache-Control` header only for streamed responses - [@stanhu](https://github.com/stanhu).\n* [#2092](https://github.com/ruby-grape/grape/pull/2092): Correct an example params in Include Missing doc - [@huyvohcmc](https://github.com/huyvohcmc).\n* [#2091](https://github.com/ruby-grape/grape/pull/2091): Fix ruby 2.7 keyword deprecations - [@dim](https://github.com/dim).\n* [#2097](https://github.com/ruby-grape/grape/pull/2097): Skip to set default value unless `meets_dependency?` - [@wanabe](https://github.com/wanabe).\n* [#2096](https://github.com/ruby-grape/grape/pull/2096): Fix redundant dependency check - [@braktar](https://github.com/braktar).\n* [#2096](https://github.com/ruby-grape/grape/pull/2098): Fix nested coercion - [@braktar](https://github.com/braktar).\n* [#2102](https://github.com/ruby-grape/grape/pull/2102): Fix retaining setup blocks when remounting APIs - [@jylamont](https://github.com/jylamont).\n\n### 1.4.0 (2020/07/10)\n\n#### Features\n\n* [#1520](https://github.com/ruby-grape/grape/pull/1520): Un-deprecate stream-like objects - [@urkle](https://github.com/urkle).\n* [#2060](https://github.com/ruby-grape/grape/pull/2060): Drop support for Ruby 2.4 - [@dblock](https://github.com/dblock).\n* [#2060](https://github.com/ruby-grape/grape/pull/2060): Upgraded Rubocop to 0.84.0 - [@dblock](https://github.com/dblock).\n* [#2077](https://github.com/ruby-grape/grape/pull/2077): Simplify logic for defining declared params - [@dnesteryuk](https://github.com/dnesteryuk).\n* [#2076](https://github.com/ruby-grape/grape/pull/2076): Make route information available for hooks when the automatically generated endpoints are invoked - [@anakinj](https://github.com/anakinj).\n\n#### Fixes\n\n* [#2067](https://github.com/ruby-grape/grape/pull/2067): Coerce empty String to `nil` for all primitive types except `String` - [@petekinnecom](https://github.com/petekinnecom).\n* [#2064](https://github.com/ruby-grape/grape/pull/2064): Fix Ruby 2.7 deprecation warning in `Grape::Middleware::Base#initialize` - [@skarger](https://github.com/skarger).\n* [#2072](https://github.com/ruby-grape/grape/pull/2072): Fix `Grape.eager_load!` and `compile!` - [@stanhu](https://github.com/stanhu).\n* [#2084](https://github.com/ruby-grape/grape/pull/2084): Fix memory leak in path normalization - [@fcheung](https://github.com/fcheung).\n\n### 1.3.3 (2020/05/23)\n\n#### Features\n\n* [#2048](https://github.com/ruby-grape/grape/issues/2034): Grape Enterprise support is now available [via TideLift](https://tidelift.com/subscription/request-a-demo?utm_source=rubygems-grape&utm_medium=referral&utm_campaign=enterprise) - [@dblock](https://github.com/dblock).\n* [#2039](https://github.com/ruby-grape/grape/pull/2039): Travis - update rails versions - [@ericproulx](https://github.com/ericproulx).\n* [#2038](https://github.com/ruby-grape/grape/pull/2038): Travis - update ruby versions - [@ericproulx](https://github.com/ericproulx).\n* [#2050](https://github.com/ruby-grape/grape/pull/2050): Refactor route public_send to AttributeTranslator - [@ericproulx](https://github.com/ericproulx).\n\n#### Fixes\n\n* [#2049](https://github.com/ruby-grape/grape/pull/2049): Coerce an empty string to nil in case of the bool type - [@dnesteryuk](https://github.com/dnesteryuk).\n* [#2043](https://github.com/ruby-grape/grape/pull/2043): Modify declared for nested array and hash - [@kadotami](https://github.com/kadotami).\n* [#2040](https://github.com/ruby-grape/grape/pull/2040): Fix a regression with Array of type nil - [@ericproulx](https://github.com/ericproulx).\n* [#2054](https://github.com/ruby-grape/grape/pull/2054): Coercing of nested arrays - [@dnesteryuk](https://github.com/dnesteryuk).\n* [#2050](https://github.com/ruby-grape/grape/pull/2053): Fix broken multiple mounts - [@Jack12816](https://github.com/Jack12816).\n\n### 1.3.2 (2020/04/12)\n\n#### Features\n\n* [#2020](https://github.com/ruby-grape/grape/pull/2020): Reduce array allocation - [@ericproulx](https://github.com/ericproulx).\n* [#2015](https://github.com/ruby-grape/grape/pull/2014): Reduce MatchData allocation - [@ericproulx](https://github.com/ericproulx).\n* [#2014](https://github.com/ruby-grape/grape/pull/2014): Reduce total allocated arrays - [@ericproulx](https://github.com/ericproulx).\n* [#2011](https://github.com/ruby-grape/grape/pull/2011): Reduce total retained regexes - [@ericproulx](https://github.com/ericproulx).\n\n#### Fixes\n\n* [#2033](https://github.com/ruby-grape/grape/pull/2033): Ensure `Float` params are correctly coerced to `BigDecimal` - [@tlconnor](https://github.com/tlconnor).\n* [#2031](https://github.com/ruby-grape/grape/pull/2031): Fix a regression with an array of a custom type - [@dnesteryuk](https://github.com/dnesteryuk).\n* [#2026](https://github.com/ruby-grape/grape/pull/2026): Fix a regression in `coerce_with` when coercion returns `nil` - [@misdoro](https://github.com/misdoro).\n* [#2025](https://github.com/ruby-grape/grape/pull/2025): Fix Decimal type category - [@kdoya](https://github.com/kdoya).\n* [#2019](https://github.com/ruby-grape/grape/pull/2019): Avoid coercing parameter with multiple types to an empty Array - [@stanhu](https://github.com/stanhu).\n\n### 1.3.1 (2020/03/11)\n\n#### Features\n\n* [#2005](https://github.com/ruby-grape/grape/pull/2005): Content types registrable - [@ericproulx](https://github.com/ericproulx).\n* [#2003](https://github.com/ruby-grape/grape/pull/2003): Upgraded Rubocop to 0.80.1 - [@ericproulx](https://github.com/ericproulx).\n* [#2002](https://github.com/ruby-grape/grape/pull/2002): Objects allocation optimization (lazy_lookup) - [@ericproulx](https://github.com/ericproulx).\n\n#### Fixes\n\n* [#2006](https://github.com/ruby-grape/grape/pull/2006): Fix explicit rescue StandardError - [@ericproulx](https://github.com/ericproulx).\n* [#2004](https://github.com/ruby-grape/grape/pull/2004): Rubocop fixes - [@ericproulx](https://github.com/ericproulx).\n* [#1995](https://github.com/ruby-grape/grape/pull/1995): Fix: \"undefined instance variables\" and \"method redefined\" warnings - [@nbeyer](https://github.com/nbeyer).\n* [#1994](https://github.com/ruby-grape/grape/pull/1993): Fix typos in README - [@bellmyer](https://github.com/bellmyer).\n* [#1993](https://github.com/ruby-grape/grape/pull/1993): Lazy join allow header - [@ericproulx](https://github.com/ericproulx).\n* [#1987](https://github.com/ruby-grape/grape/pull/1987): Re-add exactly_one_of mutually exclusive error message - [@ZeroInputCtrl](https://github.com/ZeroInputCtrl).\n* [#1977](https://github.com/ruby-grape/grape/pull/1977): Skip validation for a file if it is optional and nil - [@dnesteryuk](https://github.com/dnesteryuk).\n* [#1976](https://github.com/ruby-grape/grape/pull/1976): Ensure classes/modules listed for autoload really exist - [@dnesteryuk](https://github.com/dnesteryuk).\n* [#1971](https://github.com/ruby-grape/grape/pull/1971): Fix BigDecimal coercion - [@FlickStuart](https://github.com/FlickStuart).\n* [#1968](https://github.com/ruby-grape/grape/pull/1968): Fix args forwarding in Grape::Middleware::Stack#merge_with for ruby 2.7.0 - [@dm1try](https://github.com/dm1try).\n* [#1988](https://github.com/ruby-grape/grape/pull/1988): Refactor the full_messages method and stop overriding full_message - [@hosseintoussi](https://github.com/hosseintoussi).\n* [#1956](https://github.com/ruby-grape/grape/pull/1956): Comply with Rack spec, fix `undefined method [] for nil:NilClass` error when upgrading Rack - [@ioquatix](https://github.com/ioquatix).\n\n### 1.3.0 (2020/01/11)\n\n#### Features\n\n* [#1949](https://github.com/ruby-grape/grape/pull/1949): Add support for Ruby 2.7 - [@nbulaj](https://github.com/nbulaj).\n* [#1948](https://github.com/ruby-grape/grape/pull/1948): Relax `dry-types` dependency version - [@nbulaj](https://github.com/nbulaj).\n* [#1944](https://github.com/ruby-grape/grape/pull/1944): Reduces `attribute_translator` string allocations - [@ericproulx](https://github.com/ericproulx).\n* [#1943](https://github.com/ruby-grape/grape/pull/1943): Reduces number of regex string allocations - [@ericproulx](https://github.com/ericproulx).\n* [#1942](https://github.com/ruby-grape/grape/pull/1942): Optimizes retained memory methods - [@ericproulx](https://github.com/ericproulx).\n* [#1941](https://github.com/ruby-grape/grape/pull/1941): Adds frozen string literal - [@ericproulx](https://github.com/ericproulx).\n* [#1940](https://github.com/ruby-grape/grape/pull/1940): Gets rid of a needless step in `HashWithIndifferentAccess` - [@dnesteryuk](https://github.com/dnesteryuk).\n* [#1938](https://github.com/ruby-grape/grape/pull/1938): Adds project metadata to the gemspec - [@orien](https://github.com/orien).\n* [#1920](https://github.com/ruby-grape/grape/pull/1920): Replaces Virtus with dry-types - [@dnesteryuk](https://github.com/dnesteryuk).\n* [#1930](https://github.com/ruby-grape/grape/pull/1930): Moves block call to separate method so it can be spied on - [@estolfo](https://github.com/estolfo).\n\n#### Fixes\n\n* [#1965](https://github.com/ruby-grape/grape/pull/1965): Fix typos in README - [@davidalee](https://github.com/davidalee).\n* [#1963](https://github.com/ruby-grape/grape/pull/1963): The values validator must properly work with booleans - [@dnesteryuk](https://github.com/dnesteryuk).\n* [#1950](https://github.com/ruby-grape/grape/pull/1950): Consider the allow_blank option in the values validator - [@dnesteryuk](https://github.com/dnesteryuk).\n* [#1947](https://github.com/ruby-grape/grape/pull/1947): Careful check for empty params - [@dnesteryuk](https://github.com/dnesteryuk).\n* [#1931](https://github.com/ruby-grape/grape/pull/1946): Fixes issue when using namespaces in `Grape::API::Instance` mounted directly - [@myxoh](https://github.com/myxoh).\n\n### 1.2.5 (2019/12/01)\n\n#### Features\n\n* [#1931](https://github.com/ruby-grape/grape/pull/1931): Introduces LazyBlock to generate expressions that will executed at mount time - [@myxoh](https://github.com/myxoh).\n* [#1918](https://github.com/ruby-grape/grape/pull/1918): Helper methods to access controller context from middleware - [@NikolayRys](https://github.com/NikolayRys).\n* [#1915](https://github.com/ruby-grape/grape/pull/1915): Micro optimizations in allocating hashes and arrays - [@dnesteryuk](https://github.com/dnesteryuk).\n* [#1904](https://github.com/ruby-grape/grape/pull/1904): Allows Grape to load files on startup rather than on the first call - [@myxoh](https://github.com/myxoh).\n* [#1907](https://github.com/ruby-grape/grape/pull/1907): Adds outside configuration to Grape with `configure` - [@unleashy](https://github.com/unleashy).\n* [#1914](https://github.com/ruby-grape/grape/pull/1914): Run specs in random order - [@splattael](https://github.com/splattael).\n\n#### Fixes\n\n* [#1917](https://github.com/ruby-grape/grape/pull/1917): Update access to rack constant - [@NikolayRys](https://github.com/NikolayRys).\n* [#1916](https://github.com/ruby-grape/grape/pull/1916): Drop old appraisals - [@NikolayRys](https://github.com/NikolayRys).\n* [#1911](https://github.com/ruby-grape/grape/pull/1911): Make sure `Grape::Valiations::AtLeastOneOfValidator` properly treats nested params in errors - [@dnesteryuk](https://github.com/dnesteryuk).\n* [#1893](https://github.com/ruby-grape/grape/pull/1893): Allows `Grape::API` to behave like a Rack::app in some instances where it was misbehaving - [@myxoh](https://github.com/myxoh).\n* [#1898](https://github.com/ruby-grape/grape/pull/1898): Refactor `ValidatorFactory` to improve memory allocation - [@Bhacaz](https://github.com/Bhacaz).\n* [#1900](https://github.com/ruby-grape/grape/pull/1900): Define boolean for `Grape::Api::Instance` - [@Bhacaz](https://github.com/Bhacaz).\n* [#1903](https://github.com/ruby-grape/grape/pull/1903): Allow nested params renaming (Hash/Array) - [@bikolya](https://github.com/bikolya).\n* [#1913](https://github.com/ruby-grape/grape/pull/1913): Fix multiple params validators to return correct messages for nested params - [@bikolya](https://github.com/bikolya).\n* [#1926](https://github.com/ruby-grape/grape/pull/1926): Fixes configuration within given or mounted blocks - [@myxoh](https://github.com/myxoh).\n* [#1937](https://github.com/ruby-grape/grape/pull/1937): Fix bloat in released gem - [@dblock](https://github.com/dblock).\n\n### 1.2.4 (2019/06/13)\n\n#### Features\n\n* [#1888](https://github.com/ruby-grape/grape/pull/1888): Makes the `configuration` hash widely available - [@myxoh](https://github.com/myxoh).\n* [#1864](https://github.com/ruby-grape/grape/pull/1864): Adds `finally` on the API - [@myxoh](https://github.com/myxoh).\n* [#1869](https://github.com/ruby-grape/grape/pull/1869): Fix issue with empty headers after `error!` method call - [@anaumov](https://github.com/anaumov).\n\n#### Fixes\n\n* [#1868](https://github.com/ruby-grape/grape/pull/1868): Fix NoMethodError with none hash params - [@ksss](https://github.com/ksss).\n* [#1876](https://github.com/ruby-grape/grape/pull/1876): Fix const errors being hidden by bug in `const_missing` - [@dandehavilland](https://github.com/dandehavilland).\n\n### 1.2.3 (2019/01/16)\n\n#### Features\n\n* [#1850](https://github.com/ruby-grape/grape/pull/1850): Adds `same_as` validator - [@glaucocustodio](https://github.com/glaucocustodio).\n* [#1833](https://github.com/ruby-grape/grape/pull/1833): Allows to set the `ParamBuilder` globally - [@myxoh](https://github.com/myxoh).\n\n#### Fixes\n\n* [#1852](https://github.com/ruby-grape/grape/pull/1852): `allow_blank` called after `as` when the original param is not blank - [@glaucocustodio](https://github.com/glaucocustodio).\n* [#1844](https://github.com/ruby-grape/grape/pull/1844): Enforce `:tempfile` to be a `Tempfile` object in `File` validator - [@Nyangawa](https://github.com/Nyangawa).\n\n### 1.2.2 (2018/12/07)\n\n#### Features\n\n* [#1832](https://github.com/ruby-grape/grape/pull/1832): Support `body_name` in `desc` block - [@fotos](https://github.com/fotos).\n* [#1831](https://github.com/ruby-grape/grape/pull/1831): Support `security` in `desc` block - [@fotos](https://github.com/fotos).\n\n#### Fixes\n\n* [#1836](https://github.com/ruby-grape/grape/pull/1836): Fix: memory leak not releasing `call` method calls from setup - [@myxoh](https://github.com/myxoh).\n* [#1830](https://github.com/ruby-grape/grape/pull/1830), [#1829](https://github.com/ruby-grape/grape/issues/1829): Restores `self` sanity - [@myxoh](https://github.com/myxoh).\n\n### 1.2.1 (2018/11/28)\n\n#### Fixes\n\n* [#1825](https://github.com/ruby-grape/grape/pull/1825): `to_s` on a mounted class now responses with the API name - [@myxoh](https://github.com/myxoh).\n\n### 1.2.0 (2018/11/26)\n\n#### Features\n\n* [#1813](https://github.com/ruby-grape/grape/pull/1813): Add ruby 2.5 support, drop 2.2. Update rails version in travis - [@darren987469](https://github.com/darren987469).\n* [#1803](https://github.com/ruby-grape/grape/pull/1803): Adds the ability to re-mount all endpoints in any location - [@myxoh](https://github.com/myxoh).\n* [#1795](https://github.com/ruby-grape/grape/pull/1795): Fix vendor/subtype parsing of an invalid Accept header - [@bschmeck](https://github.com/bschmeck).\n* [#1791](https://github.com/ruby-grape/grape/pull/1791): Support `summary`, `hidden`, `deprecated`, `is_array`, `nickname`, `produces`, `consumes`, `tags` options in `desc` block - [@darren987469](https://github.com/darren987469).\n\n#### Fixes\n\n* [#1796](https://github.com/ruby-grape/grape/pull/1796): Fix crash when available locales are enforced but fallback locale unavailable - [@Morred](https://github.com/Morred).\n* [#1776](https://github.com/ruby-grape/grape/pull/1776): Validate response returned by the exception handler - [@darren987469](https://github.com/darren987469).\n* [#1787](https://github.com/ruby-grape/grape/pull/1787): Add documented but not implemented ability to `.insert` a middleware in the stack - [@michaellennox](https://github.com/michaellennox).\n* [#1788](https://github.com/ruby-grape/grape/pull/1788): Fix route requirements bug - [@darren987469](https://github.com/darren987469), [@darrellnash](https://github.com/darrellnash).\n* [#1810](https://github.com/ruby-grape/grape/pull/1810): Fix support in `given` for aliased params - [@darren987469](https://github.com/darren987469).\n* [#1811](https://github.com/ruby-grape/grape/pull/1811): Support nested dependent parameters - [@darren987469](https://github.com/darren987469), [@andreacfm](https://github.com/andreacfm).\n* [#1822](https://github.com/ruby-grape/grape/pull/1822): Raise validation error when optional hash type parameter is received string type value and exactly_one_of be used - [@woshidan](https://github.com/woshidan).\n\n### 1.1.0 (2018/8/4)\n\n#### Features\n\n* [#1759](https://github.com/ruby-grape/grape/pull/1759): Instrument serialization as `'format_response.grape'` - [@zvkemp](https://github.com/zvkemp).\n\n#### Fixes\n\n* [#1762](https://github.com/ruby-grape/grape/pull/1763): Fix unsafe HTML rendering on errors - [@ctennis](https://github.com/ctennis).\n* [#1759](https://github.com/ruby-grape/grape/pull/1759): Update appraisal for rails_edge - [@zvkemp](https://github.com/zvkemp).\n* [#1758](https://github.com/ruby-grape/grape/pull/1758): Fix expanding load_path in gemspec - [@2maz](https://github.com/2maz).\n* [#1765](https://github.com/ruby-grape/grape/pull/1765): Use 415 when request body is of an unsupported media type - [@jdmurphy](https://github.com/jdmurphy).\n* [#1771](https://github.com/ruby-grape/grape/pull/1771): Fix param aliases with 'given' blocks - [@jereynolds](https://github.com/jereynolds).\n\n### 1.0.3 (2018/4/23)\n\n#### Fixes\n\n* [#1755](https://github.com/ruby-grape/grape/pull/1755): Fix shared params with exactly_one_of - [@milgner](https://github.com/milgner).\n* [#1740](https://github.com/ruby-grape/grape/pull/1740): Fix dependent parameter validation using `given` when parameter is a `Hash` - [@jvortmann](https://github.com/jvortmann).\n* [#1737](https://github.com/ruby-grape/grape/pull/1737): Fix translating error when passing symbols as params in custom validations - [@mlzhuyi](https://github.com/mlzhuyi).\n* [#1749](https://github.com/ruby-grape/grape/pull/1749): Allow rescue from non-`StandardError` exceptions - [@dm1try](https://github.com/dm1try).\n* [#1750](https://github.com/ruby-grape/grape/pull/1750): Fix a circular dependency warning due to router being loaded by API - [@salasrod](https://github.com/salasrod).\n* [#1752](https://github.com/ruby-grape/grape/pull/1752): Fix `include_missing` behavior for aliased parameters - [@jonasoberschweiber](https://github.com/jonasoberschweiber).\n* [#1754](https://github.com/ruby-grape/grape/pull/1754): Allow rescue from non-`StandardError` exceptions to use default error handling - [@jelkster](https://github.com/jelkster).\n* [#1756](https://github.com/ruby-grape/grape/pull/1756): Allow custom Grape exception handlers when the built-in exception handling is enabled - [@soylent](https://github.com/soylent).\n\n### 1.0.2 (2018/1/10)\n\n#### Features\n\n* [#1686](https://github.com/ruby-grape/grape/pull/1686): Avoid coercion of a value if it is valid - [@timothysu](https://github.com/timothysu).\n* [#1688](https://github.com/ruby-grape/grape/pull/1688): Removes yard docs - [@ramkumar-kr](https://github.com/ramkumar-kr).\n* [#1702](https://github.com/ruby-grape/grape/pull/1702): Added danger-toc, verify correct TOC in README - [@dblock](https://github.com/dblock).\n* [#1711](https://github.com/ruby-grape/grape/pull/1711): Automatically coerce arrays and sets of types that implement a `parse` method - [@dslh](https://github.com/dslh).\n\n#### Fixes\n\n* [#1710](https://github.com/ruby-grape/grape/pull/1710): Fix wrong transformation of empty Array in declared params - [@pablonahuelgomez](https://github.com/pablonahuelgomez).\n* [#1722](https://github.com/ruby-grape/grape/pull/1722): Fix catch-all hiding multiple versions of an endpoint after the first definition - [@zherr](https://github.com/zherr).\n* [#1724](https://github.com/ruby-grape/grape/pull/1724): Optional nested array validation - [@ericproulx](https://github.com/ericproulx).\n* [#1725](https://github.com/ruby-grape/grape/pull/1725): Fix `rescue_from :all` documentation - [@Jelkster](https://github.com/Jelkster).\n* [#1726](https://github.com/ruby-grape/grape/pull/1726): Improved startup performance during API method generation - [@jkowens](https://github.com/jkowens).\n* [#1727](https://github.com/ruby-grape/grape/pull/1727): Fix infinite loop when mounting endpoint with same superclass - [@jkowens](https://github.com/jkowens).\n\n### 1.0.1 (2017/9/8)\n\n#### Features\n\n* [#1652](https://github.com/ruby-grape/grape/pull/1652): Add the original exception to the error_formatter the original exception - [@dcsg](https://github.com/dcsg).\n* [#1665](https://github.com/ruby-grape/grape/pull/1665): Make helpers available in subclasses - [@pablonahuelgomez](https://github.com/pablonahuelgomez).\n* [#1674](https://github.com/ruby-grape/grape/pull/1674): Add parameter alias (`as`) - [@glaucocustodio](https://github.com/glaucocustodio).\n\n#### Fixes\n\n* [#1652](https://github.com/ruby-grape/grape/pull/1652): Fix missing backtrace that was not being bubbled up to the `error_formatter` - [@dcsg](https://github.com/dcsg).\n* [#1661](https://github.com/ruby-grape/grape/pull/1661): Handle deeply-nested dependencies correctly - [@rnubel](https://github.com/rnubel), [@jnardone](https://github.com/jnardone).\n* [#1679](https://github.com/ruby-grape/grape/pull/1679): Treat StandardError from explicit values validator proc as false - [@jlfaber](https://github.com/jlfaber).\n\n### 1.0.0 (2017/7/3)\n\n#### Features\n\n* [#1594](https://github.com/ruby-grape/grape/pull/1594): Replace `Hashie::Mash` parameters with `ActiveSupport::HashWithIndifferentAccess` - [@james2m](https://github.com/james2m), [@dblock](https://github.com/dblock).\n* [#1622](https://github.com/ruby-grape/grape/pull/1622): Add `except_values` validator to replace `except` option of `values` validator - [@jlfaber](https://github.com/jlfaber).\n* [#1635](https://github.com/ruby-grape/grape/pull/1635): Instrument validators with ActiveSupport::Notifications - [@ktimothy](https://github.com/ktimothy).\n* [#1646](https://github.com/ruby-grape/grape/pull/1646): Add ability to include an array of modules as helpers - [@pablonahuelgomez](https://github.com/pablonahuelgomez).\n* [#1623](https://github.com/ruby-grape/grape/pull/1623): Removed `multi_json` and `multi_xml` dependencies - [@dblock](https://github.com/dblock).\n* [#1650](https://github.com/ruby-grape/grape/pull/1650): Add extra specs for Boolean type field - [@tiarly](https://github.com/tiarly).\n\n#### Fixes\n\n* [#1648](https://github.com/ruby-grape/grape/pull/1631): Declared now returns declared options using the class that params is set to use - [@thogg4](https://github.com/thogg4).\n* [#1632](https://github.com/ruby-grape/grape/pull/1632): Silence warnings - [@thogg4](https://github.com/thogg4).\n* [#1615](https://github.com/ruby-grape/grape/pull/1615): Fix default and type validator when values is a Hash with no value attribute - [@jlfaber](https://github.com/jlfaber).\n* [#1625](https://github.com/ruby-grape/grape/pull/1625): Handle `given` correctly when nested in Array params - [@rnubel](https://github.com/rnubel), [@avellable](https://github.com/avellable).\n* [#1649](https://github.com/ruby-grape/grape/pull/1649): Don't share validator instances between requests - [@anakinj](https://github.com/anakinj).\n\n### 0.19.2 (2017/4/12)\n\n#### Features\n\n* [#1555](https://github.com/ruby-grape/grape/pull/1555): Added code coverage w/Coveralls - [@dblock](https://github.com/dblock).\n* [#1568](https://github.com/ruby-grape/grape/pull/1568): Add `proc` option to `values` validator to allow custom checks - [@jlfaber](https://github.com/jlfaber).\n* [#1575](https://github.com/ruby-grape/grape/pull/1575): Include nil values for missing nested params in declared - [@thogg4](https://github.com/thogg4).\n* [#1585](https://github.com/ruby-grape/grape/pull/1585): Bugs in declared method - make sure correct options var is used and respect include missing for non children params - [@thogg4](https://github.com/thogg4).\n\n#### Fixes\n\n* [#1570](https://github.com/ruby-grape/grape/pull/1570): Make versioner consider the mount destination path - [@namusyaka](https://github.com/namusyaka).\n* [#1579](https://github.com/ruby-grape/grape/pull/1579): Fix delete status with a return value - [@eproulx-petalmd](https://github.com/eproulx-petalmd).\n* [#1559](https://github.com/ruby-grape/grape/pull/1559): You can once again pass `nil` to optional attributes with `values` validation set - [@ghiculescu](https://github.com/ghiculescu).\n* [#1562](https://github.com/ruby-grape/grape/pull/1562): Fix rainbow gem installation failure above ruby 2.3.3 on travis-ci - [@brucehsu](https://github.com/brucehsu).\n* [#1561](https://github.com/ruby-grape/grape/pull/1561): Fix performance issue introduced by duplicated calls in StackableValue#[] - [@brucehsu](https://github.com/brucehsu).\n* [#1564](https://github.com/ruby-grape/grape/pull/1564): Fix declared params bug with nested namespaces - [@bmarini](https://github.com/bmarini).\n* [#1567](https://github.com/ruby-grape/grape/pull/1567): Fix values validator when value is empty array and apply except to input array - [@jlfaber](https://github.com/jlfaber).\n* [#1569](https://github.com/ruby-grape/grape/pull/1569), [#1511](https://github.com/ruby-grape/grape/issues/1511): Upgrade mustermann-grape to 1.0.0 - [@namusyaka](https://github.com/namusyaka).\n* [#1589](https://github.com/ruby-grape/grape/pull/1589): [#726](https://github.com/ruby-grape/grape/issues/726): Use default_format when Content-type is missing and respond with 406 when Content-type is invalid - [@inclooder](https://github.com/inclooder).\n\n### 0.19.1 (2017/1/9)\n\n#### Features\n\n* [#1536](https://github.com/ruby-grape/grape/pull/1536): Updated `invalid_versioner_option` translation - [@Lavode](https://github.com/Lavode).\n* [#1543](https://github.com/ruby-grape/grape/pull/1543): Added support for ruby 2.4 - [@LeFnord](https://github.com/LeFnord), [@namusyaka](https://github.com/namusyaka).\n\n#### Fixes\n\n* [#1548](https://github.com/ruby-grape/grape/pull/1548): Fix: avoid failing even if given path does not match with prefix - [@thomas-peyric](https://github.com/thomas-peyric), [@namusyaka](https://github.com/namusyaka).\n* [#1550](https://github.com/ruby-grape/grape/pull/1550): Fix: return 200 as default status for DELETE - [@jthornec](https://github.com/jthornec).\n\n### 0.19.0 (2016/12/18)\n\n#### Features\n\n* [#1503](https://github.com/ruby-grape/grape/pull/1503): Allowed use of regexp validator with arrays - [@akoltun](https://github.com/akoltun).\n* [#1507](https://github.com/ruby-grape/grape/pull/1507): Added group attributes for parameter definitions - [@304](https://github.com/304).\n* [#1532](https://github.com/ruby-grape/grape/pull/1532): Set 204 as default status for DELETE - [@LeFnord](https://github.com/LeFnord).\n\n#### Fixes\n\n* [#1505](https://github.com/ruby-grape/grape/pull/1505): Run `before` and `after` callbacks, but skip the rest when handling OPTIONS - [@jlfaber](https://github.com/jlfaber).\n* [#1517](https://github.com/ruby-grape/grape/pull/1517), [#1089](https://github.com/ruby-grape/grape/pull/1089): Fix: priority of ANY routes - [@namusyaka](https://github.com/namusyaka), [@wagenet](https://github.com/wagenet).\n* [#1512](https://github.com/ruby-grape/grape/pull/1512): Fix: deeply nested parameters are included within `#declared(params)` - [@krbs](https://github.com/krbs).\n* [#1510](https://github.com/ruby-grape/grape/pull/1510): Fix: inconsistent validation for multiple parameters - [@dgasper](https://github.com/dgasper).\n* [#1526](https://github.com/ruby-grape/grape/pull/1526): Reduced warnings caused by instance variables not initialized - [@cpetschnig](https://github.com/cpetschnig).\n\n### 0.18.0 (2016/10/7)\n\n#### Features\n\n* [#1480](https://github.com/ruby-grape/grape/pull/1480): Used the ruby-grape-danger gem for PR linting - [@dblock](https://github.com/dblock).\n* [#1486](https://github.com/ruby-grape/grape/pull/1486): Implemented except in values validator - [@jonmchan](https://github.com/jonmchan).\n* [#1470](https://github.com/ruby-grape/grape/pull/1470): Dropped support for Ruby 2.0 - [@namusyaka](https://github.com/namusyaka).\n* [#1490](https://github.com/ruby-grape/grape/pull/1490): Switched to Ruby-2.x+ syntax - [@namusyaka](https://github.com/namusyaka).\n* [#1499](https://github.com/ruby-grape/grape/pull/1499): Support `fail_fast` param validation option - [@dgasper](https://github.com/dgasper).\n\n#### Fixes\n\n* [#1498](https://github.com/ruby-grape/grape/pull/1498): Fix: skip validations in inactive given blocks - [@jlfaber](https://github.com/jlfaber).\n* [#1479](https://github.com/ruby-grape/grape/pull/1479): Fix: support inserting middleware before/after anonymous classes in the middleware stack - [@rosa](https://github.com/rosa).\n* [#1488](https://github.com/ruby-grape/grape/pull/1488): Fix: ensure calling before filters when receiving OPTIONS request - [@namusyaka](https://github.com/namusyaka), [@jlfaber](https://github.com/jlfaber).\n* [#1493](https://github.com/ruby-grape/grape/pull/1493): Fix: coercion and lambda fails params validation - [@jonmchan](https://github.com/jonmchan).\n\n### 0.17.0 (2016/7/29)\n\n#### Features\n\n* [#1393](https://github.com/ruby-grape/grape/pull/1393): Middleware can be inserted before or after default Grape middleware - [@ridiculous](https://github.com/ridiculous).\n* [#1390](https://github.com/ruby-grape/grape/pull/1390): Allowed inserting middleware at arbitrary points in the middleware stack - [@rosa](https://github.com/rosa).\n* [#1366](https://github.com/ruby-grape/grape/pull/1366): Stored `message_key` on `Grape::Exceptions::Validation` - [@mkou](https://github.com/mkou).\n* [#1398](https://github.com/ruby-grape/grape/pull/1398): Added `rescue_from :grape_exceptions` - allow Grape to use the built-in `Grape::Exception` handing and use `rescue :all` behavior for everything else - [@mmclead](https://github.com/mmclead).\n* [#1443](https://github.com/ruby-grape/grape/pull/1443): Extended `given` to receive a `Proc` - [@glaucocustodio](https://github.com/glaucocustodio).\n* [#1455](https://github.com/ruby-grape/grape/pull/1455): Added an automated PR linter - [@orta](https://github.com/orta).\n\n#### Fixes\n\n* [#1463](https://github.com/ruby-grape/grape/pull/1463): Fix array indicies in error messages - [@ffloyd](https://github.com/ffloyd).\n* [#1465](https://github.com/ruby-grape/grape/pull/1465): Fix 'before' being called twice when using not allowed method - [@jsteinberg](https://github.com/jsteinberg).\n* [#1446](https://github.com/ruby-grape/grape/pull/1446): Fix for `env` inside `before` when using not allowed method - [@leifg](https://github.com/leifg).\n* [#1438](https://github.com/ruby-grape/grape/pull/1439): Try to dup non-frozen default params with each use - [@jlfaber](https://github.com/jlfaber).\n* [#1430](https://github.com/ruby-grape/grape/pull/1430): Fix for `declared(params)` inside `route_param` - [@Arkanain](https://github.com/Arkanain).\n* [#1405](https://github.com/ruby-grape/grape/pull/1405): Fix priority of `rescue_from` clauses applying - [@hedgesky](https://github.com/hedgesky).\n* [#1365](https://github.com/ruby-grape/grape/pull/1365): Fix finding exception handler in error middleware - [@ktimothy](https://github.com/ktimothy).\n* [#1380](https://github.com/ruby-grape/grape/pull/1380): Fix `allow_blank: false` for `Time` attributes with valid values causes `NoMethodError` - [@ipkes](https://github.com/ipkes).\n* [#1384](https://github.com/ruby-grape/grape/pull/1384): Fix parameter validation with an empty optional nested `Array` - [@ipkes](https://github.com/ipkes).\n* [#1414](https://github.com/ruby-grape/grape/pull/1414): Fix multiple version definitions for path versioning - [@304](https://github.com/304).\n* [#1415](https://github.com/ruby-grape/grape/pull/1415): Fix `declared(params, include_parent_namespaces: false)` - [@304](https://github.com/304).\n* [#1421](https://github.com/ruby-grape/grape/pull/1421): Avoid polluting `Grape::Middleware::Error` - [@namusyaka](https://github.com/namusyaka).\n* [#1422](https://github.com/ruby-grape/grape/pull/1422): Concat parent declared params with current one - [@plukevdh](https://github.com/plukevdh), [@rnubel](https://github.com/rnubel), [@namusyaka](https://github.com/namusyaka).\n\n### 0.16.2 (2016/4/12)\n\n#### Features\n\n* [#1348](https://github.com/ruby-grape/grape/pull/1348): Fix global functions polluting Grape::API scope - [@dblock](https://github.com/dblock).\n* [#1357](https://github.com/ruby-grape/grape/pull/1357): Expose Route#options - [@namusyaka](https://github.com/namusyaka).\n\n#### Fixes\n\n* [#1357](https://github.com/ruby-grape/grape/pull/1357): Don't include fixed named captures as route params - [@namusyaka](https://github.com/namusyaka).\n* [#1359](https://github.com/ruby-grape/grape/pull/1359): Avoid evaluating the same route twice - [@namusyaka](https://github.com/namusyaka), [@dblock](https://github.com/dblock).\n* [#1361](https://github.com/ruby-grape/grape/pull/1361): Return 405 correctly even if version is using as header and wrong request method - [@namusyaka](https://github.com/namusyaka), [@dblock](https://github.com/dblock).\n\n### 0.16.1 (2016/4/3)\n\n#### Features\n\n* [#1276](https://github.com/ruby-grape/grape/pull/1276): Replace rack-mount with new router - [@namusyaka](https://github.com/namusyaka).\n* [#1321](https://github.com/ruby-grape/grape/pull/1321): Serve files without using FileStreamer-like object - [@lfidnl](https://github.com/lfidnl).\n* [#1339](https://github.com/ruby-grape/grape/pull/1339): Implement Grape::API.recognize_path - [@namusyaka](https://github.com/namusyaka).\n\n#### Fixes\n\n* [#1325](https://github.com/ruby-grape/grape/pull/1325): Params: Fix coerce_with helper with Array types - [@ngonzalez](https://github.com/ngonzalez).\n* [#1326](https://github.com/ruby-grape/grape/pull/1326): Fix wrong behavior for OPTIONS and HEAD requests with catch-all - [@ekampp](https://github.com/ekampp), [@namusyaka](https://github.com/namusyaka).\n* [#1330](https://github.com/ruby-grape/grape/pull/1330): Add `register` keyword for adding customized parsers and formatters - [@namusyaka](https://github.com/namusyaka).\n* [#1336](https://github.com/ruby-grape/grape/pull/1336): Do not modify Hash argument to `error!` - [@tjwp](https://github.com/tjwp).\n\n### 0.15.0 (2016/3/8)\n\n#### Features\n\n* [#1227](https://github.com/ruby-grape/grape/pull/1227): Store `message_key` on `Grape::Exceptions::Validation` - [@stjhimy](https://github.com/sthimy).\n* [#1232](https://github.com/ruby-grape/grape/pull/1232): Helpers are now available inside `rescue_from` - [@namusyaka](https://github.com/namusyaka).\n* [#1237](https://github.com/ruby-grape/grape/pull/1237): Allow multiple parameters in `given`, which behaves as if the scopes were nested in the inputted order - [@ochagata](https://github.com/ochagata).\n* [#1238](https://github.com/ruby-grape/grape/pull/1238): Call `after` of middleware on error - [@namusyaka](https://github.com/namusyaka).\n* [#1243](https://github.com/ruby-grape/grape/pull/1243): Add `header` support for middleware - [@namusyaka](https://github.com/namusyaka).\n* [#1252](https://github.com/ruby-grape/grape/pull/1252): Allow default to be a subset or equal to allowed values without raising IncompatibleOptionValues - [@jeradphelps](https://github.com/jeradphelps).\n* [#1255](https://github.com/ruby-grape/grape/pull/1255): Allow param type definition in `route_param` - [@namusyaka](https://github.com/namusyaka).\n* [#1257](https://github.com/ruby-grape/grape/pull/1257): Allow Proc, Symbol or String in `rescue_from with: ...` - [@namusyaka](https://github.com/namusyaka).\n* [#1280](https://github.com/ruby-grape/grape/pull/1280): Support `Rack::Sendfile` middleware - [@lfidnl](https://github.com/lfidnl).\n* [#1285](https://github.com/ruby-grape/grape/pull/1285): Add a warning for errors appearing in `after` callbacks - [@gregormelhorn](https://github.com/gregormelhorn).\n* [#1295](https://github.com/ruby-grape/grape/pull/1295): Add custom validation messages for parameter exceptions - [@railsmith](https://github.com/railsmith).\n\n#### Fixes\n\n* [#1216](https://github.com/ruby-grape/grape/pull/1142): Fix JSON error response when calling `error!` with non-Strings - [@jrforrest](https://github.com/jrforrest).\n* [#1225](https://github.com/ruby-grape/grape/pull/1225): Fix `given` with nested params not returning correct declared params - [@JanStevens](https://github.com/JanStevens).\n* [#1249](https://github.com/ruby-grape/grape/pull/1249): Don't fail even if invalid type value is passed to default validator - [@namusyaka](https://github.com/namusyaka).\n* [#1266](https://github.com/ruby-grape/grape/pull/1266): Fix `Allow` header including `OPTIONS` when `do_not_route_options!` is active - [@arempe93](https://github.com/arempe93).\n* [#1270](https://github.com/ruby-grape/grape/pull/1270): Fix `param` versioning with a custom parameter - [@wshatch](https://github.com/wshatch).\n* [#1282](https://github.com/ruby-grape/grape/pull/1282): Fix specs circular dependency - [@304](https://github.com/304).\n* [#1283](https://github.com/ruby-grape/grape/pull/1283): Fix 500 error for xml format when method is not allowed - [@304](https://github.com/304).\n* [#1197](https://github.com/ruby-grape/grape/pull/1290): Fix using JSON and Array[JSON] as groups when parameter is optional - [@lukeivers](https://github.com/lukeivers).\n\n### 0.14.0 (2015/12/07)\n\n#### Features\n\n* [#1218](https://github.com/ruby-grape/grape/pull/1218): Provide array index context in errors - [@towanda](https://github.com/towanda).\n* [#1196](https://github.com/ruby-grape/grape/pull/1196): Allow multiple `before_each` blocks - [@huynhquancam](https://github.com/huynhquancam).\n* [#1190](https://github.com/ruby-grape/grape/pull/1190): Bypass formatting for statuses with no entity-body - [@tylerdooling](https://github.com/tylerdooling).\n* [#1188](https://github.com/ruby-grape/grape/pull/1188): Allow parameters with more than one type - [@dslh](https://github.com/dslh).\n* [#1179](https://github.com/ruby-grape/grape/pull/1179): Allow all RFC6838 valid characters in header vendor - [@suan](https://github.com/suan).\n* [#1170](https://github.com/ruby-grape/grape/pull/1170): Allow dashes and periods in header vendor - [@suan](https://github.com/suan).\n* [#1167](https://github.com/ruby-grape/grape/pull/1167): Convenience wrapper `type: File` for validating multipart file parameters - [@dslh](https://github.com/dslh).\n* [#1167](https://github.com/ruby-grape/grape/pull/1167): Refactor and extend coercion and type validation system - [@dslh](https://github.com/dslh).\n* [#1163](https://github.com/ruby-grape/grape/pull/1163): First-class `JSON` parameter type - [@dslh](https://github.com/dslh).\n* [#1161](https://github.com/ruby-grape/grape/pull/1161): Custom parameter coercion using `coerce_with` - [@dslh](https://github.com/dslh).\n\n#### Fixes\n\n* [#1194](https://github.com/ruby-grape/grape/pull/1194): Redirect as plain text with message - [@tylerdooling](https://github.com/tylerdooling).\n* [#1185](https://github.com/ruby-grape/grape/pull/1185): Use formatters for custom vendored content types - [@tylerdooling](https://github.com/tylerdooling).\n* [#1156](https://github.com/ruby-grape/grape/pull/1156): Fixed `no implicit conversion of Symbol into Integer` with nested `values` validation - [@quickpay](https://github.com/quickpay).\n* [#1153](https://github.com/ruby-grape/grape/pull/1153): Fixes boolean declaration in an external file - [@towanda](https://github.com/towanda).\n* [#1142](https://github.com/ruby-grape/grape/pull/1142): Makes #declared unavailable to before filters - [@jrforrest](https://github.com/jrforrest).\n* [#1114](https://github.com/ruby-grape/grape/pull/1114): Fix regression which broke identical endpoints with different versions - [@suan](https://github.com/suan).\n* [#1109](https://github.com/ruby-grape/grape/pull/1109): Memoize Virtus attribute and fix memory leak - [@marshall-lee](https://github.com/marshall-lee).\n* [#1101](https://github.com/ruby-grape/grape/pull/1101): Fix: Incorrect media-type `Accept` header now correctly returns 406 with `strict: true` - [@elliotlarson](https://github.com/elliotlarson).\n* [#1108](https://github.com/ruby-grape/grape/pull/1039): Raise a warning when `desc` is called with options hash and block - [@rngtng](https://github.com/rngtng).\n\n### 0.13.0 (2015/8/10)\n\n#### Features\n\n* [#1039](https://github.com/ruby-grape/grape/pull/1039): Added support for custom parameter types - [@rnubel](https://github.com/rnubel).\n* [#1047](https://github.com/ruby-grape/grape/pull/1047): Adds `given` to DSL::Parameters, allowing for dependent params - [@rnubel](https://github.com/rnubel).\n* [#1064](https://github.com/ruby-grape/grape/pull/1064): Add public `Grape::Exception::ValidationErrors#full_messages` - [@romanlehnert](https://github.com/romanlehnert).\n* [#1079](https://github.com/ruby-grape/grape/pull/1079): Added `stream` method to take advantage of `Rack::Chunked` - [@zbelzer](https://github.com/zbelzer).\n* [#1086](https://github.com/ruby-grape/grape/pull/1086): Added `ActiveSupport::Notifications` instrumentation - [@wagenet](https://github.com/wagenet).\n\n#### Fixes\n\n* [#1062](https://github.com/ruby-grape/grape/issues/1062): Fix: `Grape::Exceptions::ValidationErrors` will include headers set by `header` - [@yairgo](https://github.com/yairgo).\n* [#1038](https://github.com/ruby-grape/grape/pull/1038): Avoid dup-ing the `String` class when used in inherited params - [@rnubel](https://github.com/rnubel).\n* [#1042](https://github.com/ruby-grape/grape/issues/1042): Fix coercion of complex arrays - [@dim](https://github.com/dim).\n* [#1045](https://github.com/ruby-grape/grape/pull/1045): Do not convert `Rack::Response` to `Rack::Response` in middleware - [@dmitry](https://github.com/dmitry).\n* [#1048](https://github.com/ruby-grape/grape/pull/1048): Only dup `InheritableValues`, remove support for `deep_dup` - [@toddmazierski](https://github.com/toddmazierski).\n* [#1052](https://github.com/ruby-grape/grape/pull/1052): Reset `description[:params]` when resetting validations - [@marshall-lee](https://github.com/marshall-lee).\n* [#1088](https://github.com/ruby-grape/grape/pull/1088): Support ActiveSupport 3.x by explicitly requiring `Hash#except` - [@wagenet](https://github.com/wagenet).\n* [#1096](https://github.com/ruby-grape/grape/pull/1096): Fix coercion on booleans - [@towanda](https://github.com/towanda).\n\n### 0.12.0 (2015/6/18)\n\n#### Features\n\n* [#995](https://github.com/ruby-grape/grape/issues/995): Added support for coercion to Set or Set[Other] - [@jordansexton](https://github.com/jordansexton) [@u2](https://github.com/u2).\n* [#980](https://github.com/ruby-grape/grape/issues/980): Grape is now eager-loaded - [@u2](https://github.com/u2).\n* [#956](https://github.com/ruby-grape/grape/issues/956): Support `present` with `Grape::Presenters::Presenter`  - [@u2](https://github.com/u2).\n* [#974](https://github.com/ruby-grape/grape/pull/974): Added `error!` to `rescue_from` blocks - [@whatasunnyday](https://github.com/whatasunnyday).\n* [#950](https://github.com/ruby-grape/grape/pull/950): Status method can now accept one of Rack::Utils status code symbols (:ok, :found, :bad_request, etc.) - [@dabrorius](https://github.com/dabrorius).\n* [#952](https://github.com/ruby-grape/grape/pull/952): Status method now raises error when called with invalid status code - [@dabrorius](https://github.com/dabrorius).\n* [#957](https://github.com/ruby-grape/grape/pull/957): Regexp validator now supports `allow_blank`, `nil` value behavior changed - [@calfzhou](https://github.com/calfzhou).\n* [#962](https://github.com/ruby-grape/grape/pull/962): The `default` attribute with `false` value is documented now - [@ajvondrak](https://github.com/ajvondrak).\n* [#1026](https://github.com/ruby-grape/grape/pull/1026): Added `file` method, explicitly setting a file-like response object - [@dblock](https://github.com/dblock).\n\n#### Fixes\n\n* [#994](https://github.com/ruby-grape/grape/pull/994): Fixed optional Array params default to Hash - [@u2](https://github.com/u2).\n* [#988](https://github.com/ruby-grape/grape/pull/988): Fixed duplicate identical endpoints - [@u2](https://github.com/u2).\n* [#936](https://github.com/ruby-grape/grape/pull/936): Fixed default params processing for optional groups - [@dm1try](https://github.com/dm1try).\n* [#942](https://github.com/ruby-grape/grape/pull/942): Fixed forced presence for optional params when based on a reused entity that was also required in another context - [@croeck](https://github.com/croeck).\n* [#1001](https://github.com/ruby-grape/grape/pull/1001): Fixed calling endpoint with specified format with format in its path - [@hodak](https://github.com/hodak).\n* [#1005](https://github.com/ruby-grape/grape/pull/1005): Fixed the Grape::Middleware::Globals - [@urkle](https://github.com/urkle).\n* [#1012](https://github.com/ruby-grape/grape/pull/1012): Fixed `allow_blank: false` with a Boolean value of `false` - [@mfunaro](https://github.com/mfunaro).\n* [#1023](https://github.com/ruby-grape/grape/issues/1023): Fixes unexpected behavior with `present` and an object that responds to `merge` but isn't a Hash - [@dblock](https://github.com/dblock).\n* [#1017](https://github.com/ruby-grape/grape/pull/1017): Fixed `undefined method stringify_keys` with nested mutual exclusive params - [@quickpay](https://github.com/quickpay).\n\n### 0.11.0 (2015/2/23)\n\n* [#925](https://github.com/ruby-grape/grape/pull/925): Fixed `toplevel constant DateTime referenced by Virtus::Attribute::DateTime` - [@u2](https://github.com/u2).\n* [#916](https://github.com/ruby-grape/grape/pull/916): Added `DateTime/Date/Numeric/Boolean` type support `allow_blank` - [@u2](https://github.com/u2).\n* [#871](https://github.com/ruby-grape/grape/pull/871): Fixed `Grape::Middleware::Base#response` - [@galathius](https://github.com/galathius).\n* [#559](https://github.com/ruby-grape/grape/issues/559): Added support for Rack 1.6.0, which parses requests larger than 128KB - [@myitcv](https://github.com/myitcv).\n* [#876](https://github.com/ruby-grape/grape/pull/876): Call to `declared(params)` now returns a `Hashie::Mash` - [@rodzyn](https://github.com/rodzyn).\n* [#879](https://github.com/ruby-grape/grape/pull/879): The `route_info` value is no longer included in `params` Hash - [@rodzyn](https://github.com/rodzyn).\n* [#881](https://github.com/ruby-grape/grape/issues/881): Fixed `Grape::Validations::ValuesValidator` support for `Range` type - [@ajvondrak](https://github.com/ajvondrak).\n* [#901](https://github.com/ruby-grape/grape/pull/901): Fix: callbacks defined in a version block are only called for the routes defined in that block - [@kushkella](https://github.com/kushkella).\n* [#886](https://github.com/ruby-grape/grape/pull/886): Group of parameters made to require an explicit type of Hash or Array - [@jrichter1](https://github.com/jrichter1).\n* [#912](https://github.com/ruby-grape/grape/pull/912): Extended the `:using` feature for param documentation to `optional` fields - [@croeck](https://github.com/croeck).\n* [#906](https://github.com/ruby-grape/grape/pull/906): Fix: invalid body parse errors are not rescued by handlers - [@croeck](https://github.com/croeck).\n* [#913](https://github.com/ruby-grape/grape/pull/913): Fix: Invalid accept headers are not processed by rescue handlers - [@croeck](https://github.com/croeck).\n* [#913](https://github.com/ruby-grape/grape/pull/913): Fix: Invalid accept headers cause internal processing errors (500) when http_codes are defined - [@croeck](https://github.com/croeck).\n* [#917](https://github.com/ruby-grape/grape/pull/917): Use HTTPS for rubygems.org - [@O-I](https://github.com/O-I).\n\n### 0.10.1 (2014/12/28)\n\n* [#868](https://github.com/ruby-grape/grape/pull/868), [#862](https://github.com/ruby-grape/grape/pull/862), [#861](https://github.com/ruby-grape/grape/pull/861): Fixed `version`, `prefix`, and other settings being overridden or changing scope when mounting API - [@yesmeck](https://github.com/yesmeck).\n* [#864](https://github.com/ruby-grape/grape/pull/864): Fixed `declared(params, include_missing: false)` now returning attributes with `nil` and `false` values - [@ppadron](https://github.com/ppadron).\n\n### 0.10.0 (2014/12/19)\n\n* [#803](https://github.com/ruby-grape/grape/pull/803), [#820](https://github.com/ruby-grape/grape/pull/820): Added `all_or_none_of` parameter validator - [@loveltyoic](https://github.com/loveltyoic), [@natecj](https://github.com/natecj).\n* [#774](https://github.com/ruby-grape/grape/pull/774): Extended `mutually_exclusive`, `exactly_one_of`, `at_least_one_of` to work inside any kind of group: `requires` or `optional`, `Hash` or `Array` - [@ShPakvel](https://github.com/ShPakvel).\n* [#743](https://github.com/ruby-grape/grape/pull/743): Added `allow_blank` parameter validator to validate non-empty strings - [@elado](https://github.com/elado).\n* [#745](https://github.com/ruby-grape/grape/pull/745): Removed `atom+xml`, `rss+xml`, and `jsonapi` content-types - [@akabraham](https://github.com/akabraham).\n* [#745](https://github.com/ruby-grape/grape/pull/745): Added `:binary, application/octet-stream` content-type - [@akabraham](https://github.com/akabraham).\n* [#757](https://github.com/ruby-grape/grape/pull/757): Changed `desc` can now be used with a block syntax - [@dspaeth-faber](https://github.com/dspaeth-faber).\n* [#779](https://github.com/ruby-grape/grape/pull/779): Fixed using `values` with a `default` proc - [@ShPakvel](https://github.com/ShPakvel).\n* [#799](https://github.com/ruby-grape/grape/pull/799): Fixed custom validators with required `Hash`, `Array` types - [@bwalex](https://github.com/bwalex).\n* [#784](https://github.com/ruby-grape/grape/pull/784): Fixed `present` to not overwrite the previously added contents of the response body whebn called more than once - [@mfunaro](https://github.com/mfunaro).\n* [#809](https://github.com/ruby-grape/grape/pull/809): Removed automatic `(.:format)` suffix on paths if you're using only one format (e.g., with `format :json`, `/path` will respond with JSON but `/path.xml` will be a 404) - [@ajvondrak](https://github.com/ajvondrak).\n* [#816](https://github.com/ruby-grape/grape/pull/816): Added ability to filter out missing params if params is a nested hash with `declared(params, include_missing: false)` - [@georgimitev](https://github.com/georgimitev).\n* [#819](https://github.com/ruby-grape/grape/pull/819): Allowed both `desc` and `description` in the params DSL - [@mzikherman](https://github.com/mzikherman).\n* [#821](https://github.com/ruby-grape/grape/pull/821): Fixed passing string value when hash is expected in params - [@rebelact](https://github.com/rebelact).\n* [#824](https://github.com/ruby-grape/grape/pull/824): Validate array params against list of acceptable values - [@dnd](https://github.com/dnd).\n* [#813](https://github.com/ruby-grape/grape/pull/813): Routing methods dsl refactored to get rid of explicit `paths` parameter - [@AlexYankee](https://github.com/AlexYankee).\n* [#826](https://github.com/ruby-grape/grape/pull/826): Find `coerce_type` for `Array` when not specified - [@manovotn](https://github.com/manovotn).\n* [#645](https://github.com/ruby-grape/grape/issues/645): Invoking `body false` will return `204 No Content` - [@dblock](https://github.com/dblock).\n* [#801](https://github.com/ruby-grape/grape/issues/801): Only evaluate permitted parameter `values` and `default` lazily on each request when declared as a proc - [@dblock](https://github.com/dblock).\n* [#679](https://github.com/ruby-grape/grape/issues/679): Fixed `OPTIONS` method returning 404 when combined with `prefix` - [@dblock](https://github.com/dblock).\n* [#679](https://github.com/ruby-grape/grape/issues/679): Fixed unsupported methods returning 404 instead of 405 when combined with `prefix` - [@dblock](https://github.com/dblock).\n\n### 0.9.0 (2014/8/27)\n\n#### Features\n\n* [#691](https://github.com/ruby-grape/grape/issues/691): Added `at_least_one_of` parameter validator - [@dblock](https://github.com/dblock).\n* [#698](https://github.com/ruby-grape/grape/pull/698): `error!` sets `status` for `Endpoint` too - [@dspaeth-faber](https://github.com/dspaeth-faber).\n* [#703](https://github.com/ruby-grape/grape/pull/703): Added support for Auth-Middleware extension - [@dspaeth-faber](https://github.com/dspaeth-faber).\n* [#703](https://github.com/ruby-grape/grape/pull/703): Removed `Grape::Middleware::Auth::Basic` - [@dspaeth-faber](https://github.com/dspaeth-faber).\n* [#703](https://github.com/ruby-grape/grape/pull/703): Removed `Grape::Middleware::Auth::Digest` - [@dspaeth-faber](https://github.com/dspaeth-faber).\n* [#703](https://github.com/ruby-grape/grape/pull/703): Removed `Grape::Middleware::Auth::OAuth2` - [@dspaeth-faber](https://github.com/dspaeth-faber).\n* [#719](https://github.com/ruby-grape/grape/pull/719): Allow passing options hash to a custom validator - [@elado](https://github.com/elado).\n* [#716](https://github.com/ruby-grape/grape/pull/716): Calling `content-type` will now return the current content-type - [@dblock](https://github.com/dblock).\n* [#705](https://github.com/ruby-grape/grape/pull/705): Errors can now be presented with a `Grape::Entity` class - [@dspaeth-faber](https://github.com/dspaeth-faber).\n\n#### Fixes\n\n* [#687](https://github.com/ruby-grape/grape/pull/687): Fix: `mutually_exclusive` and `exactly_one_of` validation error messages now label parameters as strings, consistently with `requires` and `optional` - [@dblock](https://github.com/dblock).\n\n### 0.8.0 (2014/7/10)\n\n#### Features\n\n* [#639](https://github.com/ruby-grape/grape/pull/639): Added support for blocks with reusable params - [@mibon](https://github.com/mibon).\n* [#637](https://github.com/ruby-grape/grape/pull/637): Added support for `exactly_one_of` parameter validation - [@Morred](https://github.com/Morred).\n* [#626](https://github.com/ruby-grape/grape/pull/626): Added support for `mutually_exclusive` parameters - [@oliverbarnes](https://github.com/oliverbarnes).\n* [#617](https://github.com/ruby-grape/grape/pull/617): Running tests on Ruby 2.1.1, Rubinius 2.1 and 2.2, Ruby and JRuby HEAD - [@dblock](https://github.com/dblock).\n* [#397](https://github.com/ruby-grape/grape/pull/397): Adds `Grape::Endpoint.before_each` to allow easy helper stubbing - [@mbleigh](https://github.com/mbleigh).\n* [#673](https://github.com/ruby-grape/grape/pull/673): Avoid requiring non-existent fields when using Grape::Entity documentation - [@qqshfox](https://github.com/qqshfox).\n\n#### Fixes\n\n* [#671](https://github.com/ruby-grape/grape/pull/671): Allow required param with predefined set of values to be nil inside optional group - [@dm1try](https://github.com/dm1try).\n* [#651](https://github.com/ruby-grape/grape/pull/651): The `rescue_from` keyword now properly defaults to rescuing subclasses of exceptions - [@xevix](https://github.com/xevix).\n* [#614](https://github.com/ruby-grape/grape/pull/614): Params with `nil` value are now refused by `RegexpValidator` - [@dm1try](https://github.com/dm1try).\n* [#494](https://github.com/ruby-grape/grape/issues/494): Fixed performance issue with requests carrying a large payload - [@dblock](https://github.com/dblock).\n* [#619](https://github.com/ruby-grape/grape/pull/619): Convert specs to RSpec 3 syntax with Transpec - [@danielspector](https://github.com/danielspector).\n* [#632](https://github.com/ruby-grape/grape/pull/632): `Grape::Endpoint#present` causes ActiveRecord to make an extra query during entity's detection - [@fixme](https://github.com/fixme).\n\n### 0.7.0 (2014/4/2)\n\n#### Features\n\n* [#558](https://github.com/ruby-grape/grape/pull/558): Support lambda-based values for params - [@wpschallenger](https://github.com/wpschallenger).\n* [#510](https://github.com/ruby-grape/grape/pull/510): Support lambda-based default values for params - [@myitcv](https://github.com/myitcv).\n* [#511](https://github.com/ruby-grape/grape/pull/511): Added `required` option for OAuth2 middleware - [@bcm](https://github.com/bcm).\n* [#520](https://github.com/ruby-grape/grape/pull/520): Use `default_error_status` to specify the default status code returned from `error!` - [@salimane](https://github.com/salimane).\n* [#525](https://github.com/ruby-grape/grape/pull/525): The default status code returned from `error!` has been changed from 403 to 500 - [@dblock](https://github.com/dblock).\n* [#526](https://github.com/ruby-grape/grape/pull/526): Allowed specifying headers in `error!` - [@dblock](https://github.com/dblock).\n* [#527](https://github.com/ruby-grape/grape/pull/527): The `before_validation` callback is now a distinct one - [@myitcv](https://github.com/myitcv).\n* [#530](https://github.com/ruby-grape/grape/pull/530): Added ability to restrict `declared(params)` to the local endpoint with `include_parent_namespaces: false` - [@myitcv](https://github.com/myitcv).\n* [#531](https://github.com/ruby-grape/grape/pull/531): Helpers are now available to auth middleware, executing in the context of the endpoint - [@joelvh](https://github.com/joelvh).\n* [#540](https://github.com/ruby-grape/grape/pull/540): Ruby 2.1.0 is now supported - [@salimane](https://github.com/salimane).\n* [#544](https://github.com/ruby-grape/grape/pull/544): The `rescue_from` keyword now handles subclasses of exceptions by default - [@xevix](https://github.com/xevix).\n* [#545](https://github.com/ruby-grape/grape/pull/545): Added `type` (`Array` or `Hash`) support to `requires`, `optional` and `group` - [@bwalex](https://github.com/bwalex).\n* [#550](https://github.com/ruby-grape/grape/pull/550): Added possibility to define reusable params - [@dm1try](https://github.com/dm1try).\n* [#560](https://github.com/ruby-grape/grape/pull/560): Use `Grape::Entity` documentation to define required and optional parameters with `requires using:` - [@reynardmh](https://github.com/reynardmh).\n* [#572](https://github.com/ruby-grape/grape/pull/572): Added `documentation` support to `requires`, `optional` and `group` parameters - [@johnallen3d](https://github.com/johnallen3d).\n\n#### Fixes\n\n* [#600](https://github.com/ruby-grape/grape/pull/600): Don't use an `Entity` constant that is available in the namespace as presenter - [@fuksito](https://github.com/fuksito).\n* [#590](https://github.com/ruby-grape/grape/pull/590): Fix issue where endpoint param of type `Integer` cannot set values array - [@xevix](https://github.com/xevix).\n* [#586](https://github.com/ruby-grape/grape/pull/586): Do not repeat the same validation error messages - [@kiela](https://github.com/kiela).\n* [#508](https://github.com/ruby-grape/grape/pull/508): Allow parameters, such as content encoding, in `content_type` - [@dm1try](https://github.com/dm1try).\n* [#492](https://github.com/ruby-grape/grape/pull/492): Don't allow to have nil value when a param is required and has a list of allowed values - [@Antti](https://github.com/Antti).\n* [#495](https://github.com/ruby-grape/grape/pull/495): Fixed `ParamsScope#params` for parameters nested inside arrays - [@asross](https://github.com/asross).\n* [#498](https://github.com/ruby-grape/grape/pull/498): Dry'ed up options and headers logic, allow headers to be passed to OPTIONS requests - [@karlfreeman](https://github.com/karlfreeman).\n* [#500](https://github.com/ruby-grape/grape/pull/500): Skip entity auto-detection when explicitly passed - [@yaneq](https://github.com/yaneq).\n* [#503](https://github.com/ruby-grape/grape/pull/503): Calling declared(params) from child namespace fails to include parent namespace defined params - [@myitcv](https://github.com/myitcv).\n* [#512](https://github.com/ruby-grape/grape/pull/512): Don't create `Grape::Request` multiple times - [@dblock](https://github.com/dblock).\n* [#538](https://github.com/ruby-grape/grape/pull/538): Fixed default values for grouped params - [@dm1try](https://github.com/dm1try).\n* [#549](https://github.com/ruby-grape/grape/pull/549): Fixed handling of invalid version headers to return 406 if a header cannot be parsed - [@bwalex](https://github.com/bwalex).\n* [#557](https://github.com/ruby-grape/grape/pull/557): Pass `content_types` option to `Grape::Middleware::Error` to fix the content-type header for custom formats - [@bernd](https://github.com/bernd).\n* [#585](https://github.com/ruby-grape/grape/pull/585): Fix after boot thread-safety issue - [@etehtsea](https://github.com/etehtsea).\n* [#587](https://github.com/ruby-grape/grape/pull/587): Fix oauth2 middleware compatibility with [draft-ietf-oauth-v2-31](http://tools.ietf.org/html/draft-ietf-oauth-v2-31) spec - [@etehtsea](https://github.com/etehtsea).\n* [#610](https://github.com/ruby-grape/grape/pull/610): Fixed group keyword was not working with type parameter - [@klausmeyer](https://github.com/klausmeyer).\n\n### 0.6.1 (2013/10/19)\n\n#### Features\n\n* [#475](https://github.com/ruby-grape/grape/pull/475): Added support for the `:jsonapi`, `application/vnd.api+json` media type registered at http://jsonapi.org - [@bcm](https://github.com/bcm).\n* [#471](https://github.com/ruby-grape/grape/issues/471): Added parameter validator for a list of allowed values - [@vickychijwani](https://github.com/vickychijwani).\n* [#488](https://github.com/ruby-grape/grape/issues/488): Upgraded to Virtus 1.0 - [@dblock](https://github.com/dblock).\n\n#### Fixes\n\n* [#477](https://github.com/ruby-grape/grape/pull/477): Fixed `default_error_formatter` which takes a format symbol - [@vad4msiu](https://github.com/vad4msiu).\n\n#### Development\n\n* Implemented Rubocop, a Ruby code static code analyzer - [@dblock](https://github.com/dblock).\n\n### 0.6.0 (2013/9/16)\n\n#### Features\n\n* Grape is no longer tested against Ruby 1.8.7 - [@dblock](https://github.com/dblock).\n* [#442](https://github.com/ruby-grape/grape/issues/442): Enable incrementally building on top of a previous API version - [@dblock](https://github.com/dblock).\n* [#442](https://github.com/ruby-grape/grape/issues/442): API `version` can now take an array of multiple versions - [@dblock](https://github.com/dblock).\n* [#444](https://github.com/ruby-grape/grape/issues/444): Added `:en` as fallback locale for I18n - [@aew](https://github.com/aew).\n* [#448](https://github.com/ruby-grape/grape/pull/448): Adding POST style parameters for DELETE requests - [@dquimper](https://github.com/dquimper).\n* [#450](https://github.com/ruby-grape/grape/pull/450): Added option to pass an exception handler lambda as an argument to `rescue_from` - [@robertopedroso](https://github.com/robertopedroso).\n* [#443](https://github.com/ruby-grape/grape/pull/443): Let `requires` and `optional` take blocks that initialize new scopes - [@asross](https://github.com/asross).\n* [#452](https://github.com/ruby-grape/grape/pull/452): Added `with` as a hash option to specify handlers for `rescue_from` and `error_formatter` - [@robertopedroso](https://github.com/robertopedroso).\n* [#433](https://github.com/ruby-grape/grape/issues/433), [#462](https://github.com/ruby-grape/grape/issues/462): Validation errors are now collected and `Grape::Exceptions::ValidationErrors` is raised - [@stevschmid](https://github.com/stevschmid).\n\n#### Fixes\n\n* [#428](https://github.com/ruby-grape/grape/issues/428): Removes memoization from `Grape::Request` params to prevent middleware from freezing parameter values before `Formatter` can get them - [@mbleigh](https://github.com/mbleigh).\n\n### 0.5.0 (2013/6/14)\n\n#### Features\n\n* [#344](https://github.com/ruby-grape/grape/pull/344): Added `parser :type, nil` which disables input parsing for a given content-type - [@dblock](https://github.com/dblock).\n* [#381](https://github.com/ruby-grape/grape/issues/381): Added `cascade false` option at API level to remove the `X-Cascade: true` header from the API response - [@dblock](https://github.com/dblock).\n* [#392](https://github.com/ruby-grape/grape/pull/392): Extracted headers and params from `Endpoint` to `Grape::Request` - [@niedhui](https://github.com/niedhui).\n* [#376](https://github.com/ruby-grape/grape/pull/376): Added `route_param`, syntax sugar for quick declaration of route parameters - [@mbleigh](https://github.com/mbleigh).\n* [#390](https://github.com/ruby-grape/grape/pull/390): Added default value for an `optional` parameter - [@oivoodoo](https://github.com/oivoodoo).\n* [#403](https://github.com/ruby-grape/grape/pull/403): Added support for versioning using the `Accept-Version` header - [@politician](https://github.com/politician).\n* [#407](https://github.com/ruby-grape/grape/issues/407): Specifying `default_format` will also set the default POST/PUT data parser to the given format - [@dblock](https://github.com/dblock).\n* [#241](https://github.com/ruby-grape/grape/issues/241): Present with multiple entities using an optional Symbol - [@niedhui](https://github.com/niedhui).\n\n#### Fixes\n\n* [#378](https://github.com/ruby-grape/grape/pull/378): Fix: stop rescuing all exceptions during formatting - [@kbarrette](https://github.com/kbarrette).\n* [#380](https://github.com/ruby-grape/grape/pull/380): Fix: `Formatter#read_body_input` when transfer encoding is chunked - [@paulnicholon](https://github.com/paulnicholson).\n* [#347](https://github.com/ruby-grape/grape/issues/347): Fix: handling non-hash body params - [@paulnicholon](https://github.com/paulnicholson).\n* [#394](https://github.com/ruby-grape/grape/pull/394): Fix: path version no longer overwrites a `version` parameter - [@tmornini](https://github.com/tmornini).\n* [#412](https://github.com/ruby-grape/grape/issues/412): Fix: specifying `content_type` will also override the selection of the data formatter - [@dblock](https://github.com/dblock).\n* [#383](https://github.com/ruby-grape/grape/issues/383): Fix: Mounted APIs aren't inheriting settings (including `before` and `after` filters) - [@seanmoon](https://github.com/seanmoon).\n* [#408](https://github.com/ruby-grape/grape/pull/408): Fix: Goliath passes request header keys as symbols not strings - [@bobek](https://github.com/bobek).\n* [#417](https://github.com/ruby-grape/grape/issues/417): Fix: Rails 4 does not rewind input, causes POSTed data to be empty - [@dblock](https://github.com/dblock).\n* [#423](https://github.com/ruby-grape/grape/pull/423): Fix: `Grape::Endpoint#declared` now correctly handles nested params (ie. declared with `group`) - [@jbarreneche](https://github.com/jbarreneche).\n* [#427](https://github.com/ruby-grape/grape/issues/427): Fix: `declared(params)` breaks when `params` contains array - [@timhabermaas](https://github.com/timhabermaas).\n\n### 0.4.1 (2013/4/1)\n\n* [#375](https://github.com/ruby-grape/grape/pull/375): Fix: throwing an `:error` inside a middleware doesn't respect the `format` settings - [@dblock](https://github.com/dblock).\n\n### 0.4.0 (2013/3/17)\n\n* [#356](https://github.com/ruby-grape/grape/pull/356): Fix: presenting collections other than `Array` (eg. `ActiveRecord::Relation`) - [@zimbatm](https://github.com/zimbatm).\n* [#352](https://github.com/ruby-grape/grape/pull/352): Fix: using `Rack::JSONP` with `Grape::Entity` responses - [@deckchair](https://github.com/deckchair).\n* [#347](https://github.com/ruby-grape/grape/issues/347): Grape will accept any valid JSON as PUT or POST, including strings, symbols and arrays - [@qqshfox](https://github.com/qqshfox), [@dblock](https://github.com/dblock).\n* [#347](https://github.com/ruby-grape/grape/issues/347): JSON format APIs always return valid JSON, eg. strings are now returned as `\"string\"` and no longer `string` - [@dblock](https://github.com/dblock).\n* Raw body input from POST and PUT requests (`env['rack.input'].read`) is now available in `api.request.input` - [@dblock](https://github.com/dblock).\n* Parsed body input from POST and PUT requests is now available in `api.request.body` - [@dblock](https://github.com/dblock).\n* [#343](https://github.com/ruby-grape/grape/pull/343): Fix: return `Content-Type: text/plain` with error 405 - [@gustavosaume](https://github.com/gustavosaume), [@wyattisimo](https://github.com/wyattisimo).\n* [#357](https://github.com/ruby-grape/grape/pull/357): Grape now requires Rack 1.3.0 or newer - [@jhecking](https://github.com/jhecking).\n* [#320](https://github.com/ruby-grape/grape/issues/320): API `namespace` now supports `requirements` - [@niedhui](https://github.com/niedhui).\n* [#353](https://github.com/ruby-grape/grape/issues/353): Revert to standard Ruby logger formatter, `require active_support/all` if you want old behavior - [@rhunter](https://github.com/rhunter), [@dblock](https://github.com/dblock).\n* Fix: `undefined method 'call' for nil:NilClass` for an API method implementation without a block, now returns an empty string - [@dblock](https://github.com/dblock).\n\n### 0.3.2 (2013/2/28)\n\n* [#355](https://github.com/ruby-grape/grape/issues/355): Relax dependency constraint on Hashie - [@reset](https://github.com/reset).\n\n### 0.3.1 (2013/2/25)\n\n* [#351](https://github.com/ruby-grape/grape/issues/351): Compatibility with Ruby 2.0 - [@mbleigh](https://github.com/mbleigh).\n\n### 0.3.0 (2013/02/21)\n\n* [#294](https://github.com/ruby-grape/grape/issues/294): Extracted `Grape::Entity` into a [grape-entity](https://github.com/agileanimal/grape-entity) gem - [@agileanimal](https://github.com/agileanimal).\n* [#340](https://github.com/ruby-grape/grape/pull/339), [#342](https://github.com/ruby-grape/grape/pull/342): Added `:cascade` option to `version` to allow disabling of rack/mount cascade behavior - [@dieb](https://github.com/dieb).\n* [#333](https://github.com/ruby-grape/grape/pull/333): Added support for validation of arrays in `params` - [@flyerhzm](https://github.com/flyerhzm).\n* [#306](https://github.com/ruby-grape/grape/issues/306): Added I18n support for all Grape exceptions - [@niedhui](https://github.com/niedhui).\n* [#309](https://github.com/ruby-grape/grape/pull/309): Added XML support to the entity presenter - [@johnnyiller](https://github.com/johnnyiller), [@dblock](https://github.com/dblock).\n* [#131](https://github.com/ruby-grape/grape/issues/131): Added instructions for Grape API reloading in Rails - [@jyn](https://github.com/jyn), [@dblock](https://github.com/dblock).\n* [#317](https://github.com/ruby-grape/grape/issues/317): Added `headers` that returns a hash of parsed HTTP request headers - [@dblock](https://github.com/dblock).\n* [#332](https://github.com/ruby-grape/grape/pull/332): `Grape::Exceptions::Validation` now contains full nested parameter names - [@alovak](https://github.com/alovak).\n* [#328](https://github.com/ruby-grape/grape/issues/328): API version can now be specified as both String and Symbol - [@dblock](https://github.com/dblock).\n* [#190](https://github.com/ruby-grape/grape/issues/190): When you add a `GET` route for a resource, a route for the `HEAD` method will also be added automatically. You can disable this behavior with `do_not_route_head!` - [@dblock](https://github.com/dblock).\n* Added `do_not_route_options!`, which disables the automatic creation of the `OPTIONS` route - [@dblock](https://github.com/dblock).\n* [#309](https://github.com/ruby-grape/grape/pull/309): An XML format API will return an error instead of returning a string representation of the response if the latter cannot be converted to XML - [@dblock](https://github.com/dblock).\n* A formatter that raises an exception will cause the API to return a 500 error - [@dblock](https://github.com/dblock).\n* [#322](https://github.com/ruby-grape/grape/issues/322): When returning a 406 status, Grape will include the requested format or content-type in the response body - [@dblock](https://github.com/dblock).\n* [#60](https://github.com/ruby-grape/grape/issues/60): Fix: mounting of a Grape API onto a path - [@dblock](https://github.com/dblock).\n* [#335](https://github.com/ruby-grape/grape/pull/335): Fix: request body parameters from a `PATCH` request not available in `params` - [@FreakenK](https://github.com/FreakenK).\n\n### 0.2.6 (2013/01/11)\n\n* Fix: support content-type with character set when parsing POST and PUT input - [@dblock](https://github.com/dblock).\n* Fix: CVE-2013-0175, multi_xml parse vulnerability, require multi_xml 0.5.2 - [@dblock](https://github.com/dblock).\n\n### 0.2.5 (2013/01/10)\n\n* Added support for custom parsers via `parser`, in addition to built-in multipart, JSON and XML parsers - [@dblock](https://github.com/dblock).\n* Removed `body_params`, data sent via a POST or PUT with a supported content-type is merged into `params` - [@dblock](https://github.com/dblock).\n* Setting `format` will automatically remove other content-types by calling `content_type` - [@dblock](https://github.com/dblock).\n* Setting `content_type` will prevent any input data other than the matching content-type or any Rack-supported form and parseable media types (`application/x-www-form-urlencoded`, `multipart/form-data`, `multipart/related` and `multipart/mixed`) from being parsed - [@dblock](https://github.com/dblock).\n* [#305](https://github.com/ruby-grape/grape/issues/305): Fix: presenting arrays of objects via `represent` or when auto-detecting an `Entity` constant in the objects being presented - [@brandonweiss](https://github.com/brandonweiss).\n* [#306](https://github.com/ruby-grape/grape/issues/306): Added i18n support for validation error messages - [@niedhui](https://github.com/niedhui).\n\n### 0.2.4 (2013/01/06)\n\n* [#297](https://github.com/ruby-grape/grape/issues/297): Added `default_error_formatter` - [@dblock](https://github.com/dblock).\n* [#297](https://github.com/ruby-grape/grape/issues/297): Setting `format` will automatically set `default_error_formatter` - [@dblock](https://github.com/dblock).\n* [#295](https://github.com/ruby-grape/grape/issues/295): Storing original API source block in endpoint's `source` attribute - [@dblock](https://github.com/dblock).\n* [#293](https://github.com/ruby-grape/grape/pull/293): Added options to `cookies.delete`, enables passing a path - [@inst](https://github.com/inst).\n* [#174](https://github.com/ruby-grape/grape/issues/174): The value of `env['PATH_INFO']` is no longer altered with `path` versioning - [@dblock](https://github.com/dblock).\n* [#296](https://github.com/ruby-grape/grape/issues/296): Fix: ArgumentError with default error formatter - [@dblock](https://github.com/dblock).\n* [#298](https://github.com/ruby-grape/grape/pull/298): Fix: subsequent calls to `body_params` would fail due to IO read - [@justinmcp](https://github.com/justinmcp).\n* [#301](https://github.com/ruby-grape/grape/issues/301): Fix: symbol memory leak in cookie and formatter middleware - [@dblock](https://github.com/dblock).\n* [#300](https://github.com/ruby-grape/grape/issues/300): Fix `Grape::API.routes` to include mounted api routes - [@aiwilliams](https://github.com/aiwilliams).\n* [#302](https://github.com/ruby-grape/grape/pull/302): Fix: removed redundant `autoload` entries - [@ugisozols](https://github.com/ugisozols).\n* [#172](https://github.com/ruby-grape/grape/issues/172): Fix: MultiJson deprecated methods warnings - [@dblock](https://github.com/dblock).\n* [#133](https://github.com/ruby-grape/grape/issues/133): Fix: header-based versioning with use of `prefix` - [@seanmoon](https://github.com/seanmoon), [@dblock](https://github.com/dblock).\n* [#280](https://github.com/ruby-grape/grape/issues/280): Fix: grouped parameters mangled in `route_params` hash - [@marcusg](https://github.com/marcusg), [@dblock](https://github.com/dblock).\n* [#304](https://github.com/ruby-grape/grape/issues/304): Fix: `present x, :with => Entity` returns class references with `format :json` - [@dblock](https://github.com/dblock).\n* [#196](https://github.com/ruby-grape/grape/issues/196): Fix: root requests don't work with `prefix` - [@dblock](https://github.com/dblock).\n\n### 0.2.3 (2012/12/24)\n\n* [#179](https://github.com/ruby-grape/grape/issues/178): Using `content_type` will remove all default content-types - [@dblock](https://github.com/dblock).\n* [#265](https://github.com/ruby-grape/grape/issues/264): Fix: Moved `ValidationError` into `Grape::Exceptions` - [@thepumpkin1979](https://github.com/thepumpkin1979).\n* [#269](https://github.com/ruby-grape/grape/pull/269): Fix: `LocalJumpError` will not be raised when using explict return in API methods - [@simulacre](https://github.com/simulacre).\n* [#86](https://github.com/ruby-grape/grape/issues/275): Fix Path-based versioning not recognizing `/` route - [@walski](https://github.com/walski).\n* [#273](https://github.com/ruby-grape/grape/pull/273): Disabled formatting via `serializable_hash` and added support for `format :serializable_hash` - [@dblock](https://github.com/dblock).\n* [#277](https://github.com/ruby-grape/grape/pull/277): Added a DSL to declare `formatter` in API settings - [@tim-vandecasteele](https://github.com/tim-vandecasteele).\n* [#284](https://github.com/ruby-grape/grape/pull/284): Added a DSL to declare `error_formatter` in API settings - [@dblock](https://github.com/dblock).\n* [#285](https://github.com/ruby-grape/grape/pull/285): Removed `error_format` from API settings, now matches request format - [@dblock](https://github.com/dblock).\n* [#290](https://github.com/ruby-grape/grape/pull/290): The default error format for XML is now `error/message` instead of `hash/error` - [@dpsk](https://github.com/dpsk).\n* [#44](https://github.com/ruby-grape/grape/issues/44): Pass `env` into formatters to enable templating - [@dblock](https://github.com/dblock).\n\n### 0.2.2 (2012/12/10)\n\n#### Features\n\n* [#201](https://github.com/ruby-grape/grape/pull/201), [#236](https://github.com/ruby-grape/grape/pull/236), [#221](https://github.com/ruby-grape/grape/pull/221): Added coercion and validations support to `params` DSL - [@schmurfy](https://github.com/schmurfy), [@tim-vandecasteele](https://github.com/tim-vandecasteele), [@adamgotterer](https://github.com/adamgotterer).\n* [#204](https://github.com/ruby-grape/grape/pull/204): Added ability to declare shared `params` at `namespace` level - [@tim-vandecasteele](https://github.com/tim-vandecasteele).\n* [#234](https://github.com/ruby-grape/grape/pull/234): Added a DSL for creating entities via mixin - [@mbleigh](https://github.com/mbleigh).\n* [#240](https://github.com/ruby-grape/grape/pull/240): Define API response format from a query string `format` parameter, if specified - [@neetiraj](https://github.com/neetiraj).\n* Adds Endpoint#declared to easily filter out unexpected params - [@mbleigh](https://github.com/mbleigh).\n\n#### Fixes\n\n* [#248](https://github.com/ruby-grape/grape/pull/248): Fix: API `version` returns last version set - [@narkoz](https://github.com/narkoz).\n* [#242](https://github.com/ruby-grape/grape/issues/242): Fix: permanent redirect status should be `301`, was `304` - [@adamgotterer](https://github.com/adamgotterer).\n* [#211](https://github.com/ruby-grape/grape/pull/211): Fix: custom validations are no longer triggered when optional and parameter is not present - [@adamgotterer](https://github.com/adamgotterer).\n* [#210](https://github.com/ruby-grape/grape/pull/210): Fix: `Endpoint#body_params` causing undefined method 'size' - [@adamgotterer](https://github.com/adamgotterer).\n* [#205](https://github.com/ruby-grape/grape/pull/205): Fix: Corrected parsing of empty JSON body on POST/PUT - [@tim-vandecasteele](https://github.com/tim-vandecasteele).\n* [#181](https://github.com/ruby-grape/grape/pull/181): Fix: Corrected JSON serialization of nested hashes containing `Grape::Entity` instances - [@benrosenblum](https://github.com/benrosenblum).\n* [#203](https://github.com/ruby-grape/grape/pull/203): Added a check to `Entity#serializable_hash` that verifies an entity exists on an object - [@adamgotterer](https://github.com/adamgotterer).\n* [#208](https://github.com/ruby-grape/grape/pull/208): `Entity#serializable_hash` must also check if attribute is generated by a user supplied block - [@ppadron](https://github.com/ppadron).\n* [#252](https://github.com/ruby-grape/grape/pull/252): Resources that don't respond to a requested HTTP method return 405 (Method Not Allowed) instead of 404 (Not Found) - [@simulacre](https://github.com/simulacre).\n\n### 0.2.1 (2012/7/11)\n\n* [#186](https://github.com/ruby-grape/grape/issues/186): Fix: helpers allow multiple calls with modules and blocks - [@ppadron](https://github.com/ppadron).\n* [#188](https://github.com/ruby-grape/grape/pull/188): Fix: multi-method routes append '(.:format)' only once - [@kainosnoema](https://github.com/kainosnoema).\n* [#64](https://github.com/ruby-grape/grape/issues/64), [#180](https://github.com/ruby-grape/grape/pull/180): Added support to `GET` request bodies as parameters - [@bobbytables](https://github.com/bobbytables).\n* [#175](https://github.com/ruby-grape/grape/pull/175): Added support for API versioning based on a request parameter - [@jackcasey](https://github.com/jackcasey).\n* [#168](https://github.com/ruby-grape/grape/pull/168): Fix: Formatter can parse symbol keys in the headers hash - [@netmask](https://github.com/netmask).\n* [#169](https://github.com/ruby-grape/grape/pull/169): Silence multi_json deprecation warnings - [@whiteley](https://github.com/whiteley).\n* [#166](https://github.com/ruby-grape/grape/pull/166): Added support for `redirect`, including permanent and temporary - [@allenwei](https://github.com/allenwei).\n* [#159](https://github.com/ruby-grape/grape/pull/159): Added `:requirements` to routes, allowing to use reserved characters in paths - [@gaiottino](https://github.com/gaiottino).\n* [#156](https://github.com/ruby-grape/grape/pull/156): Added support for adding formatters to entities - [@bobbytables](https://github.com/bobbytables).\n* [#183](https://github.com/ruby-grape/grape/pull/183): Added ability to include documentation in entities - [@flah00](https://github.com/flah00).\n* [#189](https://github.com/ruby-grape/grape/pull/189): `HEAD` requests no longer return a body - [@stephencelis](https://github.com/stephencelis).\n* [#97](https://github.com/ruby-grape/grape/issues/97): Allow overriding `Content-Type` - [@dblock](https://github.com/dblock).\n\n### 0.2.0 (2012/3/28)\n\n* Added support for inheriting exposures from entities - [@bobbytables](https://github.com/bobbytables).\n* Extended formatting with `default_format` - [@dblock](https://github.com/dblock).\n* Added support for cookies - [@lukaszsliwa](https://github.com/lukaszsliwa).\n* Added support for declaring additional content-types - [@joeyAghion](https://github.com/joeyAghion).\n* Added support for HTTP PATCH - [@LTe](https://github.com/LTe).\n* Added support for describing, documenting and reflecting APIs - [@dblock](https://github.com/dblock).\n* Added support for anchoring and vendoring - [@jwkoelewijn](https://github.com/jwkoelewijn).\n* Added support for HTTP OPTIONS - [@grimen](https://github.com/grimen).\n* Added support for silencing logger - [@evansj](https://github.com/evansj).\n* Added support for helper modules - [@freelancing-god](https://github.com/freelancing-god).\n* Added support for Accept header-based versioning - [@jch](https://github.com/jch), [@rodzyn](https://github.com/rodzyn).\n* Added support for mounting APIs and other Rack applications within APIs - [@mbleigh](https://github.com/mbleigh).\n* Added entities, multiple object representations - [@mbleigh](https://github.com/mbleigh).\n* Added ability to handle XML in the incoming request body - [@jwillis](https://github.com/jwillis).\n* Added support for a configurable logger - [@mbleigh](https://github.com/mbleigh).\n* Added support for before and after filters - [@mbleigh](https://github.com/mbleigh).\n* Extended `rescue_from`, which can now take a block - [@dblock](https://github.com/dblock).\n\n### 0.1.5 (2011/6/14)\n\n* Extended exception handling to all exceptions - [@dblock](https://github.com/dblock).\n* Added support for returning JSON objects from within error blocks - [@dblock](https://github.com/dblock).\n* Added support for handling incoming JSON in body - [@tedkulp](https://github.com/tedkulp).\n* Added support for HTTP digest authentication - [@daddz](https://github.com/daddz).\n\n### 0.1.4 (2011/4/8)\n\n* Allow multiple definitions of the same endpoint under multiple versions - [@chrisrhoden](https://github.com/chrisrhoden).\n* Added support for multipart URL parameters - [@mcastilho](https://github.com/mcastilho).\n* Added support for custom formatters - [@spraints](https://github.com/spraints).\n\n### 0.1.3 (2011/1/10)\n\n* Added support for JSON format in route matching - [@aiwilliams](https://github.com/aiwilliams).\n* Added suport for custom middleware - [@mbleigh](https://github.com/mbleigh).\n\n### 0.1.1 (2010/11/14)\n\n* Endpoints properly reset between each request - [@mbleigh](https://github.com/mbleigh).\n\n### 0.1.0 (2010/11/13)\n\n* Initial public release - [@mbleigh](https://github.com/mbleigh).\n\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Contributing to Grape\n=====================\n\nGrape is work of [hundreds of contributors](https://github.com/ruby-grape/grape/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/ruby-grape/grape/pulls), [propose features and discuss issues](https://github.com/ruby-grape/grape/issues).\n\n#### Fork the Project\n\nFork the [project on Github](https://github.com/ruby-grape/grape) and check out your copy.\n\n```\ngit clone https://github.com/contributor/grape.git\ncd grape\ngit remote add upstream https://github.com/ruby-grape/grape.git\n```\n\n#### Create a Topic Branch\n\nMake sure your fork is up-to-date and create a topic branch for your feature or bug fix.\n\n```\ngit checkout master\ngit pull upstream master\ngit checkout -b my-feature-branch\n```\n\n### Docker\n\nIf you're familiar with [Docker](https://www.docker.com/), you can run everything through the following command:\n\n```\ndocker-compose run --rm --build grape <command_and_parameters>\n```\n\nAbout the execution process:\n - displays Ruby, Rubygems, Bundle and Gemfile version when starting:\n    ```\n    ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-linux-musl]\n    rubygems 3.4.12\n    Bundler version 2.4.1 (2022-12-24 commit f3175f033c)\n    Running default Gemfile\n    ```\n - keeps the gems to the latest possible version\n - executes under `bundle exec`\n\nHere are some examples:\n\n- running all specs `docker-compose run --rm --build grape rspec`\n- running rspec on a specific file `docker-compose run --rm --build grape rspec spec/:file_path`\n- running task `docker-compose run --rm --build grape rake <task_name>`\n- running rubocop `docker-compose run --rm --build grape rubocop`\n- running all specs on a specific ruby version (e.g 3.4) `RUBY_VERSION=3.4 docker-compose run --rm --build grape rspec`\n- running specs on a specific gemfile (e.g rails_8_1.gemfile) `docker-compose run -e GEMFILE=rails_8_1 --rm --build grape rspec`\n\n#### Bundle Install and Test\n\nEnsure that you can build the project and run tests.\n\n```\nbundle install\nbundle exec rake\n```\n\n#### Write Tests\n\nTry to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. Add to [spec/grape](spec/grape).\n\nWe definitely appreciate pull requests that highlight or reproduce a problem, even without a fix.\n\n#### Write Code\n\nImplement your feature or bug fix.\n\nRuby style is enforced with [Rubocop](https://github.com/bbatsov/rubocop), run `bundle exec rubocop` and fix any style issues highlighted.\n\nMake sure that `bundle exec rake` completes without errors.\n\n#### Write Documentation\n\nDocument any external behavior in the [README](README.md).\n\nYou should also document code as necessary, using current code as examples. This project uses [YARD](https://yardoc.org/). You can run and preview the docs locally by [installing `yard`](https://yardoc.org/), running `yard server --reload` and view the docs at http://localhost:8808.\n\n#### Update Changelog\n\nAdd a line to [CHANGELOG](CHANGELOG.md) under *Next Release*. Make it look like every other line, including your name and link to your Github account.\n\n#### Commit Changes\n\nMake sure git knows your name and email address:\n\n```\ngit config --global user.name \"Your Name\"\ngit config --global user.email \"contributor@example.com\"\n```\n\nWriting good commit logs is important. A commit log should describe what changed and why.\n\n```\ngit add ...\ngit commit\n```\n\n#### Push\n\n```\ngit push origin my-feature-branch\n```\n\n#### Make a Pull Request\n\nGo to https://github.com/contributor/grape and select your feature branch. Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days.\n\n#### Rebase\n\nIf you've been working on a change for a while, rebase with upstream/master.\n\n```\ngit fetch upstream\ngit rebase upstream/master\ngit push origin my-feature-branch -f\n```\n\n#### Update CHANGELOG Again\n\nUpdate the [CHANGELOG](CHANGELOG.md) with the pull request number. A typical entry looks as follows.\n\n```\n* [#123](https://github.com/ruby-grape/grape/pull/123): Reticulated splines - [@contributor](https://github.com/contributor).\n```\n\nAmend your previous commit and force push the changes.\n\n```\ngit commit --amend\ngit push origin my-feature-branch -f\n```\n\n#### Check on Your Pull Request\n\nGo back to your pull request after a few minutes and see whether it passed muster with CI. Everything should look green, otherwise fix issues and amend your commit as described above.\n\n#### Be Patient\n\nIt's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang in there!\n\n#### Thank You\n\nPlease do know that we really appreciate and value your time and work. We love you, really.\n"
  },
  {
    "path": "Dangerfile",
    "content": "# frozen_string_literal: true\n\ndanger.import_dangerfile(gem: 'danger-pr-comment')\n\nchangelog.check!\n"
  },
  {
    "path": "Gemfile",
    "content": "# frozen_string_literal: true\n\nsource('https://rubygems.org')\n\ngemspec\n\ngroup :development, :test do\n  gem 'builder', require: false\n  gem 'bundler'\n  gem 'rake'\n  gem 'rubocop', '1.84.0', require: false\n  gem 'rubocop-performance', '1.26.1', require: false\n  gem 'rubocop-rspec', '3.9.0', require: false\nend\n\ngroup :development do\n  gem 'benchmark-ips'\n  gem 'benchmark-memory'\n  gem 'guard'\n  gem 'guard-rspec'\n  gem 'guard-rubocop'\n  gem 'irb'\nend\n\ngroup :test do\n  gem 'danger', require: false\n  gem 'danger-changelog', require: false\n  gem 'danger-pr-comment', require: false\n  gem 'rack-contrib', require: false\n  gem 'rack-test', '~> 2.1'\n  gem 'rspec', '~> 3.13'\n  gem 'simplecov', '~> 0.21', require: false\n  gem 'simplecov-lcov', '~> 0.8', require: false\nend\n\nplatforms :jruby do\n  gem 'racc'\nend\n"
  },
  {
    "path": "Guardfile",
    "content": "# frozen_string_literal: true\n\nguard :rspec, all_on_start: true, cmd: 'bundle exec rspec' do\n  watch(%r{^spec/.+_spec\\.rb$})\n  watch(%r{^lib/(.+)\\.rb$})     { |m| \"spec/#{m[1]}_spec.rb\" }\n  watch('spec/spec_helper.rb')  { 'spec' }\nend\n\nguard :rubocop do\n  watch(/.+\\.rb$/)\n  watch(%r{(?:.+/)?\\.rubocop\\.yml$}) { |m| File.dirname(m[0]) }\nend\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2010-2020 Michael Bleigh, Intridea Inc. and Contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "![grape logo](grape.png)\n\n[![Gem Version](https://badge.fury.io/rb/grape.svg)](http://badge.fury.io/rb/grape)\n[![test](https://github.com/ruby-grape/grape/actions/workflows/test.yml/badge.svg)](https://github.com/ruby-grape/grape/actions/workflows/test.yml)\n[![Coverage Status](https://coveralls.io/repos/github/ruby-grape/grape/badge.svg?branch=master)](https://coveralls.io/github/ruby-grape/grape?branch=master)\n\n## What is Grape?\n\nGrape is a REST-like API framework for Ruby. It's designed to run on Rack or complement existing web application frameworks such as Rails and Sinatra by providing a simple DSL to easily develop RESTful APIs. It has built-in support for common conventions, including multiple formats, subdomain/prefix restriction, content negotiation, versioning and much more.\n\n## Stable Release\n\nYou're reading the documentation for the next release of Grape, which should be 3.2.0.\nThe current stable release is [3.1.1](https://github.com/ruby-grape/grape/blob/v3.1.1/README.md).\n\n## Project Resources\n\n* [Grape Website](http://www.ruby-grape.org)\n* [Documentation](http://www.rubydoc.info/gems/grape)\n* Need help? [Open an Issue](https://github.com/ruby-grape/grape/issues)\n* [Follow us on Twitter](https://twitter.com/grapeframework)\n\n## Grape for Enterprise\n\nAvailable as part of the Tidelift Subscription.\n\nThe maintainers of Grape are working with Tidelift to deliver commercial support and maintenance. Save time, reduce risk, and improve code health, while paying the maintainers of Grape. Click [here](https://tidelift.com/subscription/request-a-demo?utm_source=rubygems-grape&utm_medium=referral&utm_campaign=enterprise) for more details.\n\n## Installation\n\nRuby 3.2 or newer is required.\n\nGrape is available as a gem, to install it run:\n\n    bundle add grape\n\n## Basic Usage\n\nGrape APIs are Rack applications that are created by subclassing `Grape::API`.\nBelow is a simple example showing some of the more common features of Grape in the context of recreating parts of the Twitter API.\n\n```ruby\nmodule Twitter\n  class API < Grape::API\n    version 'v1', using: :header, vendor: 'twitter'\n    format :json\n    prefix :api\n\n    helpers do\n      def current_user\n        @current_user ||= User.authorize!(env)\n      end\n\n      def authenticate!\n        error!('401 Unauthorized', 401) unless current_user\n      end\n    end\n\n    resource :statuses do\n      desc 'Return a public timeline.'\n      get :public_timeline do\n        Status.limit(20)\n      end\n\n      desc 'Return a personal timeline.'\n      get :home_timeline do\n        authenticate!\n        current_user.statuses.limit(20)\n      end\n\n      desc 'Return a status.'\n      params do\n        requires :id, type: Integer, desc: 'Status ID.'\n      end\n      route_param :id do\n        get do\n          Status.find(params[:id])\n        end\n      end\n\n      desc 'Create a status.'\n      params do\n        requires :status, type: String, desc: 'Your status.'\n      end\n      post do\n        authenticate!\n        Status.create!({\n          user: current_user,\n          text: params[:status]\n        })\n      end\n\n      desc 'Update a status.'\n      params do\n        requires :id, type: String, desc: 'Status ID.'\n        requires :status, type: String, desc: 'Your status.'\n      end\n      put ':id' do\n        authenticate!\n        current_user.statuses.find(params[:id]).update({\n          user: current_user,\n          text: params[:status]\n        })\n      end\n\n      desc 'Delete a status.'\n      params do\n        requires :id, type: String, desc: 'Status ID.'\n      end\n      delete ':id' do\n        authenticate!\n        current_user.statuses.find(params[:id]).destroy\n      end\n    end\n  end\nend\n```\n\n## Rails 7.1\n\nGrape's [deprecator](https://api.rubyonrails.org/v7.1.0/classes/ActiveSupport/Deprecation.html) will be added to your application's deprecators [automatically](lib/grape/railtie.rb) as `:grape`, so that your application's configuration can be applied to it.\n\n## Mounting\n\n### All\n\n\nBy default Grape will compile the routes on the first route, but it is possible to pre-load routes using the `compile!` method.\n\n```ruby\nTwitter::API.compile!\n```\n\nThis can be added to your `config.ru` (if using rackup), `application.rb` (if using rails), or any file that loads your server.\n\n### Rack\n\nThe above sample creates a Rack application that can be run from a rackup `config.ru` file with `rackup`:\n\n```ruby\nrun Twitter::API\n```\n\n(With pre-loading you can use)\n\n```ruby\nTwitter::API.compile!\nrun Twitter::API\n```\n\nAnd would respond to the following routes:\n\n    GET /api/statuses/public_timeline\n    GET /api/statuses/home_timeline\n    GET /api/statuses/:id\n    POST /api/statuses\n    PUT /api/statuses/:id\n    DELETE /api/statuses/:id\n\nGrape will also automatically respond to HEAD and OPTIONS for all GET, and just OPTIONS for all other routes.\n\n### Alongside Sinatra (or other frameworks)\n\nIf you wish to mount Grape alongside another Rack framework such as Sinatra, you can do so easily using `Rack::Cascade`:\n\n```ruby\n# Example config.ru\n\nrequire 'sinatra'\nrequire 'grape'\n\nclass API < Grape::API\n  get :hello do\n    { hello: 'world' }\n  end\nend\n\nclass Web < Sinatra::Base\n  get '/' do\n    'Hello world.'\n  end\nend\n\nuse Rack::Session::Cookie\nrun Rack::Cascade.new [Web, API]\n```\n\nNote that order of loading apps using `Rack::Cascade` matters. The grape application must be last if you want to raise custom 404 errors from grape (such as `error!('Not Found',404)`). If the grape application is not last and returns 404 or 405 response, [cascade utilizes that as a signal to try the next app](https://www.rubydoc.info/gems/rack/Rack/Cascade). This may lead to undesirable behavior showing the [wrong 404 page from the wrong app](https://github.com/ruby-grape/grape/issues/1515).\n\n\n### Rails\n\nPlace API files into `app/api`. Rails expects a subdirectory that matches the name of the Ruby module and a file name that matches the name of the class. In our example, the file name location and directory for `Twitter::API` should be `app/api/twitter/api.rb`.\n\nModify `config/routes`:\n\n```ruby\nmount Twitter::API => '/'\n```\n#### Zeitwerk\nRails's default autoloader is `Zeitwerk`. By default, it inflects `api` as `Api` instead of `API`. To make our example work, you need to uncomment the lines at the bottom of `config/initializers/inflections.rb`, and add `API` as an acronym:\n\n```ruby\nActiveSupport::Inflector.inflections(:en) do |inflect|\n  inflect.acronym 'API'\nend\n```\n\n### Modules\n\nYou can mount multiple API implementations inside another one. These don't have to be different versions, but may be components of the same API.\n\n```ruby\nclass Twitter::API < Grape::API\n  mount Twitter::APIv1\n  mount Twitter::APIv2\nend\n```\n\nYou can also mount on a path, which is similar to using `prefix` inside the mounted API itself.\n\n```ruby\nclass Twitter::API < Grape::API\n  mount Twitter::APIv1 => '/v1'\nend\n```\n\nDeclarations as `before/after/rescue_from` can be placed before or after `mount`. In any case they will be inherited.\n\n```ruby\nclass Twitter::API < Grape::API\n  before do\n    header 'X-Base-Header', 'will be defined for all APIs that are mounted below'\n  end\n\n  rescue_from :all do\n    error!({ \"error\" => \"Internal Server Error\" }, 500)\n  end\n\n  mount Twitter::Users\n  mount Twitter::Search\n\n  after do\n    clean_cache!\n  end\n\n  rescue_from ZeroDivisionError do\n    error!({ \"error\" => \"Not found\" }, 404)\n  end\nend\n```\n\n## Remounting\n\nYou can mount the same endpoints in two different locations.\n\n```ruby\nclass Voting::API < Grape::API\n  namespace 'votes' do\n    get do\n      # Your logic\n    end\n\n    post do\n      # Your logic\n    end\n  end\nend\n\nclass Post::API < Grape::API\n  mount Voting::API\nend\n\nclass Comment::API < Grape::API\n  mount Voting::API\nend\n```\n\nAssuming that the post and comment endpoints are mounted in `/posts` and `/comments`, you should now be able to do `get /posts/votes`, `post /posts/votes`, `get /comments/votes` and `post /comments/votes`.\n\n### Mount Configuration\n\nYou can configure remountable endpoints to change how they behave according to where they are mounted.\n\n```ruby\nclass Voting::API < Grape::API\n  namespace 'votes' do\n    desc \"Vote for your #{configuration[:votable]}\"\n    get do\n      # Your logic\n    end\n  end\nend\n\nclass Post::API < Grape::API\n  mount Voting::API, with: { votable: 'posts' }\nend\n\nclass Comment::API < Grape::API\n  mount Voting::API, with: { votable: 'comments' }\nend\n```\n\nNote that if you're passing a hash as the first parameter to `mount`, you will need to explicitly put `()` around parameters:\n```ruby\n# good\nmount({ ::Some::Api => '/some/api' }, with: { condition: true })\n\n# bad\nmount ::Some::Api => '/some/api', with: { condition: true }\n```\n\nYou can access `configuration` on the class (to use as dynamic attributes), inside blocks (like namespace)\n\nIf you want logic happening given on an `configuration`, you can use the helper `given`.\n\n```ruby\nclass ConditionalEndpoint::API < Grape::API\n  given configuration[:some_setting] do\n    get 'mount_this_endpoint_conditionally' do\n      configuration[:configurable_response]\n    end\n  end\nend\n```\n\nIf you want a block of logic running every time an endpoint is mounted (within which you can access the `configuration` Hash)\n\n\n```ruby\nclass ConditionalEndpoint::API < Grape::API\n  mounted do\n    YourLogger.info \"This API was mounted at: #{Time.now}\"\n\n    get configuration[:endpoint_name] do\n      configuration[:configurable_response]\n    end\n  end\nend\n```\n\nMore complex results can be achieved by using `mounted` as an expression within which the `configuration` is already evaluated as a Hash.\n\n```ruby\nclass ExpressionEndpointAPI < Grape::API\n  get(mounted { configuration[:route_name] || 'default_name' }) do\n    # some logic\n  end\nend\n```\n\n```ruby\nclass BasicAPI < Grape::API\n  desc 'Statuses index' do\n    params: (configuration[:entity] || API::Entities::Status).documentation\n  end\n  params do\n    requires :all, using: (configuration[:entity] || API::Entities::Status).documentation\n  end\n  get '/statuses' do\n    statuses = Status.all\n    type = current_user.admin? ? :full : :default\n    present statuses, with: (configuration[:entity] || API::Entities::Status), type: type\n  end\nend\n\nclass V1 < Grape::API\n  version 'v1'\n  mount BasicAPI, with: { entity: mounted { configuration[:entity] || API::Entities::Status } }\nend\n\nclass V2 < Grape::API\n  version 'v2'\n  mount BasicAPI, with: { entity: mounted { configuration[:entity] || API::Entities::V2::Status } }\nend\n```\n\n## Versioning\n\nYou have the option to provide various versions of your API by establishing a separate `Grape::API` class for each offered version and then integrating them into a primary `Grape::API` class. Ensure that newer versions are mounted before older ones. The default approach to versioning directs the request to the subsequent Rack middleware if a specific version is not found.\n\n```ruby\nrequire 'v1'\nrequire 'v2'\nrequire 'v3'\nclass App < Grape::API\n  mount V3\n  mount V2\n  mount V1\nend\n```\n\nTo maintain the same endpoints from earlier API versions without rewriting them, you can indicate multiple versions within the previous API versions.\n\n```ruby\nclass V1 < Grape::API\n  version 'v1', 'v2', 'v3'\n\n  get '/foo' do\n    # your code for GET /foo\n  end\n\n  get '/other' do\n    # your code for GET /other\n  end\nend\n\nclass V2 < Grape::API\n  version 'v2', 'v3'\n\n  get '/var' do\n    # your code for GET /var\n  end\nend\n\nclass V3 < Grape::API\n  version 'v3'\n\n  get '/foo' do\n    # your new code for GET /foo\n  end\nend\n```\n\nUsing the example provided, the subsequent endpoints will be accessible across various versions:\n\n```shell\nGET /v1/foo\nGET /v1/other\nGET /v2/foo # => Same behavior as v1\nGET /v2/other # => Same behavior as v1\nGET /v2/var # => New endpoint not available in v1\nGET /v3/foo # => Different behavior to v1 and v2\nGET /v3/other # => Same behavior as v1 and v2\nGET /v3/var # => Same behavior as v2\n```\n\nThere are four strategies in which clients can reach your API's endpoints: `:path`, `:header`, `:accept_version_header` and `:param`. The default strategy is `:path`.\n\n### Strategies\n\n#### Path\n\n```ruby\nversion 'v1', using: :path\n```\n\nUsing this versioning strategy, clients should pass the desired version in the URL.\n\n    curl http://localhost:9292/v1/statuses/public_timeline\n\n#### Header\n\n```ruby\nversion 'v1', using: :header, vendor: 'twitter'\n```\n\nCurrently, Grape only supports versioned media types in the following format:\n\n```\nvnd.vendor-and-or-resource-v1234+format\n```\n\nBasically all tokens between the final `-` and the `+` will be interpreted as the version.\n\nUsing this versioning strategy, clients should pass the desired version in the HTTP `Accept` head.\n\n    curl -H Accept:application/vnd.twitter-v1+json http://localhost:9292/statuses/public_timeline\n\nBy default, the first matching version is used when no `Accept` header is supplied. This behavior is similar to routing in Rails. To circumvent this default behavior, one could use the `:strict` option. When this option is set to `true`, a `406 Not Acceptable` error is returned when no correct `Accept` header is supplied.\n\nWhen an invalid `Accept` header is supplied, a `406 Not Acceptable` error is returned if the `:cascade` option is set to `false`. Otherwise a `404 Not Found` error is returned by Rack if no other route matches.\n\nGrape will evaluate the relative quality preference included in Accept headers and default to a quality of 1.0 when omitted. In the following example a Grape API that supports XML and JSON in that order will return JSON:\n\n    curl -H \"Accept: text/xml;q=0.8, application/json;q=0.9\" localhost:1234/resource\n\n#### Accept-Version Header\n\n```ruby\nversion 'v1', using: :accept_version_header\n```\n\nUsing this versioning strategy, clients should pass the desired version in the HTTP `Accept-Version` header.\n\n    curl -H \"Accept-Version:v1\" http://localhost:9292/statuses/public_timeline\n\nBy default, the first matching version is used when no `Accept-Version` header is supplied. This behavior is similar to routing in Rails. To circumvent this default behavior, one could use the `:strict` option. When this option is set to `true`, a `406 Not Acceptable` error is returned when no correct `Accept` header is supplied and the `:cascade` option is set to `false`. Otherwise a `404 Not Found` error is returned by Rack if no other route matches.\n\n#### Param\n\n```ruby\nversion 'v1', using: :param\n```\n\nUsing this versioning strategy, clients should pass the desired version as a request parameter, either in the URL query string or in the request body.\n\n    curl http://localhost:9292/statuses/public_timeline?apiver=v1\n\nThe default name for the query parameter is 'apiver' but can be specified using the `:parameter` option.\n\n```ruby\nversion 'v1', using: :param, parameter: 'v'\n```\n\n    curl http://localhost:9292/statuses/public_timeline?v=v1\n\n\n## Linting\n\nYou can check whether your API is in conformance with the [Rack's specification](https://github.com/rack/rack/blob/main/SPEC.rdoc) by calling `lint!` at the API level or through [configuration](#configuration).\n\n```ruby\nclass Api < Grape::API\n  lint!\nend\n```\n```ruby\nGrape.configure do |config|\n  config.lint = true\nend\n```\n```ruby\nGrape.config.lint = true\n```\n\n### Bug in Rack::ETag under Rack 3.X\nIf you're using Rack 3.X and the `Rack::Etag` middleware (used by [Rails](https://guides.rubyonrails.org/rails_on_rack.html#inspecting-middleware-stack)), a [bug](https://github.com/rack/rack/pull/2324) related to linting has been fixed in [3.1.13](https://github.com/rack/rack/blob/v3.1.13/CHANGELOG.md#3113---2025-04-13) and [3.0.15](https://github.com/rack/rack/blob/v3.1.13/CHANGELOG.md#3015---2025-04-13) respectively.\n\n## Describing Methods\n\nYou can add a description to API methods and namespaces. The description would be used by [grape-swagger][grape-swagger] to generate swagger compliant documentation.\n\nNote: Description block is only for documentation and won't affects API behavior.\n\n```ruby\ndesc 'Returns your public timeline.' do\n  summary 'summary'\n  detail 'more details'\n  params  API::Entities::Status.documentation\n  success API::Entities::Entity\n  failure [[401, 'Unauthorized', 'Entities::Error']]\n  default { code: 500, message: 'InvalidRequest', model: Entities::Error }\n  named 'My named route'\n  headers XAuthToken: {\n            description: 'Validates your identity',\n            required: true\n          },\n          XOptionalHeader: {\n            description: 'Not really needed',\n            required: false\n          }\n  hidden false\n  deprecated false\n  is_array true\n  nickname 'nickname'\n  produces ['application/json']\n  consumes ['application/json']\n  tags ['tag1', 'tag2']\nend\nget :public_timeline do\n  Status.limit(20)\nend\n```\n\n* `detail`: A more enhanced description\n* `params`: Define parameters directly from an `Entity`\n* `success`: (former entity) The `Entity` to be used to present the success response for this route.\n* `failure`: (former http_codes) A definition of the used failure HTTP Codes and Entities.\n* `default`: The definition and `Entity` used to present the default response for this route.\n* `named`: A helper to give a route a name and find it with this name in the documentation Hash\n* `headers`: A definition of the used Headers\n* Other options can be found in [grape-swagger][grape-swagger]\n\n[grape-swagger]: https://github.com/ruby-grape/grape-swagger\n\n## Configuration\n\nUse `Grape.configure` to set up global settings at load time.\nCurrently the configurable settings are:\n\n* `param_builder`: Sets the [Parameter Builder](#parameters), defaults to `Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder`.\n\nTo change a setting value make sure that at some point during load time the following code runs\n\n```ruby\nGrape.configure do |config|\n  config.setting = value\nend\n```\n\nFor example, for the `param_builder`, the following code could run in an initializer:\n\n```ruby\nGrape.configure do |config|\n  config.param_builder = :hashie_mash\nend\n```\n\nAvailable parameter builders are `:hash`, `:hash_with_indifferent_access`, and `:hashie_mash`.\nSee [params_builder](lib/grape/params_builder).\n\nYou can also configure a single API:\n\n```ruby\nAPI.configure do |config|\n  config[key] = value\nend\n```\n\nThis will be available inside the API with `configuration`, as if it were [mount configuration](#mount-configuration).\n\n## Parameters\n\nRequest parameters are available through the `params` hash object. This includes `GET`, `POST` and `PUT` parameters, along with any named parameters you specify in your route strings.\n\n```ruby\nget :public_timeline do\n  Status.order(params[:sort_by])\nend\n```\n\nParameters are automatically populated from the request body on `POST` and `PUT` for form input, JSON and XML content-types.\n\nThe request:\n\n```\ncurl -d '{\"text\": \"140 characters\"}' 'http://localhost:9292/statuses' -H Content-Type:application/json -v\n```\n\nThe Grape endpoint:\n\n```ruby\npost '/statuses' do\n  Status.create!(text: params[:text])\nend\n```\n\nMultipart POSTs and PUTs are supported as well.\n\nThe request:\n\n```\ncurl --form image_file='@image.jpg;type=image/jpg' http://localhost:9292/upload\n```\n\nThe Grape endpoint:\n\n```ruby\npost 'upload' do\n  # file in params[:image_file]\nend\n```\n\nIn the case of conflict between either of:\n\n* route string parameters\n* `GET`, `POST` and `PUT` parameters\n* the contents of the request body on `POST` and `PUT`\n\nRoute string parameters will have precedence.\n\n### Params Class\n\nBy default parameters are available as `ActiveSupport::HashWithIndifferentAccess`. This can be changed to, for example, Ruby `Hash` or `Hashie::Mash` for the entire API.\n\n```ruby\nclass API < Grape::API\n  build_with :hashie_mash\n\n  params do\n    optional :color, type: String\n  end\n  get do\n    params.color # instead of params[:color]\n  end\n```\n\nThe class can also be overridden on individual parameter blocks using `build_with` as follows.\n\n```ruby\nparams do\n  build_with :hash\n  optional :color, type: String\nend\n```\n\nIn the example above, `params[\"color\"]` will return `nil` since `params` is a plain `Hash`.\n\nAvailable parameter builders are `:hash`, `:hash_with_indifferent_access`, and `:hashie_mash`.\nSee [params_builder](lib/grape/params_builder).\n\n### Declared\n\nGrape allows you to access only the parameters that have been declared by your `params` block. It will:\n\n  * Filter out the params that have been passed, but are not allowed.\n  * Include any optional params that are declared but not passed.\n  * Perform any parameter renaming on the resulting hash.\n\nConsider the following API endpoint:\n\n````ruby\nformat :json\n\npost 'users/signup' do\n  { 'declared_params' => declared(params) }\nend\n````\n\nIf you do not specify any parameters, `declared` will return an empty hash.\n\n**Request**\n\n````bash\ncurl -X POST -H \"Content-Type: application/json\" localhost:9292/users/signup -d '{\"user\": {\"first_name\":\"first name\", \"last_name\": \"last name\"}}'\n````\n\n**Response**\n\n````json\n{\n  \"declared_params\": {}\n}\n\n````\n\nOnce we add parameters requirements, grape will start returning only the declared parameters.\n\n````ruby\nformat :json\n\nparams do\n  optional :user, type: Hash do\n    optional :first_name, type: String\n    optional :last_name, type: String\n  end\nend\n\npost 'users/signup' do\n  { 'declared_params' => declared(params) }\nend\n````\n\n**Request**\n\n````bash\ncurl -X POST -H \"Content-Type: application/json\" localhost:9292/users/signup -d '{\"user\": {\"first_name\":\"first name\", \"last_name\": \"last name\", \"random\": \"never shown\"}}'\n````\n\n**Response**\n\n````json\n{\n  \"declared_params\": {\n    \"user\": {\n      \"first_name\": \"first name\",\n      \"last_name\": \"last name\"\n    }\n  }\n}\n````\n\nMissing params that are declared as type `Hash` or `Array` will be included.\n\n````ruby\nformat :json\n\nparams do\n  optional :user, type: Hash do\n    optional :first_name, type: String\n    optional :last_name, type: String\n  end\n  optional :widgets, type: Array\nend\n\npost 'users/signup' do\n  { 'declared_params' => declared(params) }\nend\n````\n\n**Request**\n\n````bash\ncurl -X POST -H \"Content-Type: application/json\" localhost:9292/users/signup -d '{}'\n````\n\n**Response**\n\n````json\n{\n  \"declared_params\": {\n    \"user\": {\n      \"first_name\": null,\n      \"last_name\": null\n    },\n    \"widgets\": []\n  }\n}\n````\n\nThe returned hash is an `ActiveSupport::HashWithIndifferentAccess`.\n\nThe `#declared` method is not available to `before` filters, as those are evaluated prior to parameter coercion.\n\n### Include Parent Namespaces\n\nBy default `declared(params)` includes parameters that were defined in all parent namespaces. If you want to return only parameters from your current namespace, you can set `include_parent_namespaces` option to `false`.\n\n````ruby\nformat :json\n\nnamespace :parent do\n  params do\n    requires :parent_name, type: String\n  end\n\n  namespace ':parent_name' do\n    params do\n      requires :child_name, type: String\n    end\n    get ':child_name' do\n      {\n        'without_parent_namespaces' => declared(params, include_parent_namespaces: false),\n        'with_parent_namespaces' => declared(params, include_parent_namespaces: true),\n      }\n    end\n  end\nend\n````\n\n**Request**\n\n````bash\ncurl -X GET -H \"Content-Type: application/json\" localhost:9292/parent/foo/bar\n````\n\n**Response**\n\n````json\n{\n  \"without_parent_namespaces\": {\n    \"child_name\": \"bar\"\n  },\n  \"with_parent_namespaces\": {\n    \"parent_name\": \"foo\",\n    \"child_name\": \"bar\"\n  },\n}\n````\n\n### Include Missing\n\nBy default `declared(params)` includes parameters that have `nil` values. If you want to return only the parameters that are not `nil`, you can use the `include_missing` option. By default, `include_missing` is set to `true`. Consider the following API:\n\n````ruby\nformat :json\n\nparams do\n  requires :user, type: Hash do\n    requires :first_name, type: String\n    optional :last_name, type: String\n  end\nend\n\npost 'users/signup' do\n  { 'declared_params' => declared(params, include_missing: false) }\nend\n````\n\n**Request**\n\n````bash\ncurl -X POST -H \"Content-Type: application/json\" localhost:9292/users/signup -d '{\"user\": {\"first_name\":\"first name\", \"random\": \"never shown\"}}'\n````\n\n**Response with include_missing:false**\n\n````json\n{\n  \"declared_params\": {\n    \"user\": {\n      \"first_name\": \"first name\"\n    }\n  }\n}\n````\n\n**Response with include_missing:true**\n\n````json\n{\n  \"declared_params\": {\n    \"user\": {\n      \"first_name\": \"first name\",\n      \"last_name\": null\n    }\n  }\n}\n````\n\nIt also works on nested hashes:\n\n````ruby\nformat :json\n\nparams do\n  requires :user, type: Hash do\n    requires :first_name, type: String\n    optional :last_name, type: String\n    requires :address, type: Hash do\n      requires :city, type: String\n      optional :region, type: String\n    end\n  end\nend\n\npost 'users/signup' do\n  { 'declared_params' => declared(params, include_missing: false) }\nend\n````\n\n**Request**\n\n````bash\ncurl -X POST -H \"Content-Type: application/json\" localhost:9292/users/signup -d '{\"user\": {\"first_name\":\"first name\", \"random\": \"never shown\", \"address\": { \"city\": \"SF\"}}}'\n````\n\n**Response with include_missing:false**\n\n````json\n{\n  \"declared_params\": {\n    \"user\": {\n      \"first_name\": \"first name\",\n      \"address\": {\n        \"city\": \"SF\"\n      }\n    }\n  }\n}\n````\n\n**Response with include_missing:true**\n\n````json\n{\n  \"declared_params\": {\n    \"user\": {\n      \"first_name\": \"first name\",\n      \"last_name\": null,\n      \"address\": {\n        \"city\": \"Zurich\",\n        \"region\": null\n      }\n    }\n  }\n}\n````\n\nNote that an attribute with a `nil` value is not considered *missing* and will also be returned when `include_missing` is set to `false`:\n\n**Request**\n\n````bash\ncurl -X POST -H \"Content-Type: application/json\" localhost:9292/users/signup -d '{\"user\": {\"first_name\":\"first name\", \"last_name\": null, \"address\": { \"city\": \"SF\"}}}'\n````\n\n**Response with include_missing:false**\n\n````json\n{\n  \"declared_params\": {\n    \"user\": {\n      \"first_name\": \"first name\",\n      \"last_name\": null,\n      \"address\": { \"city\": \"SF\"}\n    }\n  }\n}\n````\n\n### Evaluate Given\n\nBy default `declared(params)` will not evaluate `given` and return all parameters. Use `evaluate_given` to evaluate all `given` blocks and return only parameters that satisfy `given` conditions. Consider the following API:\n\n````ruby\nformat :json\n\nparams do\n  optional :child_id, type: Integer\n  given :child_id do\n    requires :father_id, type: Integer\n  end\nend\n\npost 'child' do\n  { 'declared_params' => declared(params, evaluate_given: true) }\nend\n````\n\n**Request**\n\n````bash\ncurl -X POST -H \"Content-Type: application/json\" localhost:9292/child -d '{\"father_id\": 1}'\n````\n\n**Response with evaluate_given:false**\n\n````json\n{\n  \"declared_params\": {\n    \"child_id\": null,\n    \"father_id\": 1\n  }\n}\n````\n\n**Response with evaluate_given:true**\n\n````json\n{\n  \"declared_params\": {\n    \"child_id\": null\n  }\n}\n````\n\nIt also works on nested hashes:\n\n````ruby\nformat :json\n\nparams do\n  requires :child, type: Hash do\n    optional :child_id, type: Integer\n    given :child_id do\n      requires :father_id, type: Integer\n    end\n  end\nend\n\npost 'child' do\n  { 'declared_params' => declared(params, evaluate_given: true) }\nend\n````\n\n**Request**\n\n````bash\ncurl -X POST -H \"Content-Type: application/json\" localhost:9292/child -d '{\"child\": {\"father_id\": 1}}'\n````\n\n**Response with evaluate_given:false**\n\n````json\n{\n  \"declared_params\": {\n    \"child\": {\n      \"child_id\": null,\n      \"father_id\": 1\n    }\n  }\n}\n````\n\n**Response with evaluate_given:true**\n\n````json\n{\n  \"declared_params\": {\n    \"child\": {\n      \"child_id\": null\n    }\n  }\n}\n````\n\n### Parameter Precedence\n\nUsing `route_param` takes higher precedence over a regular parameter defined with same name:\n\n```ruby\nparams do\n  requires :foo, type: String\nend\nroute_param :foo do\n  get do\n    { value: params[:foo] }\n  end\nend\n```\n\n**Request**\n\n```bash\ncurl -X POST -H \"Content-Type: application/json\" localhost:9292/bar -d '{\"foo\": \"baz\"}'\n```\n\n**Response**\n\n```json\n{\n  \"value\": \"bar\"\n}\n```\n\n## Parameter Validation and Coercion\n\nYou can define validations and coercion options for your parameters using a `params` block.\n\n```ruby\nparams do\n  requires :id, type: Integer\n  optional :text, type: String, regexp: /\\A[a-z]+\\z/\n  group :media, type: Hash do\n    requires :url\n  end\n  optional :audio, type: Hash do\n    requires :format, type: Symbol, values: [:mp3, :wav, :aac, :ogg], default: :mp3\n  end\n  mutually_exclusive :media, :audio\nend\nput ':id' do\n  # params[:id] is an Integer\nend\n```\n\nWhen a type is specified an implicit validation is done after the coercion to ensure the output type is the one declared.\n\nOptional parameters can have a default value.\n\n```ruby\nparams do\n  optional :color, type: String, default: 'blue'\n  optional :random_number, type: Integer, default: -> { Random.rand(1..100) }\n  optional :non_random_number, type: Integer, default:  Random.rand(1..100)\nend\n```\n\nDefault values are eagerly evaluated. Above `:non_random_number` will evaluate to the same number for each call to the endpoint of this `params` block. To have the default evaluate lazily with each request use a lambda, like `:random_number` above.\n\nNote that default values will be passed through to any validation options specified.\nThe following example will always fail if `:color` is not explicitly provided.\n\n```ruby\nparams do\n  optional :color, type: String, default: 'blue', values: ['red', 'green']\nend\n```\n\nThe correct implementation is to ensure the default value passes all validations.\n\n```ruby\nparams do\n  optional :color, type: String, default: 'blue', values: ['blue', 'red', 'green']\nend\n```\n\nYou can use the value of one parameter as the default value of some other parameter. In this case, if the `primary_color` parameter is not provided, it will have the same value as the `color` one. If both of them not provided, both of them will have `blue` value.\n\n```ruby\nparams do\n  optional :color, type: String, default: 'blue'\n  optional :primary_color, type: String, default: -> (params) { params[:color] }\nend\n```\n\n### Supported Parameter Types\n\nThe following are all valid types, supported out of the box by Grape:\n\n* Integer\n* Float\n* BigDecimal\n* Numeric\n* Date\n* DateTime\n* Time\n* Boolean\n* String\n* Symbol\n* Rack::Multipart::UploadedFile (alias `File`)\n* JSON\n\n### Integer/Fixnum and Coercions\n\nPlease be aware that the behavior differs between Ruby 2.4 and earlier versions.\nIn Ruby 2.4, values consisting of numbers are converted to Integer, but in earlier versions it will be treated as Fixnum.\n\n```ruby\nparams do\n  requires :integers, type: Hash do\n    requires :int, coerce: Integer\n  end\nend\nget '/int' do\n  params[:integers][:int].class\nend\n\n...\n\nget '/int' integers: { int: '45' }\n  #=> Integer in ruby 2.4\n  #=> Fixnum in earlier ruby versions\n```\n\n### Custom Types and Coercions\n\nAside from the default set of supported types listed above, any class can be used as a type as long as an explicit coercion method is supplied. If the type implements a class-level `parse` method, Grape will use it automatically. This method must take one string argument and return an instance of the correct type, or return an instance of `Grape::Types::InvalidValue` which optionally accepts a message to be returned in the response.\n\n```ruby\nclass Color\n  attr_reader :value\n  def initialize(color)\n    @value = color\n  end\n\n  def self.parse(value)\n    return new(value) if %w[blue red green].include?(value)\n\n    Grape::Types::InvalidValue.new('Unsupported color')\n  end\nend\n\nparams do\n  requires :color, type: Color, default: Color.new('blue')\n  requires :more_colors, type: Array[Color] # Collections work\n  optional :unique_colors, type: Set[Color] # Duplicates discarded\nend\n\nget '/stuff' do\n  # params[:color] is already a Color.\n  params[:color].value\nend\n```\n\nAlternatively, a custom coercion method may be supplied for any type of parameter using `coerce_with`. Any class or object may be given that implements a `parse` or `call` method, in that order of precedence. The method must accept a single string parameter, and the return value must match the given `type`.\n\n```ruby\nparams do\n  requires :passwd, type: String, coerce_with: Base64.method(:decode64)\n  requires :loud_color, type: Color, coerce_with: ->(c) { Color.parse(c.downcase) }\n\n  requires :obj, type: Hash, coerce_with: JSON do\n    requires :words, type: Array[String], coerce_with: ->(val) { val.split(/\\s+/) }\n    optional :time, type: Time, coerce_with: Chronic\n  end\nend\n```\nNote that, a `nil` value will call the custom coercion method, while a missing parameter will not.\n\nExample of use of `coerce_with` with a lambda (a class with a `parse` method could also have been used)\nIt will parse a string and return an Array of Integers, matching the `Array[Integer]` `type`.\n\n```ruby\nparams do\n  requires :values, type: Array[Integer], coerce_with: ->(val) { val.split(/\\s+/).map(&:to_i) }\nend\n```\n\nGrape will assert that coerced values match the given `type`, and will reject the request if they do not. To override this behaviour, custom types may implement a `parsed?` method that should accept a single argument and return `true` if the value passes type validation.\n\n```ruby\nclass SecureUri\n  def self.parse(value)\n    URI.parse value\n  end\n\n  def self.parsed?(value)\n    value.is_a? URI::HTTPS\n  end\nend\n\nparams do\n  requires :secure_uri, type: SecureUri\nend\n```\n\n### Multipart File Parameters\n\nGrape makes use of `Rack::Request`'s built-in support for multipart file parameters. Such parameters can be declared with `type: File`:\n\n```ruby\nparams do\n  requires :avatar, type: File\nend\npost '/' do\n  params[:avatar][:filename] # => 'avatar.png'\n  params[:avatar][:type] # => 'image/png'\n  params[:avatar][:tempfile] # => #<File>\nend\n```\n\n### First-Class `JSON` Types\n\nGrape supports complex parameters given as JSON-formatted strings using the special `type: JSON` declaration. JSON objects and arrays of objects are accepted equally, with nested validation rules applied to all objects in either case:\n\n```ruby\nparams do\n  requires :json, type: JSON do\n    requires :int, type: Integer, values: [1, 2, 3]\n  end\nend\nget '/' do\n  params[:json].inspect\nend\n\nclient.get('/', json: '{\"int\":1}') # => \"{:int=>1}\"\nclient.get('/', json: '[{\"int\":\"1\"}]') # => \"[{:int=>1}]\"\n\nclient.get('/', json: '{\"int\":4}') # => HTTP 400\nclient.get('/', json: '[{\"int\":4}]') # => HTTP 400\n```\n\nAdditionally `type: Array[JSON]` may be used, which explicitly marks the parameter as an array of objects. If a single object is supplied it will be wrapped.\n\n```ruby\nparams do\n  requires :json, type: Array[JSON] do\n    requires :int, type: Integer\n  end\nend\nget '/' do\n  params[:json].each { |obj| ... } # always works\nend\n```\nFor stricter control over the type of JSON structure which may be supplied, use `type: Array, coerce_with: JSON` or `type: Hash, coerce_with: JSON`.\n\n### Multiple Allowed Types\n\nVariant-type parameters can be declared using the `types` option rather than `type`:\n\n```ruby\nparams do\n  requires :status_code, types: [Integer, String, Array[Integer, String]]\nend\nget '/' do\n  params[:status_code].inspect\nend\n\nclient.get('/', status_code: 'OK_GOOD') # => \"OK_GOOD\"\nclient.get('/', status_code: 300) # => 300\nclient.get('/', status_code: %w(404 NOT FOUND)) # => [404, \"NOT\", \"FOUND\"]\n```\n\nAs a special case, variant-member-type collections may also be declared, by passing a `Set` or `Array` with more than one member to `type`:\n\n```ruby\nparams do\n  requires :status_codes, type: Array[Integer,String]\nend\nget '/' do\n  params[:status_codes].inspect\nend\n\nclient.get('/', status_codes: %w(1 two)) # => [1, \"two\"]\n```\n\n### Validation of Nested Parameters\n\nParameters can be nested using `group` or by calling `requires` or `optional` with a block.\nIn the [above example](#parameter-validation-and-coercion), this means `params[:media][:url]` is required along with `params[:id]`, and `params[:audio][:format]` is required only if `params[:audio]` is present.\nWith a block, `group`, `requires` and `optional` accept an additional option `type` which can be either `Array` or `Hash`, and defaults to `Array`. Depending on the value, the nested parameters will be treated either as values of a hash or as values of hashes in an array.\n\n```ruby\nparams do\n  optional :preferences, type: Array do\n    requires :key\n    requires :value\n  end\n\n  requires :name, type: Hash do\n    requires :first_name\n    requires :last_name\n  end\nend\n```\n\n### Dependent Parameters\n\nSuppose some of your parameters are only relevant if another parameter is given; Grape allows you to express this relationship through the `given` method in your parameters block, like so:\n\n```ruby\nparams do\n  optional :shelf_id, type: Integer\n  given :shelf_id do\n    requires :bin_id, type: Integer\n  end\nend\n```\n\nIn the example above Grape will use `blank?` to check whether the `shelf_id` param is present.\n\n`given` also takes a `Proc` with custom code. Below, the param `description` is required only if the value of `category` is equal `foo`:\n\n```ruby\nparams do\n  optional :category\n  given category: ->(val) { val == 'foo' } do\n    requires :description\n  end\nend\n```\n\nYou can rename parameters:\n\n```ruby\nparams do\n  optional :category, as: :type\n  given type: ->(val) { val == 'foo' } do\n    requires :description\n  end\nend\n```\n\nNote: param in `given` should be the renamed one. In the example, it should be `type`, not `category`.\n\n### Group Options\n\nParameters options can be grouped. It can be useful if you want to extract common validation or types for several parameters.\nWithin these groups, individual parameters can extend or selectively override the common settings, allowing you to maintain the defaults at the group level while still applying parameter-specific rules where necessary.\n\nThe example below presents a typical case when parameters share common options.\n\n```ruby\nparams do\n  requires :first_name, type: String, regexp: /w+/, desc: 'First name', documentation: { in: 'body' }\n  optional :middle_name, type: String, regexp: /w+/, desc: 'Middle name', documentation: { in: 'body', x: { nullable: true } }\n  requires :last_name, type: String, regexp: /w+/, desc: 'Last name', documentation: { in: 'body' }\nend\n```\n\nGrape allows you to present the same logic through the `with` method in your parameters block, like so:\n\n```ruby\nparams do\n  with(type: String, regexp: /w+/, documentation: { in: 'body' }) do\n    requires :first_name, desc: 'First name'\n    optional :middle_name, desc: 'Middle name', documentation: { x: { nullable: true } }\n    requires :last_name, desc: 'Last name'\n  end\nend\n```\n\nYou can organize settings into layers using nested `with' blocks. Each layer can use, add to, or change the settings of the layer above it. This helps to keep complex parameters organized and consistent, while still allowing for specific customizations to be made.\n\n```ruby\nparams do\n  with(documentation: { in: 'body' }) do  # Applies documentation to all nested parameters\n    with(type: String, regexp: /\\w+/) do  # Applies type and validation to names\n      requires :first_name, desc: 'First name'\n      requires :last_name, desc: 'Last name'\n    end\n    optional :age, type: Integer, desc: 'Age', documentation: { x: { nullable: true } }  # Specific settings for 'age'\n  end\nend\n```\n\n### Renaming\n\nYou can rename parameters using `as`, which can be useful when refactoring existing APIs:\n\n```ruby\nresource :users do\n  params do\n    requires :email_address, as: :email\n    requires :password\n  end\n  post do\n    User.create!(declared(params)) # User takes email and password\n  end\nend\n```\n\nThe value passed to `as` will be the key when calling `declared(params)`.\n\n### Built-in Validators\n\n#### `allow_blank`\n\nParameters can be defined as `allow_blank`, ensuring that they contain a value. By default, `requires` only validates that a parameter was sent in the request, regardless its value. With `allow_blank: false`, empty values or whitespace only values are invalid.\n\n`allow_blank` can be combined with both `requires` and `optional`. If the parameter is required, it has to contain a value. If it's optional, it's possible to not send it in the request, but if it's being sent, it has to have some value, and not an empty string/only whitespaces.\n\n\n```ruby\nparams do\n  requires :username, allow_blank: false\n  optional :first_name, allow_blank: false\nend\n```\n\n#### `values`\n\nParameters can be restricted to a specific set of values with the `:values` option.\n\n\n```ruby\nparams do\n  requires :status, type: Symbol, values: [:not_started, :processing, :done]\n  optional :numbers, type: Array[Integer], default: 1, values: [1, 2, 3, 5, 8]\nend\n```\n\nSupplying a range to the `:values` option ensures that the parameter is (or parameters are) included in that range (using `Range#include?`).\n\n```ruby\nparams do\n  requires :latitude, type: Float, values: -90.0..+90.0\n  requires :longitude, type: Float, values: -180.0..+180.0\n  optional :letters, type: Array[String], values: 'a'..'z'\nend\n```\n\nNote endless ranges are also supported with ActiveSupport >= 6.0, but they require that the type be provided.\n\n```ruby\nparams do\n  requires :minimum, type: Integer, values: 10..\n  optional :maximum, type: Integer, values: ..10\nend\n```\n\nNote that *both* range endpoints have to be a `#kind_of?` your `:type` option (if you don't supply the `:type` option, it will be guessed to be equal to the class of the range's first endpoint). So the following is invalid:\n\n```ruby\nparams do\n  requires :invalid1, type: Float, values: 0..10 # 0.kind_of?(Float) => false\n  optional :invalid2, values: 0..10.0 # 10.0.kind_of?(0.class) => false\nend\n```\n\nThe `:values` option can also be supplied with a `Proc`, evaluated lazily with each request.\nIf the Proc has arity zero (i.e. it takes no arguments) it is expected to return either a list or a range which will then be used to validate the parameter.\n\nFor example, given a status model you may want to restrict by hashtags that you have previously defined in the `HashTag` model.\n\n```ruby\nparams do\n  requires :hashtag, type: String, values: -> { Hashtag.all.map(&:tag) }\nend\n```\n\nAlternatively, a Proc with arity one (i.e. taking one argument) can be used to explicitly validate each parameter value.  In that case, the Proc is expected to return a truthy value if the parameter value is valid. The parameter will be considered invalid if the Proc returns a falsy value or if it raises a StandardError.\n\n```ruby\nparams do\n  requires :number, type: Integer, values: ->(v) { v.even? && v < 25 }\nend\n```\n\nWhile Procs are convenient for single cases, consider using [Custom Validators](#custom-validators) in cases where a validation is used more than once.\n\nNote that [allow_blank](#allow_blank) validator applies while using `:values`. In the following example the absence of `:allow_blank` does not prevent `:state` from receiving blank values because `:allow_blank` defaults to `true`.\n\n```ruby\nparams do\n  requires :state, type: Symbol, values: [:active, :inactive]\nend\n```\n\n#### `except_values`\n\nParameters can be restricted from having a specific set of values with the `:except_values` option.\n\nThe `except_values` validator behaves similarly to the `values` validator in that it accepts either an Array, a Range, or a Proc.  Unlike the `values` validator, however, `except_values` only accepts Procs with arity zero.\n\n```ruby\nparams do\n  requires :browser, except_values: [ 'ie6', 'ie7', 'ie8' ]\n  requires :port, except_values: { value: 0..1024, message: 'is not allowed' }\n  requires :hashtag, except_values: -> { Hashtag.FORBIDDEN_LIST }\nend\n```\n\n#### `same_as`\n\nA `same_as` option can be given to ensure that values of parameters match.\n\n```ruby\nparams do\n  requires :password\n  requires :password_confirmation, same_as: :password\nend\n```\n\n#### `length`\n\nParameters with types that support `#length` method can be restricted to have a specific length with the `:length` option.\n\nThe validator accepts `:min` or `:max` or both options or only `:is` to validate that the value of the parameter is within the given limits.\n\n```ruby\nparams do\n  requires :code, type: String, length: { is: 2 }\n  requires :str, type: String, length: { min: 3 }\n  requires :list, type: [Integer], length: { min: 3, max: 5 }\n  requires :hash, type: Hash, length: { max: 5 }\nend\n```\n\n#### `regexp`\n\nParameters can be restricted to match a specific regular expression with the `:regexp` option. If the value does not match the regular expression an error will be returned. Note that this is true for both `requires` and `optional` parameters.\n\n```ruby\nparams do\n  requires :email, regexp: /.+@.+/\nend\n```\n\nThe validator will pass if the parameter was sent without value. To ensure that the parameter contains a value, use `allow_blank: false`.\n\n```ruby\nparams do\n  requires :email, allow_blank: false, regexp: /.+@.+/\nend\n```\n\n#### `mutually_exclusive`\n\nParameters can be defined as `mutually_exclusive`, ensuring that they aren't present at the same time in a request.\n\n```ruby\nparams do\n  optional :beer\n  optional :wine\n  mutually_exclusive :beer, :wine\nend\n```\n\nMultiple sets can be defined:\n\n```ruby\nparams do\n  optional :beer\n  optional :wine\n  mutually_exclusive :beer, :wine\n  optional :scotch\n  optional :aquavit\n  mutually_exclusive :scotch, :aquavit\nend\n```\n\n**Warning**: Never define mutually exclusive sets with any required params. Two mutually exclusive required params will mean params are never valid, thus making the endpoint useless. One required param mutually exclusive with an optional param will mean the latter is never valid.\n\n#### `exactly_one_of`\n\nParameters can be defined as 'exactly_one_of', ensuring that exactly one parameter gets selected.\n\n```ruby\nparams do\n  optional :beer\n  optional :wine\n  exactly_one_of :beer, :wine\nend\n```\n\nNote that using `:default` with `mutually_exclusive` will cause multiple parameters to always have a default value and raise a `Grape::Exceptions::Validation` mutually exclusive exception.\n\n#### `at_least_one_of`\n\nParameters can be defined as 'at_least_one_of', ensuring that at least one parameter gets selected.\n\n```ruby\nparams do\n  optional :beer\n  optional :wine\n  optional :juice\n  at_least_one_of :beer, :wine, :juice\nend\n```\n\n#### `all_or_none_of`\n\nParameters can be defined as 'all_or_none_of', ensuring that all or none of parameters gets selected.\n\n```ruby\nparams do\n  optional :beer\n  optional :wine\n  optional :juice\n  all_or_none_of :beer, :wine, :juice\nend\n```\n\n#### Nested `mutually_exclusive`, `exactly_one_of`, `at_least_one_of`, `all_or_none_of`\n\nAll of these methods can be used at any nested level.\n\n```ruby\nparams do\n  requires :food, type: Hash do\n    optional :meat\n    optional :fish\n    optional :rice\n    at_least_one_of :meat, :fish, :rice\n  end\n  group :drink, type: Hash do\n    optional :beer\n    optional :wine\n    optional :juice\n    exactly_one_of :beer, :wine, :juice\n  end\n  optional :dessert, type: Hash do\n    optional :cake\n    optional :icecream\n    mutually_exclusive :cake, :icecream\n  end\n  optional :recipe, type: Hash do\n    optional :oil\n    optional :meat\n    all_or_none_of :oil, :meat\n  end\nend\n```\n\n### Namespace Validation and Coercion\n\nNamespaces allow parameter definitions and apply to every method within the namespace.\n\n```ruby\nnamespace :statuses do\n  params do\n    requires :user_id, type: Integer, desc: 'A user ID.'\n  end\n  namespace ':user_id' do\n    desc \"Retrieve a user's status.\"\n    params do\n      requires :status_id, type: Integer, desc: 'A status ID.'\n    end\n    get ':status_id' do\n      User.find(params[:user_id]).statuses.find(params[:status_id])\n    end\n  end\nend\n```\n\nThe `namespace` method has a number of aliases, including: `group`, `resource`, `resources`, and `segment`. Use whichever reads the best for your API.\n\nYou can conveniently define a route parameter as a namespace using `route_param`.\n\n```ruby\nnamespace :statuses do\n  route_param :id do\n    desc 'Returns all replies for a status.'\n    get 'replies' do\n      Status.find(params[:id]).replies\n    end\n    desc 'Returns a status.'\n    get do\n      Status.find(params[:id])\n    end\n  end\nend\n```\n\nYou can also define a route parameter type by passing to `route_param`'s options.\n\n```ruby\nnamespace :arithmetic do\n  route_param :n, type: Integer do\n    desc 'Returns in power'\n    get 'power' do\n      params[:n] ** params[:n]\n    end\n  end\nend\n```\n\n### Custom Validators\n\n```ruby\nclass AlphaNumeric < Grape::Validations::Validators::Base\n  def validate_param!(attr_name, params)\n    unless params[attr_name] =~ /\\A[[:alnum:]]+\\z/\n      raise Grape::Exceptions::Validation.new params: [@scope.full_name(attr_name)], message: 'must consist of alpha-numeric characters'\n    end\n  end\nend\n```\n\n```ruby\nparams do\n  requires :text, alpha_numeric: true\nend\n```\n\nYou can also create custom classes that take parameters.\n\n```ruby\nclass Length < Grape::Validations::Validators::Base\n  def validate_param!(attr_name, params)\n    unless params[attr_name].length <= @option\n      raise Grape::Exceptions::Validation.new params: [@scope.full_name(attr_name)], message: \"must be at the most #{@option} characters long\"\n    end\n  end\nend\n```\n\n```ruby\nparams do\n  requires :text, length: 140\nend\n```\n\nYou can also create custom validation that use request to validate the attribute. For example if you want to have parameters that are available to only admins, you can do the following.\n\n```ruby\nclass Admin < Grape::Validations::Validators::Base\n  def validate(request)\n    # return if the param we are checking was not in request\n    # @attrs is a list containing the attribute we are currently validating\n    # in our sample case this method once will get called with\n    # @attrs being [:admin_field] and once with @attrs being [:admin_false_field]\n    return unless request.params.key?(@attrs.first)\n    # check if admin flag is set to true\n    return unless @option\n    # check if user is admin or not\n    # as an example get a token from request and check if it's admin or not\n    raise Grape::Exceptions::Validation.new params: @attrs, message: 'Can not set admin-only field.' unless request.headers['X-Access-Token'] == 'admin'\n  end\nend\n```\n\nAnd use it in your endpoint definition as:\n\n```ruby\nparams do\n  optional :admin_field, type: String, admin: true\n  optional :non_admin_field, type: String\n  optional :admin_false_field, type: String, admin: false\nend\n```\n\nEvery validation will have its own instance of the validator, which means that the validator can have a state.\n\n### Validation Errors\n\nValidation and coercion errors are collected and an exception of type `Grape::Exceptions::ValidationErrors` is raised. If the exception goes uncaught it will respond with a status of 400 and an error message. The validation errors are grouped by parameter name and can be accessed via `Grape::Exceptions::ValidationErrors#errors`.\n\n\nThe default response from a `Grape::Exceptions::ValidationErrors` is a humanly readable string, such as \"beer, wine are mutually exclusive\", in the following example.\n\n```ruby\nparams do\n  optional :beer\n  optional :wine\n  optional :juice\n  exactly_one_of :beer, :wine, :juice\nend\n```\n\nYou can rescue a `Grape::Exceptions::ValidationErrors` and respond with a custom response or turn the response into well-formatted JSON for a JSON API that separates individual parameters and the corresponding error messages. The following `rescue_from` example produces `[{\"params\":[\"beer\",\"wine\"],\"messages\":[\"are mutually exclusive\"]}]`.\n\n```ruby\nformat :json\nsubject.rescue_from Grape::Exceptions::ValidationErrors do |e|\n  error! e, 400\nend\n```\n\n`Grape::Exceptions::ValidationErrors#full_messages` returns the validation messages as an array. `Grape::Exceptions::ValidationErrors#message` joins the messages to one string.\n\nFor responding with an array of validation messages, you can use `Grape::Exceptions::ValidationErrors#full_messages`.\n```ruby\nformat :json\nsubject.rescue_from Grape::Exceptions::ValidationErrors do |e|\n  error!({ messages: e.full_messages }, 400)\nend\n```\n\nGrape returns all validation and coercion errors found by default.\nTo skip all subsequent validation checks when a specific param is found invalid, use `fail_fast: true`.\n\nThe following example will not check if `:wine` is present unless it finds `:beer`.\n```ruby\nparams do\n  required :beer, fail_fast: true\n  required :wine\nend\n```\nThe result of empty params would be a single `Grape::Exceptions::ValidationErrors` error.\n\nSimilarly, no regular expression test will be performed if `:blah` is blank in the following example.\n```ruby\nparams do\n  required :blah, allow_blank: false, regexp: /blah/, fail_fast: true\nend\n```\n\n### I18n\n\nGrape supports I18n for parameter-related error messages, but will fallback to English if translations for the default locale have not been provided. See [en.yml](lib/grape/locale/en.yml) for message keys.\n\nIn case your app enforces available locales only and :en is not included in your available locales, Grape cannot fall back to English and will return the translation key for the error message. To avoid this behaviour, either provide a translation for your default locale or add :en to your available locales.\n\nCustom validators that inherit from `Grape::Validations::Validators::Base` have access to a `translate` helper (see `Grape::Util::Translation`) and should use it instead of calling `I18n` directly. It applies the same `:en` fallback as built-in validators, defaults `scope` to `'grape.errors.messages'`, and handles interpolation without needing `format`:\n\n```ruby\n# Good — scope defaults to 'grape.errors.messages', interpolation forwarded automatically\ntranslate(:special, min: 2, max: 10)\n\n# Bad — format is unnecessary and risks conflicting with I18n reserved keys\nformat I18n.t(:special, scope: 'grape.errors.messages'), min: 2, max: 10\n```\n\nExample custom validator:\n\n```ruby\nclass SpecialValidator < Grape::Validations::Validators::Base\n  def validate_param!(attr_name, params)\n    return if valid?(params[attr_name])\n\n    raise Grape::Exceptions::Validation.new(\n      params: [@scope.full_name(attr_name)],\n      message: translate(:special, min: 2, max: 10)\n    )\n  end\nend\n```\n\n### Custom Validation messages\n\nGrape supports custom validation messages for parameter-related and coerce-related error messages.\n\n#### `presence`, `allow_blank`, `values`, `regexp`\n\n```ruby\nparams do\n  requires :name, values: { value: 1..10, message: 'not in range from 1 to 10' }, allow_blank: { value: false, message: 'cannot be blank' }, regexp: { value: /^[a-z]+$/, message: 'format is invalid' }, message: 'is required'\nend\n```\n\n#### `same_as`\n\n```ruby\nparams do\n  requires :password\n  requires :password_confirmation, same_as: { value: :password, message: 'not match' }\nend\n```\n\n#### `length`\n\n```ruby\nparams do\n  requires :code, type: String, length: { is: 2, message: 'code is expected to be exactly 2 characters long' }\n  requires :str, type: String, length: { min: 5, message: 'str is expected to be at least 5 characters long' }\n  requires :list, type: [Integer], length: { min: 2, max: 3, message: 'list is expected to have between 2 and 3 elements' }\nend\n```\n\n#### `all_or_none_of`\n\n```ruby\nparams do\n  optional :beer\n  optional :wine\n  optional :juice\n  all_or_none_of :beer, :wine, :juice, message: \"all params are required or none is required\"\nend\n```\n\n#### `mutually_exclusive`\n\n```ruby\nparams do\n  optional :beer\n  optional :wine\n  optional :juice\n  mutually_exclusive :beer, :wine, :juice, message: \"are mutually exclusive cannot pass both params\"\nend\n```\n\n#### `exactly_one_of`\n\n```ruby\nparams do\n  optional :beer\n  optional :wine\n  optional :juice\n  exactly_one_of :beer, :wine, :juice, message: { exactly_one: \"are missing, exactly one parameter is required\", mutual_exclusion: \"are mutually exclusive, exactly one parameter is required\" }\nend\n```\n\n#### `at_least_one_of`\n\n```ruby\nparams do\n  optional :beer\n  optional :wine\n  optional :juice\n  at_least_one_of :beer, :wine, :juice, message: \"are missing, please specify at least one param\"\nend\n```\n\n#### `Coerce`\n\n```ruby\nparams do\n  requires :int, type: { value: Integer, message: \"type cast is invalid\" }\nend\n```\n\n#### `With Lambdas`\n\n```ruby\nparams do\n  requires :name, values: { value: -> { (1..10).to_a }, message: 'not in range from 1 to 10' }\nend\n```\n\n#### `Pass symbols for i18n translations`\n\nYou can pass a symbol if you want i18n translations for your custom validation messages.\n\n```ruby\nparams do\n  requires :name, message: :name_required\nend\n```\n```ruby\n# en.yml\n\nen:\n  grape:\n    errors:\n      format: ! '%{attributes} %{message}'\n      messages:\n        name_required: 'must be present'\n```\n\n#### Overriding Attribute Names\n\nYou can also override attribute names.\n\n```ruby\n# en.yml\n\nen:\n  grape:\n    errors:\n      format: ! '%{attributes} %{message}'\n      messages:\n        name_required: 'must be present'\n      attributes:\n        name: 'Oops! Name'\n```\nWill produce 'Oops! Name must be present'\n\n#### With Default\n\nYou cannot set a custom message option for Default as it requires interpolation `%{option1}: %{value1} is incompatible with %{option2}: %{value2}`. You can change the default error message for Default by changing the `incompatible_option_values` message key inside [en.yml](lib/grape/locale/en.yml)\n\n```ruby\nparams do\n  requires :name, values: { value: -> { (1..10).to_a }, message: 'not in range from 1 to 10' }, default: 5\nend\n```\n\n### Using `dry-validation` or `dry-schema`\n\nAs an alternative to the `params` DSL described above, you can use a schema or `dry-validation` contract to describe an endpoint's parameters. This can be especially useful if you use the above already in some other parts of your application. If not, you'll need to add `dry-validation` or `dry-schema` to your `Gemfile`.\n\nThen call `contract` with a contract or schema defined previously:\n\n```rb\nCreateOrdersSchema = Dry::Schema.Params do\n  required(:orders).array(:hash) do\n    required(:name).filled(:string)\n    optional(:volume).maybe(:integer, lt?: 9)\n  end\nend\n\n# ...\n\ncontract CreateOrdersSchema\n```\n\nor with a block, using the [schema definition syntax](https://dry-rb.org/gems/dry-schema/1.13/#quick-start):\n\n```rb\ncontract do\n  required(:orders).array(:hash) do\n    required(:name).filled(:string)\n    optional(:volume).maybe(:integer, lt?: 9)\n  end\nend\n```\n\nThe latter will define a coercing schema (`Dry::Schema.Params`). When using the former approach, it's up to you to decide whether the input will need coercing.\n\nThe `params` and `contract` declarations can also be used together in the same API, e.g. to describe different parts of a nested namespace for an endpoint.\n\n## Headers\n\n### Request\nRequest headers are available through the `headers` helper or from `env` in their original form.\n\n```ruby\nget do\n  error!('Unauthorized', 401) unless headers['Secret-Password'] == 'swordfish'\nend\n```\n\n```ruby\nget do\n  error!('Unauthorized', 401) unless env['HTTP_SECRET_PASSWORD'] == 'swordfish'\nend\n```\n\n#### Header Case Handling\n\nThe above example may have been requested as follows:\n\n``` shell\ncurl -H \"secret_PassWord: swordfish\" ...\n```\n\nThe header name will have been normalized for you.\n\n- In the `header` helper names will be coerced into a downcased kebab case as `secret-password` if using Rack 3.\n- In the `header` helper names will be coerced into a capitalized kebab case as `Secret-PassWord` if using Rack < 3.\n- In the `env` collection they appear in all uppercase, in snake case, and prefixed with 'HTTP_' as `HTTP_SECRET_PASSWORD`\n\nThe header name will have been normalized per HTTP standards defined in [RFC2616 Section 4.2](https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2) regardless of what is being sent by a client.\n\n### Response\n\nYou can set a response header with `header` inside an API.\n\n```ruby\nheader 'X-Robots-Tag', 'noindex'\n```\n\nWhen raising `error!`, pass additional headers as arguments. Additional headers will be merged with headers set before `error!` call.\n\n```ruby\nerror! 'Unauthorized', 401, 'X-Error-Detail' => 'Invalid token.'\n```\n\n## Routes\n\nTo define routes you can use the `route` method or the shorthands for the HTTP verbs. To define a route that accepts any route set to `:any`.\nParts of the path that are denoted with a colon will be interpreted as route parameters.\n\n```ruby\nroute :get, 'status' do\nend\n\n# is the same as\n\nget 'status' do\nend\n\n# is the same as\n\nget :status do\nend\n\n# is NOT the same as\n\nget ':status' do # this makes params[:status] available\nend\n\n# This will make both params[:status_id] and params[:id] available\n\nget 'statuses/:status_id/reviews/:id' do\nend\n```\n\nTo declare a namespace that prefixes all routes within, use the `namespace` method. `group`, `resource`, `resources` and `segment` are aliases to this method. Any endpoints within will share their parent context as well as any configuration done in the namespace context.\n\nThe `route_param` method is a convenient method for defining a parameter route segment. If you define a type, it will add a validation for this parameter.\n\n```ruby\nroute_param :id, type: Integer do\n  get 'status' do\n  end\nend\n\n# is the same as\n\nnamespace ':id' do\n  params do\n    requires :id, type: Integer\n  end\n\n  get 'status' do\n  end\nend\n```\n\nOptionally, you can define requirements for your named route parameters using regular expressions on namespace or endpoint. The route will match only if all requirements are met.\n\n```ruby\nget ':id', requirements: { id: /[0-9]*/ } do\n  Status.find(params[:id])\nend\n\nnamespace :outer, requirements: { id: /[0-9]*/ } do\n  get :id do\n  end\n\n  get ':id/edit' do\n  end\nend\n```\n\n## Helpers\n\nYou can define helper methods that your endpoints can use with the `helpers` macro by either giving a block or an array of modules.\n\n```ruby\nmodule StatusHelpers\n  def user_info(user)\n    \"#{user} has statused #{user.statuses} status(s)\"\n  end\nend\n\nmodule HttpCodesHelpers\n  def unauthorized\n    401\n  end\nend\n\nclass API < Grape::API\n  # define helpers with a block\n  helpers do\n    def current_user\n      User.find(params[:user_id])\n    end\n  end\n\n  # or mix in an array of modules\n  helpers StatusHelpers, HttpCodesHelpers\n\n  before do\n    error!('Access Denied', unauthorized) unless current_user\n  end\n\n  get 'info' do\n    # helpers available in your endpoint and filters\n    user_info(current_user)\n  end\nend\n```\n\nYou can define reusable `params` using `helpers`.\n\n```ruby\nclass API < Grape::API\n  helpers do\n    params :pagination do\n      optional :page, type: Integer\n      optional :per_page, type: Integer\n    end\n  end\n\n  desc 'Get collection'\n  params do\n    use :pagination # aliases: includes, use_scope\n  end\n  get do\n    Collection.page(params[:page]).per(params[:per_page])\n  end\nend\n```\n\nYou can also define reusable `params` using shared helpers.\n\n```ruby\nmodule SharedParams\n  extend Grape::API::Helpers\n\n  params :period do\n    optional :start_date\n    optional :end_date\n  end\n\n  params :pagination do\n    optional :page, type: Integer\n    optional :per_page, type: Integer\n  end\nend\n\nclass API < Grape::API\n  helpers SharedParams\n\n  desc 'Get collection.'\n  params do\n    use :period, :pagination\n  end\n\n  get do\n    Collection\n      .from(params[:start_date])\n      .to(params[:end_date])\n      .page(params[:page])\n      .per(params[:per_page])\n  end\nend\n```\n\nHelpers support blocks that can help set default values. The following API can return a collection sorted by `id` or `created_at` in `asc` or `desc` order.\n\n```ruby\nmodule SharedParams\n  extend Grape::API::Helpers\n\n  params :order do |options|\n    optional :order_by, type: Symbol, values: options[:order_by], default: options[:default_order_by]\n    optional :order, type: Symbol, values: %i(asc desc), default: options[:default_order]\n  end\nend\n\nclass API < Grape::API\n  helpers SharedParams\n\n  desc 'Get a sorted collection.'\n  params do\n    use :order, order_by: %i(id created_at), default_order_by: :created_at, default_order: :asc\n  end\n\n  get do\n    Collection.send(params[:order], params[:order_by])\n  end\nend\n```\n\n## Path Helpers\n\nIf you need methods for generating paths inside your endpoints, please see the [grape-route-helpers](https://github.com/reprah/grape-route-helpers) gem.\n\n## Parameter Documentation\n\nYou can attach additional documentation to `params` using a `documentation` hash.\n\n```ruby\nparams do\n  optional :first_name, type: String, documentation: { example: 'Jim' }\n  requires :last_name, type: String, documentation: { example: 'Smith' }\nend\n```\n\nIf documentation isn't needed (for instance, it is an internal API), documentation can be disabled.\n\n```ruby\nclass API < Grape::API\n  do_not_document!\n\n  # endpoints...\nend\n```\n\nIn this case, Grape won't create objects related to documentation which are retained in RAM forever.\n\n## Cookies\n\nYou can set, get and delete your cookies very simply using `cookies` method.\n\n```ruby\nclass API < Grape::API\n  get 'status_count' do\n    cookies[:status_count] ||= 0\n    cookies[:status_count] += 1\n    { status_count: cookies[:status_count] }\n  end\n\n  delete 'status_count' do\n    { status_count: cookies.delete(:status_count) }\n  end\nend\n```\n\nUse a hash-based syntax to set more than one value.\n\n```ruby\ncookies[:status_count] = {\n  value: 0,\n  expires: Time.tomorrow,\n  domain: '.twitter.com',\n  path: '/'\n}\n\ncookies[:status_count][:value] +=1\n```\n\nDelete a cookie with `delete`.\n\n```ruby\ncookies.delete :status_count\n```\n\nSpecify an optional path.\n\n```ruby\ncookies.delete :status_count, path: '/'\n```\n\n## HTTP Status Code\n\nBy default Grape returns a 201 for `POST`-Requests, 204 for `DELETE`-Requests that don't return any content, and 200 status code for all other Requests.\nYou can use `status` to query and set the actual HTTP Status Code\n\n```ruby\npost do\n  status 202\n\n  if status == 200\n     # do some thing\n  end\nend\n```\n\nYou can also use one of status codes symbols that are provided by [Rack utils](http://www.rubydoc.info/github/rack/rack/Rack/Utils#HTTP_STATUS_CODES-constant)\n\n```ruby\npost do\n  status :no_content\nend\n```\n\n## Redirecting\n\nYou can redirect to a new url temporarily (302) or permanently (301).\n\n```ruby\nredirect '/statuses'\n```\n\n```ruby\nredirect '/statuses', permanent: true\n```\n\n## Recognizing Path\n\nYou can recognize the endpoint matched with given path.\n\nThis API returns an instance of `Grape::Endpoint`.\n\n```ruby\nclass API < Grape::API\n  get '/statuses' do\n  end\nend\n\nAPI.recognize_path '/statuses'\n```\n\nSince version `2.1.0`, the `recognize_path` method takes into account the parameters type to determine which endpoint should match with given path.\n\n```ruby\nclass Books < Grape::API\n  resource :books do\n    route_param :id, type: Integer do\n      # GET /books/:id\n      get do\n        #...\n      end\n    end\n\n    resource :share do\n      # POST /books/share\n      post do\n      # ....\n      end\n    end\n  end\nend\n\nAPI.recognize_path '/books/1' # => /books/:id\nAPI.recognize_path '/books/share' # => /books/share\nAPI.recognize_path '/books/other' # => nil\n```\n\n\n## Allowed Methods\n\nWhen you add a `GET` route for a resource, a route for the `HEAD` method will also be added automatically. You can disable this behavior with `do_not_route_head!`.\n\n``` ruby\nclass API < Grape::API\n  do_not_route_head!\n\n  get '/example' do\n    # only responds to GET\n  end\nend\n```\n\nWhen you add a route for a resource, a route for the `OPTIONS` method will also be added. The response to an OPTIONS request will include an \"Allow\" header listing the supported methods. If the resource has `before` and `after` callbacks they will be executed, but no other callbacks will run.\n\n```ruby\nclass API < Grape::API\n  get '/rt_count' do\n    { rt_count: current_user.rt_count }\n  end\n\n  params do\n    requires :value, type: Integer, desc: 'Value to add to the rt count.'\n  end\n  put '/rt_count' do\n    current_user.rt_count += params[:value].to_i\n    { rt_count: current_user.rt_count }\n  end\nend\n```\n\n``` shell\ncurl -v -X OPTIONS http://localhost:3000/rt_count\n\n> OPTIONS /rt_count HTTP/1.1\n>\n< HTTP/1.1 204 No Content\n< Allow: OPTIONS, GET, PUT\n```\n\nYou can disable this behavior with `do_not_route_options!`.\n\nIf a request for a resource is made with an unsupported HTTP method, an HTTP 405 (Method Not Allowed) response will be returned. If the resource has `before` callbacks they will be executed, but no other callbacks will run.\n\n``` shell\ncurl -X DELETE -v http://localhost:3000/rt_count/\n\n> DELETE /rt_count/ HTTP/1.1\n> Host: localhost:3000\n>\n< HTTP/1.1 405 Method Not Allowed\n< Allow: OPTIONS, GET, PUT\n```\n\n## Raising Exceptions\n\nYou can abort the execution of an API method by raising errors with `error!`.\n\n```ruby\nerror! 'Access Denied', 401\n```\n\nAnything that responds to `#to_s` can be given as a first argument to `error!`.\n\n```ruby\nerror! :not_found, 404\n```\n\nYou can also return JSON formatted objects by raising error! and passing a hash instead of a message.\n\n```ruby\nerror!({ error: 'unexpected error', detail: 'missing widget' }, 500)\n```\n\nYou can set additional headers for the response. They will be merged with headers set before `error!` call.\n\n```ruby\nerror!('Something went wrong', 500, 'X-Error-Detail' => 'Invalid token.')\n```\n\nYou can present documented errors with a Grape entity using the the [grape-entity](https://github.com/ruby-grape/grape-entity) gem.\n\n```ruby\nmodule API\n  class Error < Grape::Entity\n    expose :code\n    expose :message\n  end\nend\n```\n\nThe following example specifies the entity to use in the `http_codes` definition.\n\n```ruby\ndesc 'My Route' do\n failure [[408, 'Unauthorized', API::Error]]\nend\nerror!({ message: 'Unauthorized' }, 408)\n```\n\nThe following example specifies the presented entity explicitly in the error message.\n\n```ruby\ndesc 'My Route' do\n failure [[408, 'Unauthorized']]\nend\nerror!({ message: 'Unauthorized', with: API::Error }, 408)\n```\n\n### Default Error HTTP Status Code\n\nBy default Grape returns a 500 status code from `error!`. You can change this with `default_error_status`.\n\n``` ruby\nclass API < Grape::API\n  default_error_status 400\n  get '/example' do\n    error! 'This should have http status code 400'\n  end\nend\n```\n\n### Handling 404\n\nFor Grape to handle all the 404s for your API, it can be useful to use a catch-all.\nIn its simplest form, it can be like:\n\n```ruby\nroute :any, '*path' do\n  error! # or something else\nend\n```\n\nIt is very crucial to __define this endpoint at the very end of your API__, as it literally accepts every request.\n\n## Exception Handling\n\nGrape can be told to rescue all `StandardError` exceptions and return them in the API format.\n\n```ruby\nclass Twitter::API < Grape::API\n  rescue_from :all\nend\n```\n\nThis mimics [default `rescue` behaviour](https://ruby-doc.org/core/StandardError.html) when an exception type is not provided.\nAny other exception should be rescued explicitly, see [below](#exceptions-that-should-be-rescued-explicitly).\n\nGrape can also rescue from all exceptions and still use the built-in exception handing.\nThis will give the same behavior as `rescue_from :all` with the addition that Grape will use the exception handling defined by all Exception classes that inherit `Grape::Exceptions::Base`.\n\nThe intent of this setting is to provide a simple way to cover the most common exceptions and return any unexpected exceptions in the API format.\n\n```ruby\nclass Twitter::API < Grape::API\n  rescue_from :grape_exceptions\nend\n```\n\nIf you want to customize the shape of grape exceptions returned to the user, to match your `:all` handler for example, you can pass a block to `rescue_from :grape_exceptions`.\n\n```ruby\nrescue_from :grape_exceptions do |e|\n  error!(e, e.status)\nend\n```\n\nYou can also rescue specific exceptions.\n\n```ruby\nclass Twitter::API < Grape::API\n  rescue_from ArgumentError, UserDefinedError\nend\n```\n\nIn this case ```UserDefinedError``` must be inherited from ```StandardError```.\n\nNotice that you could combine these two approaches (rescuing custom errors takes precedence). For example, it's useful for handling all exceptions except Grape validation errors.\n\n```ruby\nclass Twitter::API < Grape::API\n  rescue_from Grape::Exceptions::ValidationErrors do |e|\n    error!(e, 400)\n  end\n\n  rescue_from :all\nend\n```\n\nThe error format will match the request format. See \"Content-Types\" below.\n\nCustom error formatters for existing and additional types can be defined with a proc.\n\n```ruby\nclass Twitter::API < Grape::API\n  error_formatter :txt, ->(message, backtrace, options, env, original_exception) {\n    \"error: #{message} from #{backtrace}\"\n  }\nend\n```\n\nYou can also use a module or class.\n\n```ruby\nmodule CustomFormatter\n  def self.call(message, backtrace, options, env, original_exception)\n    { message: message, backtrace: backtrace }\n  end\nend\n\nclass Twitter::API < Grape::API\n  error_formatter :custom, CustomFormatter\nend\n```\n\nYou can rescue all exceptions with a code block. The `error!` wrapper automatically sets the default error code and content-type.\n\n```ruby\nclass Twitter::API < Grape::API\n  rescue_from :all do |e|\n    error!(\"rescued from #{e.class.name}\")\n  end\nend\n```\n\nOptionally, you can set the format, status code and headers.\n\n```ruby\nclass Twitter::API < Grape::API\n  format :json\n  rescue_from :all do |e|\n    error!({ error: 'Server error.' }, 500, { 'Content-Type' => 'text/error' })\n  end\nend\n```\n\nYou can also rescue all exceptions with a code block and handle the Rack response at the lowest level.\n\n```ruby\nclass Twitter::API < Grape::API\n  rescue_from :all do |e|\n    Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' })\n  end\nend\n```\n\nOr rescue specific exceptions.\n\n```ruby\nclass Twitter::API < Grape::API\n  rescue_from ArgumentError do |e|\n    error!(\"ArgumentError: #{e.message}\")\n  end\n\n  rescue_from NoMethodError do |e|\n    error!(\"NoMethodError: #{e.message}\")\n  end\nend\n```\n\nBy default, `rescue_from` will rescue the exceptions listed and all their subclasses.\n\nAssume you have the following exception classes defined.\n\n```ruby\nmodule APIErrors\n  class ParentError < StandardError; end\n  class ChildError < ParentError; end\nend\n```\n\nThen the following `rescue_from` clause will rescue exceptions of type `APIErrors::ParentError` and its subclasses (in this case `APIErrors::ChildError`).\n\n```ruby\nrescue_from APIErrors::ParentError do |e|\n    error!({\n      error: \"#{e.class} error\",\n      message: e.message\n    }, e.status)\nend\n```\n\nTo only rescue the base exception class, set `rescue_subclasses: false`.\nThe code below will rescue exceptions of type `RuntimeError` but _not_ its subclasses.\n\n```ruby\nrescue_from RuntimeError, rescue_subclasses: false do |e|\n    error!({\n      status: e.status,\n      message: e.message,\n      errors: e.errors\n    }, e.status)\nend\n```\n\nHelpers are also available inside `rescue_from`.\n\n```ruby\nclass Twitter::API < Grape::API\n  format :json\n  helpers do\n    def server_error!\n      error!({ error: 'Server error.' }, 500, { 'Content-Type' => 'text/error' })\n    end\n  end\n\n  rescue_from :all do |e|\n    server_error!\n  end\nend\n```\n\nThe `rescue_from` handler must return a `Rack::Response` object, call `error!`, or raise an exception (either the original exception or another custom one). The exception raised in `rescue_from` will be handled outside Grape. For example, if you mount Grape in Rails, the exception will be handle by [Rails Action Controller](https://guides.rubyonrails.org/action_controller_overview.html#rescue).\n\nAlternately, use the `with` option in `rescue_from` to specify a method or a `proc`.\n\n```ruby\nclass Twitter::API < Grape::API\n  format :json\n  helpers do\n    def server_error!\n      error!({ error: 'Server error.' }, 500, { 'Content-Type' => 'text/error' })\n    end\n  end\n\n  rescue_from :all,          with: :server_error!\n  rescue_from ArgumentError, with: -> { Rack::Response.new('rescued with a method', 400) }\nend\n```\n\nInside the `rescue_from` block, the environment of the original controller method(`.self` receiver) is accessible through the `#context` method.\n\n```ruby\nclass Twitter::API < Grape::API\n  rescue_from :all do |e|\n    user_id = context.params[:user_id]\n    error!(\"error for #{user_id}\")\n  end\nend\n```\n\n#### Rescuing exceptions inside namespaces\n\nYou could put `rescue_from` clauses inside a namespace and they will take precedence over ones\ndefined in the root scope:\n\n```ruby\nclass Twitter::API < Grape::API\n  rescue_from ArgumentError do |e|\n    error!(\"outer\")\n  end\n\n  namespace :statuses do\n    rescue_from ArgumentError do |e|\n      error!(\"inner\")\n    end\n    get do\n      raise ArgumentError.new\n    end\n  end\nend\n```\n\nHere `'inner'` will be result of handling occurred `ArgumentError`.\n\n#### Unrescuable Exceptions\n\n`Grape::Exceptions::InvalidVersionHeader`, which is raised when the version in the request header doesn't match the currently evaluated version for the endpoint, will _never_ be rescued from a `rescue_from` block (even a `rescue_from :all`) This is because Grape relies on Rack to catch that error and try the next versioned-route for cases where there exist identical Grape endpoints with different versions.\n\n#### Exceptions that should be rescued explicitly\n\nAny exception that is not subclass of `StandardError` should be rescued explicitly.\nUsually it is not a case for an application logic as such errors point to problems in Ruby runtime.\nThis is following [standard recommendations for exceptions handling](https://ruby-doc.org/core/Exception.html).\n\n## Logging\n\n`Grape::API` provides a `logger` method which by default will return an instance of the `Logger` class from Ruby's standard library.\n\nTo log messages from within an endpoint, you need to define a helper to make the logger available in the endpoint context.\n\n```ruby\nclass API < Grape::API\n  helpers do\n    def logger\n      API.logger\n    end\n  end\n  post '/statuses' do\n    logger.info \"#{current_user} has statused\"\n  end\nend\n```\n\nTo change the logger level.\n\n```ruby\nclass API < Grape::API\n  self.logger.level = Logger::INFO\nend\n```\n\nYou can also set your own logger.\n\n```ruby\nclass MyLogger\n  def warning(message)\n    puts \"this is a warning: #{message}\"\n  end\nend\n\nclass API < Grape::API\n  logger MyLogger.new\n  helpers do\n    def logger\n      API.logger\n    end\n  end\n  get '/statuses' do\n    logger.warning \"#{current_user} has statused\"\n  end\nend\n```\n\nFor similar to Rails request logging try the [grape_logging](https://github.com/aserafin/grape_logging) or [grape-middleware-logger](https://github.com/ridiculous/grape-middleware-logger) gems.\n\n## API Formats\n\nYour API can declare which content-types to support by using `content_type`. If you do not specify any, Grape will support _XML_, _JSON_, _BINARY_, and _TXT_ content-types. The default format is `:txt`; you can change this with `default_format`. Essentially, the two APIs below are equivalent.\n\n```ruby\nclass Twitter::API < Grape::API\n  # no content_type declarations, so Grape uses the defaults\nend\n\nclass Twitter::API < Grape::API\n  # the following declarations are equivalent to the defaults\n\n  content_type :xml, 'application/xml'\n  content_type :json, 'application/json'\n  content_type :binary, 'application/octet-stream'\n  content_type :txt, 'text/plain'\n\n  default_format :txt\nend\n```\n\nIf you declare any `content_type` whatsoever, the Grape defaults will be overridden. For example, the following API will only support the `:xml` and `:rss` content-types, but not `:txt`, `:json`, or `:binary`. Importantly, this means the `:txt` default format is not supported! So, make sure to set a new `default_format`.\n\n```ruby\nclass Twitter::API < Grape::API\n  content_type :xml, 'application/xml'\n  content_type :rss, 'application/xml+rss'\n\n  default_format :xml\nend\n```\n\nSerialization takes place automatically. For example, you do not have to call `to_json` in each JSON API endpoint implementation. The response format (and thus the automatic serialization) is determined in the following order:\n* Use the file extension, if specified. If the file is .json, choose the JSON format.\n* Use the value of the `format` parameter in the query string, if specified.\n* Use the format set by the `format` option, if specified.\n* Attempt to find an acceptable format from the `Accept` header.\n* Use the default format, if specified by the `default_format` option.\n* Default to `:txt`.\n\nFor example, consider the following API.\n\n```ruby\nclass MultipleFormatAPI < Grape::API\n  content_type :xml, 'application/xml'\n  content_type :json, 'application/json'\n\n  default_format :json\n\n  get :hello do\n    { hello: 'world' }\n  end\nend\n```\n\n* `GET /hello` (with an `Accept: */*` header) does not have an extension or a `format` parameter, so it will respond with JSON (the default format).\n* `GET /hello.xml` has a recognized extension, so it will respond with XML.\n* `GET /hello?format=xml` has a recognized `format` parameter, so it will respond with XML.\n* `GET /hello.xml?format=json` has a recognized extension (which takes precedence over the `format` parameter), so it will respond with XML.\n* `GET /hello.xls` (with an `Accept: */*` header) has an extension, but that extension is not recognized, so it will respond with JSON (the default format).\n* `GET /hello.xls` with an `Accept: application/xml` header has an unrecognized extension, but the `Accept` header corresponds to a recognized format, so it will respond with XML.\n* `GET /hello.xls` with an `Accept: text/plain` header has an unrecognized extension *and* an unrecognized `Accept` header, so it will respond with JSON (the default format).\n\nYou can override this process explicitly by calling `api_format` in the API itself.\nFor example, the following API will let you upload arbitrary files and return their contents as an attachment with the correct MIME type.\n\n```ruby\nclass Twitter::API < Grape::API\n  post 'attachment' do\n    filename = params[:file][:filename]\n    content_type MIME::Types.type_for(filename)[0].to_s\n    api_format :binary # there's no formatter for :binary, data will be returned \"as is\"\n    header 'Content-Disposition', \"attachment; filename*=UTF-8''#{CGI.escape(filename)}\"\n    params[:file][:tempfile].read\n  end\nend\n```\n\nYou can have your API only respond to a single format with `format`. If you use this, the API will **not** respond to file extensions other than specified in `format`. For example, consider the following API.\n\n```ruby\nclass SingleFormatAPI < Grape::API\n  format :json\n\n  get :hello do\n    { hello: 'world' }\n  end\nend\n```\n\n* `GET /hello` will respond with JSON.\n* `GET /hello.json` will respond with JSON.\n* `GET /hello.xml`, `GET /hello.foobar`, or *any* other extension will respond with an HTTP 404 error code.\n* `GET /hello?format=xml` will respond with an HTTP 406 error code, because the XML format specified by the request parameter is not supported.\n* `GET /hello` with an `Accept: application/xml` header will still respond with JSON, since it could not negotiate a recognized content-type from the headers and JSON is the effective default.\n\nThe formats apply to parsing, too. The following API will only respond to the JSON content-type and will not parse any other input than `application/json`, `application/x-www-form-urlencoded`, `multipart/form-data`, `multipart/related` and `multipart/mixed`. All other requests will fail with an HTTP 406 error code.\n\n```ruby\nclass Twitter::API < Grape::API\n  format :json\nend\n```\n\nWhen the content-type is omitted, Grape will return a 406 error code unless `default_format` is specified.\nThe following API will try to parse any data without a content-type using a JSON parser.\n\n```ruby\nclass Twitter::API < Grape::API\n  format :json\n  default_format :json\nend\n```\n\nIf you combine `format` with `rescue_from :all`, errors will be rendered using the same format.\nIf you do not want this behavior, set the default error formatter with `default_error_formatter`.\n\n```ruby\nclass Twitter::API < Grape::API\n  format :json\n  content_type :txt, 'text/plain'\n  default_error_formatter :txt\nend\n```\n\nCustom formatters for existing and additional types can be defined with a proc.\n\n```ruby\nclass Twitter::API < Grape::API\n  content_type :xls, 'application/vnd.ms-excel'\n  formatter :xls, ->(object, env) { object.to_xls }\nend\n```\n\nYou can also use a module or class.\n\n```ruby\nmodule XlsFormatter\n  def self.call(object, env)\n    object.to_xls\n  end\nend\n\nclass Twitter::API < Grape::API\n  content_type :xls, 'application/vnd.ms-excel'\n  formatter :xls, XlsFormatter\nend\n```\n\nBuilt-in formatters are the following.\n\n* `:json`: use object's `to_json` when available, otherwise call `MultiJson.dump`\n* `:xml`: use object's `to_xml` when available, usually via `MultiXml`\n* `:txt`: use object's `to_txt` when available, otherwise `to_s`\n* `:serializable_hash`: use object's `serializable_hash` when available, otherwise fallback to `:json`\n* `:binary`: data will be returned \"as is\"\n\nIf a body is present in a request to an API, with a Content-Type header value that is of an unsupported type a \"415 Unsupported Media Type\" error code will be returned by Grape.\n\nResponse statuses that indicate no content as defined by [Rack](https://github.com/rack) [here](https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L567) will bypass serialization and the body entity - though there should be none - will not be modified.\n\n### JSONP\n\nGrape supports JSONP via [Rack::JSONP](https://github.com/rack/rack-contrib), part of the [rack-contrib](https://github.com/rack/rack-contrib) gem. Add `rack-contrib` to your `Gemfile`.\n\n```ruby\nrequire 'rack/contrib'\n\nclass API < Grape::API\n  use Rack::JSONP\n  format :json\n  get '/' do\n    'Hello World'\n  end\nend\n```\n\n### CORS\n\nGrape supports CORS via [Rack::CORS](https://github.com/cyu/rack-cors), part of the [rack-cors](https://github.com/cyu/rack-cors) gem. Add `rack-cors` to your `Gemfile`, then use the middleware in your config.ru file.\n\n```ruby\nrequire 'rack/cors'\n\nuse Rack::Cors do\n  allow do\n    origins '*'\n    resource '*', headers: :any, methods: :get\n  end\nend\n\nrun Twitter::API\n\n```\n\n## Content-type\n\nContent-type is set by the formatter. You can override the content-type of the response at runtime by setting the `Content-Type` header.\n\n```ruby\nclass API < Grape::API\n  get '/home_timeline_js' do\n    content_type 'application/javascript'\n    \"var statuses = ...;\"\n  end\nend\n```\n\n## API Data Formats\n\nGrape accepts and parses input data sent with the POST and PUT methods as described in the Parameters section above. It also supports custom data formats. You must declare additional content-types via `content_type` and optionally supply a parser via `parser` unless a parser is already available within Grape to enable a custom format. Such a parser can be a function or a class.\n\nWith a parser, parsed data is available \"as-is\" in `env['api.request.body']`.\nWithout a parser, data is available \"as-is\" and in `env['api.request.input']`.\n\nThe following example is a trivial parser that will assign any input with the \"text/custom\" content-type to `:value`. The parameter will be available via `params[:value]` inside the API call.\n\n```ruby\nmodule CustomParser\n  def self.call(object, env)\n    { value: object.to_s }\n  end\nend\n```\n\n```ruby\ncontent_type :txt, 'text/plain'\ncontent_type :custom, 'text/custom'\nparser :custom, CustomParser\n\nput 'value' do\n  params[:value]\nend\n```\n\nYou can invoke the above API as follows.\n\n```\ncurl -X PUT -d 'data' 'http://localhost:9292/value' -H Content-Type:text/custom -v\n```\n\nYou can disable parsing for a content-type with `nil`. For example, `parser :json, nil` will disable JSON parsing altogether. The request data is then available as-is in `env['api.request.body']`.\n\n## JSON and XML Processors\n\nGrape uses `JSON` and `ActiveSupport::XmlMini` for JSON and XML parsing by default. It also detects and supports [multi_json](https://github.com/intridea/multi_json) and [multi_xml](https://github.com/sferik/multi_xml). Adding those gems to your Gemfile and requiring them will enable them and allow you to swap the JSON and XML back-ends.\n\n## RESTful Model Representations\n\nGrape supports a range of ways to present your data with some help from a generic `present` method, which accepts two arguments: the object to be presented and the options associated with it. The options hash may include `:with`, which defines the entity to expose.\n\n### Grape Entities\n\nAdd the [grape-entity](https://github.com/ruby-grape/grape-entity) gem to your Gemfile.\nPlease refer to the [grape-entity documentation](https://github.com/ruby-grape/grape-entity/blob/master/README.md)\nfor more details.\n\nThe following example exposes statuses.\n\n```ruby\nmodule API\n  module Entities\n    class Status < Grape::Entity\n      expose :user_name\n      expose :text, documentation: { type: 'string', desc: 'Status update text.' }\n      expose :ip, if: { type: :full }\n      expose :user_type, :user_id, if: ->(status, options) { status.user.public? }\n      expose :digest do |status, options|\n        Digest::MD5.hexdigest(status.txt)\n      end\n      expose :replies, using: API::Status, as: :replies\n    end\n  end\n\n  class Statuses < Grape::API\n    version 'v1'\n\n    desc 'Statuses index' do\n      params: API::Entities::Status.documentation\n    end\n    get '/statuses' do\n      statuses = Status.all\n      type = current_user.admin? ? :full : :default\n      present statuses, with: API::Entities::Status, type: type\n    end\n  end\nend\n```\n\nYou can use entity documentation directly in the params block with `using: Entity.documentation`.\n\n```ruby\nmodule API\n  class Statuses < Grape::API\n    version 'v1'\n\n    desc 'Create a status'\n    params do\n      requires :all, except: [:ip], using: API::Entities::Status.documentation.except(:id)\n    end\n    post '/status' do\n      Status.create! params\n    end\n  end\nend\n```\n\nYou can present with multiple entities using an optional Symbol argument.\n\n```ruby\n  get '/statuses' do\n    statuses = Status.all.page(1).per(20)\n    present :total_page, 10\n    present :per_page, 20\n    present :statuses, statuses, with: API::Entities::Status\n  end\n```\n\nThe response will be\n\n```\n  {\n    total_page: 10,\n    per_page: 20,\n    statuses: []\n  }\n```\n\nIn addition to separately organizing entities, it may be useful to put them as namespaced classes underneath the model they represent.\n\n```ruby\nclass Status\n  def entity\n    Entity.new(self)\n  end\n\n  class Entity < Grape::Entity\n    expose :text, :user_id\n  end\nend\n```\n\nIf you organize your entities this way, Grape will automatically detect the `Entity` class and use it to present your models. In this example, if you added `present Status.new` to your endpoint, Grape will automatically detect that there is a `Status::Entity` class and use that as the representative entity. This can still be overridden by using the `:with` option or an explicit `represents` call.\n\nYou can present `hash` with `Grape::Presenters::Presenter` to keep things consistent.\n\n```ruby\nget '/users' do\n  present { id: 10, name: :dgz }, with: Grape::Presenters::Presenter\nend\n````\nThe response will be\n\n```ruby\n{\n  id:   10,\n  name: 'dgz'\n}\n```\n\nIt has the same result with\n\n```ruby\nget '/users' do\n  present :id, 10\n  present :name, :dgz\nend\n```\n\n### Hypermedia and Roar\n\nYou can use [Roar](https://github.com/apotonick/roar) to render HAL or Collection+JSON with the help of [grape-roar](https://github.com/ruby-grape/grape-roar), which defines a custom JSON formatter and enables presenting entities with Grape's `present` keyword.\n\n### Rabl\n\nYou can use [Rabl](https://github.com/nesquena/rabl) templates with the help of the [grape-rabl](https://github.com/ruby-grape/grape-rabl) gem, which defines a custom Grape Rabl formatter.\n\n### Active Model Serializers\n\nYou can use [Active Model Serializers](https://github.com/rails-api/active_model_serializers) serializers with the help of the [grape-active_model_serializers](https://github.com/jrhe/grape-active_model_serializers) gem, which defines a custom Grape AMS formatter.\n\n## Sending Raw or No Data\n\nIn general, use the binary format to send raw data.\n\n```ruby\nclass API < Grape::API\n  get '/file' do\n    content_type 'application/octet-stream'\n    File.binread 'file.bin'\n  end\nend\n```\n\nYou can set the response body explicitly with `body`.\n\n```ruby\nclass API < Grape::API\n  get '/' do\n    content_type 'text/plain'\n    body 'Hello World'\n    # return value ignored\n  end\nend\n```\n\nUse `body false` to return `204 No Content` without any data or content-type.\n\nIf you want to empty the body with an HTTP status code other than `204 No Content`, you can override the status code after specifying `body false` as follows\n\n```ruby\nclass API < Grape::API\n  get '/' do\n    body false\n    status 304\n  end\nend\n```\n\nYou can also set the response to a file with `sendfile`. This works with the [Rack::Sendfile](https://www.rubydoc.info/gems/rack/Rack/Sendfile) middleware to optimally send the file through your web server software.\n\n```ruby\nclass API < Grape::API\n  get '/' do\n    sendfile '/path/to/file'\n  end\nend\n```\n\nTo stream a file in chunks use `stream`\n\n```ruby\nclass API < Grape::API\n  get '/' do\n    stream '/path/to/file'\n  end\nend\n```\n\nIf you want to stream non-file data use the `stream` method and a `Stream` object.\nThis is an object that responds to `each` and yields for each chunk to send to the client.\nEach chunk will be sent as it is yielded instead of waiting for all of the content to be available.\n\n```ruby\nclass MyStream\n  def each\n    yield 'part 1'\n    yield 'part 2'\n    yield 'part 3'\n  end\nend\n\nclass API < Grape::API\n  get '/' do\n    stream MyStream.new\n  end\nend\n```\n\n## Authentication\n\n### Basic Auth\n\nGrape has built-in Basic authentication (the given `block` is executed in the context of the current `Endpoint`).  Authentication applies to the current namespace and any children, but not parents.\n\n```ruby\nhttp_basic do |username, password|\n  # verify user's password here\n  # IMPORTANT: make sure you use a comparison method which isn't prone to a timing attack\nend\n```\n\n### Register custom middleware for authentication\n\nGrape can use custom Middleware for authentication. How to implement these Middleware have a look at `Rack::Auth::Basic` or similar implementations.\n\nFor registering a Middleware you need the following options:\n\n* `label` - the name for your authenticator to use it later\n* `MiddlewareClass` - the MiddlewareClass to use for authentication\n* `option_lookup_proc` - A Proc with one Argument to lookup the options at runtime (return value is an `Array` as Parameter for the Middleware).\n\nExample:\n\n```ruby\n\nGrape::Middleware::Auth::Strategies.add(:my_auth, AuthMiddleware, ->(options) { [options[:realm]] } )\n\n\nauth :my_auth, { realm: 'Test Api'} do |credentials|\n  # lookup the user's password here\n  { 'user1' => 'password1' }[username]\nend\n\n```\n\nUse [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper), [warden-oauth2](https://github.com/opperator/warden-oauth2) or [rack-oauth2](https://github.com/nov/rack-oauth2) for OAuth2 support.\n\nYou can access the controller params, headers, and helpers through the context with the `#context` method inside any auth middleware inherited from `Grape::Middleware::Auth::Base`.\n\n## Describing and Inspecting an API\n\nGrape routes can be reflected at runtime. This can notably be useful for generating documentation.\n\nGrape exposes arrays of API versions and compiled routes. Each route contains a `prefix`, `version`, `namespace`, `method` and `params`. You can add custom route settings to the route metadata with `route_setting`.\n\n```ruby\nclass TwitterAPI < Grape::API\n  version 'v1'\n  desc 'Includes custom settings.'\n  route_setting :custom, key: 'value'\n  get do\n\n  end\nend\n```\n\nExamine the routes at runtime.\n\n```ruby\nTwitterAPI::versions # yields [ 'v1', 'v2' ]\nTwitterAPI::routes # yields an array of Grape::Route objects\nTwitterAPI::routes[0].version # => 'v1'\nTwitterAPI::routes[0].description # => 'Includes custom settings.'\nTwitterAPI::routes[0].settings[:custom] # => { key: 'value' }\n```\n\nNote that `Route#route_xyz` methods have been deprecated since 0.15.0 and removed since 2.0.1.\n\nPlease use `Route#xyz` instead.\n\nNote that difference of `Route#options` and `Route#settings`.\n\nThe `options` can be referred from your route, it should be set by specifying key and value on verb methods such as `get`, `post` and `put`.\nThe `settings` can also be referred from your route, but it should be set by specifying key and value on `route_setting`.\n\n## Current Route and Endpoint\n\nIt's possible to retrieve the information about the current route from within an API call with `route`.\n\n```ruby\nclass MyAPI < Grape::API\n  desc 'Returns a description of a parameter.'\n  params do\n    requires :id, type: Integer, desc: 'Identity.'\n  end\n  get 'params/:id' do\n    route.params[params[:id]] # yields the parameter description\n  end\nend\n```\n\nThe current endpoint responding to the request is `self` within the API block or `env['api.endpoint']` elsewhere. The endpoint has some interesting properties, such as `source` which gives you access to the original code block of the API implementation. This can be particularly useful for building a logger middleware.\n\n```ruby\nclass ApiLogger < Grape::Middleware::Base\n  def before\n    file = env['api.endpoint'].source.source_location[0]\n    line = env['api.endpoint'].source.source_location[1]\n    logger.debug \"[api] #{file}:#{line}\"\n  end\nend\n```\n\n## Before, After and Finally\n\nBlocks can be executed before or after every API call, using `before`, `after`, `before_validation` and `after_validation`.\nIf the API fails the `after` call will not be triggered, if you need code to execute for sure use the `finally`.\n\nBefore and after callbacks execute in the following order:\n\n1. `before`\n2. `before_validation`\n3. _validations_\n4. `after_validation` (upon successful validation)\n5. _the API call_ (upon successful validation)\n6. `after` (upon successful validation and API call)\n7. `finally` (always)\n\nSteps 4, 5 and 6 only happen if validation succeeds.\n\nIf a request for a resource is made with an unsupported HTTP method (returning HTTP 405) only `before` callbacks will be executed.  The remaining callbacks will be bypassed.\n\nIf a request for a resource is made that triggers the built-in `OPTIONS` handler, only `before` and `after` callbacks will be executed.  The remaining callbacks will be bypassed.\n\nFor example, using a simple `before` block to set a header.\n\n```ruby\nbefore do\n  header 'X-Robots-Tag', 'noindex'\nend\n```\n\nYou can ensure a block of code runs after every request (including failures) with `finally`:\n\n```ruby\nfinally do\n  # this code will run after every request (successful or failed)\nend\n```\n\n**Namespaces**\n\nCallbacks apply to each API call within and below the current namespace:\n\n```ruby\nclass MyAPI < Grape::API\n  get '/' do\n    \"root - #{@blah}\"\n  end\n\n  namespace :foo do\n    before do\n      @blah = 'blah'\n    end\n\n    get '/' do\n      \"root - foo - #{@blah}\"\n    end\n\n    namespace :bar do\n      get '/' do\n        \"root - foo - bar - #{@blah}\"\n      end\n    end\n  end\nend\n```\n\nThe behavior is then:\n\n```bash\nGET /           # 'root - '\nGET /foo        # 'root - foo - blah'\nGET /foo/bar    # 'root - foo - bar - blah'\n```\n\nParams on a `namespace` (or whichever alias you are using) will also be available when using `before_validation` or `after_validation`:\n\n```ruby\nclass MyAPI < Grape::API\n  params do\n    requires :blah, type: Integer\n  end\n  resource ':blah' do\n    after_validation do\n      # if we reach this point validations will have passed\n      @blah = declared(params, include_missing: false)[:blah]\n    end\n\n    get '/' do\n      @blah.class\n    end\n  end\nend\n```\n\nThe behavior is then:\n\n```bash\nGET /123        # 'Integer'\nGET /foo        # 400 error - 'blah is invalid'\n```\n\n**Versioning**\n\nWhen a callback is defined within a version block, it's only called for the routes defined in that block.\n\n```ruby\nclass Test < Grape::API\n  resource :foo do\n    version 'v1', :using => :path do\n      before do\n        @output ||= 'v1-'\n      end\n      get '/' do\n        @output += 'hello'\n      end\n    end\n\n    version 'v2', :using => :path do\n      before do\n        @output ||= 'v2-'\n      end\n      get '/' do\n        @output += 'hello'\n      end\n    end\n  end\nend\n```\n\nThe behavior is then:\n\n```bash\nGET /foo/v1       # 'v1-hello'\nGET /foo/v2       # 'v2-hello'\n```\n\n**Altering Responses**\n\nUsing `present` in any callback allows you to add data to a response:\n\n```ruby\nclass MyAPI < Grape::API\n  format :json\n\n  after_validation do\n    present :name, params[:name] if params[:name]\n  end\n\n  get '/greeting' do\n    present :greeting, 'Hello!'\n  end\nend\n```\n\nThe behavior is then:\n\n```bash\nGET /greeting              # {\"greeting\":\"Hello!\"}\nGET /greeting?name=Alan    # {\"name\":\"Alan\",\"greeting\":\"Hello!\"}\n```\n\nInstead of altering a response, you can also terminate and rewrite it from any callback using `error!`, including `after`. This will cause all subsequent steps in the process to not be called. **This includes the actual api call and any callbacks**\n\n## Anchoring\n\nGrape by default anchors all request paths, which means that the request URL should match from start to end to match, otherwise a `404 Not Found` is returned. However, this is sometimes not what you want, because it is not always known upfront what can be expected from the call. This is because Rack-mount by default anchors requests to match from the start to the end, or not at all.\nRails solves this problem by using a `anchor: false` option in your routes.\nIn Grape this option can be used as well when a method is defined.\n\nFor instance when your API needs to get part of an URL, for instance:\n\n```ruby\nclass TwitterAPI < Grape::API\n  namespace :statuses do\n    get '/(*:status)', anchor: false do\n\n    end\n  end\nend\n```\n\nThis will match all paths starting with '/statuses/'. There is one caveat though: the `params[:status]` parameter only holds the first part of the request url.\nLuckily this can be circumvented by using the described above syntax for path specification and using the `PATH_INFO` Rack environment variable, using `env['PATH_INFO']`. This will hold everything that comes after the '/statuses/' part.\n\n## Instance Variables\n\nYou can use instance variables to pass information across the various stages of a request. An instance variable set within a `before` validator is accessible within the endpoint's code and can also be utilized within the `rescue_from` handler.\n\n```ruby\nclass TwitterAPI < Grape::API\n  before do\n    @var = 1\n  end\n\n  get '/' do\n    puts @var # => 1\n    raise\n  end\n\n  rescue_from :all do\n    puts @var # => 1\n  end\nend\n```\n\nThe values of instance variables cannot be shared among various endpoints within the same API. This limitation arises due to Grape generating a new instance for each request made. Consequently, instance variables set within an endpoint during one request differ from those set during a subsequent request, as they exist within separate instances.\n\n```ruby\nclass TwitterAPI < Grape::API\n  get '/first' do\n    @var = 1\n    puts @var # => 1\n  end\n\n  get '/second' do\n    puts @var # => nil\n  end\nend\n```\n\n## Using Custom Middleware\n\n### Grape Middleware\n\nYou can make a custom middleware by using `Grape::Middleware::Base`.\nIt's inherited from some grape official middlewares in fact.\n\nFor example, you can write a middleware to log application exception.\n\n```ruby\nclass LoggingError < Grape::Middleware::Base\n  def after\n    return unless @app_response && @app_response[0] == 500\n    env['rack.logger'].error(\"Raised error on #{env['PATH_INFO']}\")\n  end\nend\n```\n\nYour middleware can overwrite application response as follows, except error case.\n\n```ruby\nclass Overwriter < Grape::Middleware::Base\n  def after\n    [200, { 'Content-Type' => 'text/plain' }, ['Overwritten.']]\n  end\nend\n```\n\nYou can add your custom middleware with `use`, that push the middleware onto the stack, and you can also control where the middleware is inserted using `insert`, `insert_before` and `insert_after`.\n\n```ruby\nclass CustomOverwriter < Grape::Middleware::Base\n  def after\n    [200, { 'Content-Type' => 'text/plain' }, [@options[:message]]]\n  end\nend\n\n\nclass API < Grape::API\n  use Overwriter\n  insert_before Overwriter, CustomOverwriter, message: 'Overwritten again.'\n  insert 0, CustomOverwriter, message: 'Overwrites all other middleware.'\n\n  get '/' do\n  end\nend\n```\n\nYou can access the controller params, headers, and helpers through the context with the `#context` method inside any middleware inherited from `Grape::Middleware::Base`.\n\n### Rails Middleware\n\nNote that when you're using Grape mounted on Rails you don't have to use Rails middleware because it's already included into your middleware stack.\nYou only have to implement the helpers to access the specific `env` variable.\n\nIf you are using a custom application that is inherited from `Rails::Application` and need to insert a new middleware among the ones initiated via Rails, you will need to register it manually in your custom application class.\n\n```ruby\nclass Company::Application < Rails::Application\n  config.middleware.insert_before(Rack::Attack, Middleware::ApiLogger)\nend\n```\n\n### Remote IP\n\nBy default you can access remote IP with `request.ip`. This is the remote IP address implemented by Rack. Sometimes it is desirable to get the remote IP [Rails-style](http://stackoverflow.com/questions/10997005/whats-the-difference-between-request-remote-ip-and-request-ip-in-rails) with `ActionDispatch::RemoteIp`.\n\nAdd `gem 'actionpack'` to your Gemfile and `require 'action_dispatch/middleware/remote_ip.rb'`. Use the middleware in your API and expose a `client_ip` helper. See [this documentation](http://api.rubyonrails.org/classes/ActionDispatch/RemoteIp.html) for additional options.\n\n```ruby\nclass API < Grape::API\n  use ActionDispatch::RemoteIp\n\n  helpers do\n    def client_ip\n      env['action_dispatch.remote_ip'].to_s\n    end\n  end\n\n  get :remote_ip do\n    { ip: client_ip }\n  end\nend\n```\n\n## Writing Tests\n\n### Writing Tests with Rack\n\nUse `rack-test` and define your API as `app`.\n\n#### RSpec\n\nYou can test a Grape API with RSpec by making HTTP requests and examining the response.\n\n```ruby\n\n\ndescribe Twitter::API do\n  include Rack::Test::Methods\n\n  def app\n    Twitter::API\n  end\n\n  context 'GET /api/statuses/public_timeline' do\n    it 'returns an empty array of statuses' do\n      get '/api/statuses/public_timeline'\n      expect(last_response.status).to eq(200)\n      expect(JSON.parse(last_response.body)).to eq []\n    end\n  end\n  context 'GET /api/statuses/:id' do\n    it 'returns a status by id' do\n      status = Status.create!\n      get \"/api/statuses/#{status.id}\"\n      expect(last_response.body).to eq status.to_json\n    end\n  end\nend\n```\n\nThere's no standard way of sending arrays of objects via an HTTP GET, so POST JSON data and specify the correct content-type.\n\n```ruby\ndescribe Twitter::API do\n  context 'POST /api/statuses' do\n    it 'creates many statuses' do\n      statuses = [{ text: '...' }, { text: '...'}]\n      post '/api/statuses', statuses.to_json, 'CONTENT_TYPE' => 'application/json'\n      expect(last_response.body).to eq 201\n    end\n  end\nend\n```\n\n#### Airborne\n\nYou can test with other RSpec-based frameworks, including [Airborne](https://github.com/brooklynDev/airborne), which uses `rack-test` to make requests.\n\n```ruby\nrequire 'airborne'\n\nAirborne.configure do |config|\n  config.rack_app = Twitter::API\nend\n\ndescribe Twitter::API do\n  context 'GET /api/statuses/:id' do\n    it 'returns a status by id' do\n      status = Status.create!\n      get \"/api/statuses/#{status.id}\"\n      expect_json(status.as_json)\n    end\n  end\nend\n```\n\n#### MiniTest\n\n```ruby\nrequire 'test_helper'\n\nclass Twitter::APITest < MiniTest::Test\n  include Rack::Test::Methods\n\n  def app\n    Twitter::API\n  end\n\n  def test_get_api_statuses_public_timeline_returns_an_empty_array_of_statuses\n    get '/api/statuses/public_timeline'\n    assert last_response.ok?\n    assert_equal [], JSON.parse(last_response.body)\n  end\n\n  def test_get_api_statuses_id_returns_a_status_by_id\n    status = Status.create!\n    get \"/api/statuses/#{status.id}\"\n    assert_equal status.to_json, last_response.body\n  end\nend\n```\n\n### Writing Tests with Rails\n\n#### RSpec\n\n```ruby\ndescribe Twitter::API do\n  context 'GET /api/statuses/public_timeline' do\n    it 'returns an empty array of statuses' do\n      get '/api/statuses/public_timeline'\n      expect(response.status).to eq(200)\n      expect(JSON.parse(response.body)).to eq []\n    end\n  end\n  context 'GET /api/statuses/:id' do\n    it 'returns a status by id' do\n      status = Status.create!\n      get \"/api/statuses/#{status.id}\"\n      expect(response.body).to eq status.to_json\n    end\n  end\nend\n```\n\nIn Rails, HTTP request tests would go into the `spec/requests` group. You may want your API code to go into `app/api` - you can match that layout under `spec` by adding the following in `spec/rails_helper.rb`.\n\n```ruby\nRSpec.configure do |config|\n  config.include RSpec::Rails::RequestExampleGroup, type: :request, file_path: /spec\\/api/\nend\n```\n\n#### MiniTest\n\n```ruby\nclass Twitter::APITest < ActiveSupport::TestCase\n  include Rack::Test::Methods\n\n  def app\n    Rails.application\n  end\n\n  test 'GET /api/statuses/public_timeline returns an empty array of statuses' do\n    get '/api/statuses/public_timeline'\n    assert last_response.ok?\n    assert_equal [], JSON.parse(last_response.body)\n  end\n\n  test 'GET /api/statuses/:id returns a status by id' do\n    status = Status.create!\n    get \"/api/statuses/#{status.id}\"\n    assert_equal status.to_json, last_response.body\n  end\nend\n```\n\n### Stubbing Helpers\n\nBecause helpers are mixed in based on the context when an endpoint is defined, it can be difficult to stub or mock them for testing. The `Grape::Endpoint.before_each` method can help by allowing you to define behavior on the endpoint that will run before every request.\n\n```ruby\ndescribe 'an endpoint that needs helpers stubbed' do\n  before do\n    Grape::Endpoint.before_each do |endpoint|\n      allow(endpoint).to receive(:helper_name).and_return('desired_value')\n    end\n  end\n\n  after do\n    Grape::Endpoint.before_each nil\n  end\n\n  it 'stubs the helper' do\n\n  end\nend\n```\n\n## Reloading API Changes in Development\n\n### Reloading in Rack Applications\n\nUse [grape-reload](https://github.com/AlexYankee/grape-reload).\n\n### Reloading in Rails Applications\n\n#### Rails 7+ (Zeitwerk)\n\nRails 7+ uses [Zeitwerk](https://github.com/fxn/zeitwerk) as the default autoloader, which automatically handles reloading of code in development mode without any additional configuration.\n\nIf your API files are in `app/api`, Zeitwerk will automatically autoload and reload them. No additional configuration is needed.\n\nIf you encounter issues with reloading, ensure that:\n\n1. Your API files follow Zeitwerk naming conventions (file names should match class names).\n2. The `config.enable_reloading` is set to `true` in `config/environments/development.rb` (this is the default).\n\nFor troubleshooting autoloading issues, have a look at the [Rails documentation](https://guides.rubyonrails.org/autoloading_and_reloading_constants.html#troubleshooting).\n\nSee the [Rails Autoloading and Reloading Constants guide](https://guides.rubyonrails.org/autoloading_and_reloading_constants.html) for more information.\n\n#### Rails 6 and Earlier\n\nFor Rails versions before 7, you need to configure reloading manually.\n\nAdd API paths to `config/application.rb`.\n\n```ruby\n# Auto-load API and its subdirectories\nconfig.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')\nconfig.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]\n```\n\nCreate `config/initializers/reload_api.rb`.\n\n```ruby\nif Rails.env.development?\n  ActiveSupport::Dependencies.explicitly_unloadable_constants << 'Twitter::API'\n\n  api_files = Dir[Rails.root.join('app', 'api', '**', '*.rb')]\n  api_reloader = ActiveSupport::FileUpdateChecker.new(api_files) do\n    Rails.application.reload_routes!\n  end\n  ActiveSupport::Reloader.to_prepare do\n    api_reloader.execute_if_updated\n  end\nend\n```\n\nSee [StackOverflow #3282655](http://stackoverflow.com/questions/3282655/ruby-on-rails-3-reload-lib-directory-for-each-request/4368838#4368838) for more information.\n\n## Performance Monitoring\n\n### Active Support Instrumentation\n\nGrape has built-in support for [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) which provides simple hook points to instrument key parts of your application.\n\n\n#### Hook Points\n\nThe following hook points are currently supported:\n\n##### endpoint_run.grape\n\nThe main execution of an endpoint, includes filters and rendering.\n\n* *endpoint* - The endpoint instance\n\n##### endpoint_render.grape\n\nThe execution of the main content block of the endpoint.\n\n* *endpoint* - The endpoint instance\n\n##### endpoint_run_filters.grape\n\n* *endpoint* - The endpoint instance\n* *filters* - The filters being executed\n* *type* - The type of filters (before, before_validation, after_validation, after)\n\n##### endpoint_run_validators.grape\n\nThe execution of validators.\n\n* *endpoint* - The endpoint instance\n* *validators* - The validators being executed\n* *request* - The request being validated\n\n##### format_response.grape\n\nSerialization or template rendering.\n\n* *env* - The request environment\n* *formatter* - The formatter object (e.g., `Grape::Formatter::Json`)\n\nSee the [ActiveSupport::Notifications documentation](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) for information on how to subscribe to these events.\n\n#### Subscribe to Hooks\n\nOnce subscribed to the instrumentation, you can intercept the events reported above.\n\n```ruby\nActiveSupport::Notifications.subscribe(/<api_path>/) do |name, start, finish, id, payload|\n  # your code to intercept the notification\nend\n```\n\nThe request data, the API’s internal data, and the response can be retrieved from the payload.\n\nYou can use `payload.fetch(:endpoint)` or directly `payload[:endpoint]`.\n\nThe `:endpoint` contains the data currently being processed, and access to attributes such as `body`, `request`, `params`, `headers`, `cookies` and `response_cookies`\n\nFor example, `payload[:endpoint].body` provides the current state of the response.\n\n```ruby\nActiveSupport::Notifications.subscribe(/v1/) do |name, start, finish, id, payload|\n  hook_record = {\n    hook: name\n    status: payload[:env]&.dig(\"api.endpoint\")&.status\n    format: payload[:env]&.dig(\"api.format\")\n    body: payload[:endpoint]&.body\n    duration: (finish - start) * 1000\n  }\n  # your code to save the notification\nend\n```\n\n### Monitoring Products\n\nGrape integrates with following third-party tools:\n\n* **New Relic** - [built-in support](https://docs.newrelic.com/docs/agents/ruby-agent/frameworks/grape-instrumentation) from v3.10.0 of the official [newrelic_rpm](https://github.com/newrelic/rpm) gem, also [newrelic-grape](https://github.com/xinminlabs/newrelic-grape) gem\n* **Librato Metrics** - [grape-librato](https://github.com/seanmoon/grape-librato) gem\n* **Rails Performance** - [rails_performance](https://github.com/igorkasyanchuk/rails_performance) gem\n* **[Skylight](https://www.skylight.io/)** - [skylight](https://github.com/skylightio/skylight-ruby) gem, [documentation](https://docs.skylight.io/grape/)\n* **[AppSignal](https://www.appsignal.com)** - [appsignal-ruby](https://github.com/appsignal/appsignal-ruby) gem, [documentation](http://docs.appsignal.com/getting-started/supported-frameworks.html#grape)\n* **[ElasticAPM](https://www.elastic.co/products/apm)** - [elastic-apm](https://github.com/elastic/apm-agent-ruby) gem, [documentation](https://www.elastic.co/guide/en/apm/agent/ruby/3.x/getting-started-rack.html#getting-started-grape)\n* **[Datadog APM](https://docs.datadoghq.com/tracing/)** - [ddtrace](https://github.com/datadog/dd-trace-rb) gem, [documentation](https://docs.datadoghq.com/tracing/setup_overview/setup/ruby/#grape)\n\n## Contributing to Grape\n\nGrape is work of hundreds of contributors. You're encouraged to submit pull requests, propose features and discuss issues.\n\nSee [CONTRIBUTING](CONTRIBUTING.md).\n\n## Security\n\nSee [SECURITY](SECURITY.md) for details.\n\n## License\n\nMIT License. See [LICENSE](LICENSE) for details.\n\n## Copyright\n\nCopyright (c) 2010-2020 Michael Bleigh, Intridea Inc. and Contributors.\n"
  },
  {
    "path": "RELEASING.md",
    "content": "Releasing Grape\n===============\n\nThere're no particular rules about when to release Grape. Release bug fixes frequently, features not so frequently and breaking API changes rarely.\n\n### Release\n\nRun tests, check that all tests succeed locally.\n\n```\nbundle install\nrake\n```\n\nDouble-check that the [last build succeeded](https://github.com/ruby-grape/grape/actions) for all supported platforms.\n\nThose with r/w permissions to the [master Grape repository](https://github.com/ruby-grape/grape) generally have large Grape-based projects. Point one to Grape HEAD and run all your API tests to catch any obvious regressions.\n\n```\ngem grape, github: 'ruby-grape/grape'\n```\n\nModify the \"Stable Release\" section in [README.md](README.md). Change the text to reflect that this is going to be the documentation for a stable release. Remove references to the previous release of Grape. Keep the file open, you'll have to undo this change after the release.\n\n```\n## Stable Release\n\nYou're reading the documentation for the stable release of Grape, 0.6.0.\n```\n\nChange \"Next Release\" in [CHANGELOG.md](CHANGELOG.md) to the new version.\n\n```\n#### 0.6.0 (2013/9/16)\n```\n\nRemove the line with \"Your contribution here.\", since there will be no more contributions to this release.\n\nCommit your changes.\n\n```\ngit add README.md CHANGELOG.md\ngit commit -m \"Preparing for release, 0.6.0.\"\ngit push origin master\n```\n\nRelease.\n\n```\n$ rake release\n\ngrape 0.6.0 built to pkg/grape-0.6.0.gem.\nTagged v0.6.0.\nPushed git commits and tags.\nPushed grape 0.6.0 to rubygems.org.\n```\n\n### Prepare for the Next Version\n\nModify the \"Stable Release\" section in [README.md](README.md). Change the text to reflect that this is going to be the next release.\n\n```\n## Stable Release\n\nYou're reading the documentation for the next release of Grape, which should be 0.6.1.\nThe current stable release is [0.6.0](https://github.com/ruby-grape/grape/blob/v0.6.0/README.md).\n```\n\nAdd the next release to [CHANGELOG.md](CHANGELOG.md).\n\n```\n### 0.6.1 (Next)\n\n#### Features\n\n* Your contribution here.\n\n#### Fixes\n\n* Your contribution here.\n```\n\nBump the minor version in lib/grape/version.rb.\n\n```ruby\nmodule Grape\n  VERSION = '0.6.1'.freeze\nend\n```\n\nCommit your changes.\n\n```\ngit add CHANGELOG.md README.md lib/grape/version.rb\ngit commit -m \"Preparing for next development iteration, 0.6.1.\"\ngit push origin master\n```\n\n### Make an Announcement\n\nMake an announcement on the [ruby-grape@googlegroups.com](mailto:ruby-grape@googlegroups.com) mailing list. The general format is as follows.\n\n```\nGrape 0.6.0 has been released.\n\nThere were 8 contributors to this release, not counting documentation.\n\nPlease note the breaking API change in ...\n\n[copy/paste CHANGELOG here]\n\n```\n"
  },
  {
    "path": "Rakefile",
    "content": "# frozen_string_literal: true\n\nrequire('rubygems')\nrequire('bundler')\nBundler.setup(:default, :test, :development)\n\nBundler::GemHelper.install_tasks\n\nrequire('rspec/core/rake_task')\nRSpec::Core::RakeTask.new(:spec) do |spec|\n  spec.pattern = 'spec/**/*_spec.rb'\n  spec.exclude_pattern = 'spec/integration/**/*_spec.rb'\nend\n\nRSpec::Core::RakeTask.new(:rcov) do |spec|\n  spec.pattern = 'spec/**/*_spec.rb'\n  spec.rcov = true\nend\n\ntask(:spec)\n\nrequire('rainbow/ext/string') unless String.respond_to?(:color)\n\nrequire('rubocop/rake_task')\nRuboCop::RakeTask.new\n\ntask(default: %i[rubocop spec])\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nVersion 2.2 or newer is currently supported.\n\n## Reporting a Vulnerability\n\nTidelift acts as the security contact for this open-source project. To make a report, please email the security team at security@tidelift.com. See [tidelift.com/security](https://tidelift.com/security) for details and more options.\n\n"
  },
  {
    "path": "UPGRADING.md",
    "content": "Upgrading Grape\n===============\n\n### Upgrading to >= 3.2\n\n#### `with` now uses keyword arguments\n\nThe `with` DSL method now uses `**opts` instead of a positional hash. Calls using bare keyword syntax are unaffected:\n\n```ruby\n# still works\nwith(type: String, documentation: { in: 'body' }) { ... }\n```\n\nHowever, passing an explicit hash literal will now raise an `ArgumentError`:\n\n```ruby\n# raises ArgumentError\nwith({ type: String }) { ... }\n```\n\nSee [#2663](https://github.com/ruby-grape/grape/pull/2663) for more information.\n\n#### Custom validators: use `translate` instead of `I18n` directly\n\n`Grape::Util::Translation` is now included in `Grape::Validations::Validators::Base`. Custom validators that previously called `I18n.t` or `I18n.translate` directly should switch to the `translate`, which provides the same `:en` fallback logic used by all built-in validators.\n\nKey points:\n- `scope` defaults to `'grape.errors.messages'` — no need to specify it for standard error message keys.\n- Interpolation variables are passed directly to I18n.\n- `format` is no longer needed — `translate` returns the fully interpolated string.\n\n```ruby\n# Before\nraise Grape::Exceptions::Validation.new(\n  params: [@scope.full_name(attr_name)],\n  message: format(I18n.t(:my_key, scope: 'grape.errors.messages'), min: 2, max: 10)\n)\n\n# After\nraise Grape::Exceptions::Validation.new(\n  params: [@scope.full_name(attr_name)],\n  message: translate(:my_key, min: 2, max: 10)\n)\n```\n\nSee [#2662](https://github.com/ruby-grape/grape/pull/2662) for more information.\n\n### Upgrading to >= 3.1\n\n#### Explicit kwargs for `namespace` and `route_param`\n\nThe `API#namespace` and `route_param` methods are now defined with `**options` instead of `options = {}`. In addtion, `requirements` in explicitly defined so it's not in `options` anymore. You can still call `requirements` like before but `options[:requirements]` will be empty. For `route_param`, `type` is also an explicit parameter so it's not in `options` anymore. See [#2647](https://github.com/ruby-grape/grape/pull/2647) for more information.\n\n#### ParamsBuilder Grape::Extensions\n\nDeprecated [ParamsBuilder's extensions](https://github.com/ruby-grape/grape/blob/master/UPGRADING.md#params-builder) have been removed.\n\n#### Enhanced API compile!\n\nEndpoints are now \"compiled\" instead of lazy loaded. Historically, when calling `YourAPI.compile!` in `config.ru` (or just receiving the first API call), only routing was compiled see [Grape::Router#compile!](https://github.com/ruby-grape/grape/blob/bf90e95c3b17c415c944363b1c07eb9727089ee7/lib/grape/router.rb#L41-L54) and endpoints were lazy loaded. Now, it's part of the API compilation. See [#2645](https://github.com/ruby-grape/grape/pull/2645) for more information.\n\n### Upgrading to >= 3.0.0\n\n#### Ruby 3+ Argument Delegation Modernization\n\nGrape has been modernized to use Ruby 3+'s preferred argument delegation patterns. This change replaces `args.extract_options!` with explicit `**kwargs` parameters throughout the codebase.\n\n- All DSL methods now use explicit keyword arguments (`**kwargs`) instead of extracting options from mixed argument lists\n- Method signatures are now more explicit and follow Ruby 3+ best practices\n- The `active_support/core_ext/array/extract_options` dependency has been removed\n\nThis is a modernization effort that improves code quality while maintaining full backward compatibility.\n\nSee [#2618](https://github.com/ruby-grape/grape/pull/2618) for more information.\n\n#### Configuration API Migration from ActiveSupport::Configurable to Dry::Configurable\n\nGrape has migrated from `ActiveSupport::Configurable` to `Dry::Configurable` for its configuration system since its [deprecated](https://github.com/rails/rails/blob/1cdd190a25e483b65f1f25bbd0f13a25d696b461/activesupport/lib/active_support/configurable.rb#L3-L7).\n\nSee [#2617](https://github.com/ruby-grape/grape/pull/2617) for more information.\n\n#### Endpoint execution simplified and `return` deprecated\n\nExecuting a endpoint's block has been simplified and calling `return` in it has been deprecated. Use `next` instead.\n\nSee [#2577](https://github.com/ruby-grape/grape/pull/2577) for more information.\n\n#### Old Deprecations Clean Up\n\n- `rack_response` has been removed in favor of using `error!`.\n- `Grape::Exceptions::MissingGroupType` and `Grape::Exceptions::UnsupportedGroupType` aliases `MissingGroupTypeError and `UnsupportedGroupType` have been removed.\n- `Grape::Validations::Base` has been removed in favor of `Grape::Validations::Validators::Base`.\n\nSee [2573](https://github.com/ruby-grape/grape/pull/2573) for more information.\n\n### Upgrading to >= 2.4.0\n\n#### Grape::Middleware::Auth::Base\n`type` is now validated at compile time and will raise a `Grape::Exceptions::UnknownAuthStrategy` if unknown.\n\n#### Grape::Middleware::Base\n\n- Second argument `options` is now a double splat (**) instead of single splat (*). If you're redefining `initialize` in your middleware and/or calling `super` in it, you might have to adapt the signature and the `super` call. Also, you might have to remove `{}` if you're pass `options` as a literal `Hash` or add `**` if you're using a variable.\n- `Grape::Middleware::Helpers` has been removed. The equivalent method `context` is now part of `Grape::Middleware::Base`.\n\n#### Grape::Http::Headers, Grape::Util::Lazy::Object\n\nBoth have been removed. See [2554](https://github.com/ruby-grape/grape/pull/2554).\nHere are the notable changes:\n\n- Constants like `HTTP_ACCEPT` have been replaced by their literal value.\n- `SUPPORTED_METHODS` has been moved to `Grape` module.\n- `HTTP_HEADERS` has been moved to `Grape::Request` and renamed `KNOWN_HEADERS`. The last has been refreshed with new headers, and it's not lazy anymore.\n- `SUPPORTED_METHODS_WITHOUT_OPTIONS` and `find_supported_method` have been removed.\n\n#### Grape::Middleware::Base\n\n- Constant `TEXT_HTML` has been removed in favor of using literal string 'text/html'.\n- `rack_request` and `query_params` have been added. Feel free to call these in your middlewares.\n\n#### Params Builder\n\n- Passing a class to `build_with` or `Grape.config.param_builder` has been deprecated in favor of a symbolized short_name. See `SHORTNAME_LOOKUP` in [params_builder](lib/grape/params_builder.rb).\n- Including Grape's extensions like `Grape::Extensions::Hashie::Mash::ParamBuilder` has been deprecated in favor of using `build_with` at the route level.\n\n#### Accept Header Negotiation Harmonized\n\n[Accept](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept) header is now fully interpreted through `Rack::Utils.best_q_match` which is following [RFC2616 14.1](https://datatracker.ietf.org/doc/html/rfc2616#section-14.1). Since [Grape 2.1.0](https://github.com/ruby-grape/grape/blob/master/CHANGELOG.md#210-20240615), the [header versioning strategy](https://github.com/ruby-grape/grape?tab=readme-ov-file#header) was adhering to it, but `Grape::Middleware::Formatter` never did.\n\nYour API might act differently since it will strictly follow the [RFC2616 14.1](https://datatracker.ietf.org/doc/html/rfc2616#section-14.1) when interpreting the `Accept` header. Here are the differences:\n\n##### Invalid or missing quality ranking\nThe following used to yield `application/xml` and now will yield `application/json` as the preferred media type:\n- `application/json;q=invalid,application/xml;q=0.5`\n- `application/json,application/xml;q=1.0`\n\nFor the invalid case, the value `invalid` was automatically `to_f` and `invalid.to_f` equals `0.0`. Now, since it doesn't match [Rack's regex](https://github.com/rack/rack/blob/3-1-stable/lib/rack/utils.rb#L138), its interpreted as non provided and its quality ranking equals 1.0.\n\nFor the non provided case, 1.0 was automatically assigned and in a case of multiple best matches, the first was returned based on Ruby's sort_by `quality`. Now, 1.0 is still assigned and the last is returned in case of multiple best matches. See [Rack's implementation](https://github.com/rack/rack/blob/e8f47608668d507e0f231a932fa37c9ca551c0a5/lib/rack/utils.rb#L167) of the RFC.\n\n##### Considering the closest generic when vendor tree\nExcluding the [header versioning strategy](https://github.com/ruby-grape/grape?tab=readme-ov-file#header), whenever a media type with the [vendor tree](https://datatracker.ietf.org/doc/html/rfc6838#section-3.2) leading facet `vnd.` like `application/vnd.api+json` was provided, Grape would also consider its closest generic when negotiating. In that case, `application/json` was added to the negotiation. Now, it will just consider the provided media types without considering any closest generics, and you'll need to [register](https://github.com/ruby-grape/grape?tab=readme-ov-file#api-formats) it.\nYou can find the official vendor tree registrations on [IANA](https://www.iana.org/assignments/media-types/media-types.xhtml)\n\n#### Custom Validators\n\nIf you now receive an error of `'Grape::Validations.require_validator': unknown validator: your_custom_validation (Grape::Exceptions::UnknownValidator)` after upgrading to 2.4.0 then you will need to ensure that you require the `your_custom_validation` file before your Grape API code is loaded.\n\nSee [2533](https://github.com/ruby-grape/grape/issues/2533) for more information.\n\n### Upgrading to >= 2.3.0\n\n### `content_type` vs `api.format` inside API\n\nBefore 2.3.0, `content_type` had priority over `env['api.format']` when set in an API, which was incorrect. The priority has been flipped and `env['api.format']` will be checked first.\nIn addition, the function `api_format` has been added. Instead of setting `env['api.format']` directly, you can call `api_format`.\nSee [#2506](https://github.com/ruby-grape/grape/pull/2506) for more information.\n\n#### Remove Deprecated Methods and Options\n\n- Deprecated `file` method has been removed. Use `send_file` or `stream`.\nSee [#2500](https://github.com/ruby-grape/grape/pull/2500) for more information.\n\n- The `except` and `proc` options have been removed from the `values` validator. Use `except_values` validator or assign `proc` directly to `values`.\nSee [#2501](https://github.com/ruby-grape/grape/pull/2501) for more information.\n\n- `Passing an options hash and a block to 'desc'` deprecation has been removed. Move all hash options to block instead.\nSee [#2502](https://github.com/ruby-grape/grape/pull/2502) for more information.\n\n### Upgrading to >= 2.2.0\n\n### `Length` validator\n\nAfter Grape 2.2.0, `length` validator will only take effect for parameters with types that support `#length` method, will not throw `ArgumentError` exception.\n\nSee [#2464](https://github.com/ruby-grape/grape/pull/2464) for more information.\n\n### Upgrading to >= 2.1.0\n\n#### Optional Builder\n\nThe `builder` gem dependency has been made optional as it's only used when generating XML. If your code does, add `builder` to your `Gemfile`.\n\nSee [#2445](https://github.com/ruby-grape/grape/pull/2445) for more information.\n\n#### Deep Merging of Parameter Attributes\n\nGrape now uses `deep_merge` to combine parameter attributes within the `with` method. Previously, attributes defined at the parameter level would override those defined at the group level.\nWith deep merge, attributes are now combined, allowing for more detailed and nuanced API specifications.\n\nFor example:\n\n```ruby\nwith(documentation: { in: 'body' }) do\n  optional :vault, documentation: { default: 33 }\nend\n```\n\nBefore it was equivalent to:\n\n```ruby\noptional :vault, documentation: { default: 33 }\n```\n\nAfter it is an equivalent of:\n\n```ruby\noptional :vault, documentation: { in: 'body', default: 33 }\n```\n\nSee [#2432](https://github.com/ruby-grape/grape/pull/2432) for more information.\n\n#### Zeitwerk\n\nGrape's autoloader has been updated and it's now based on [Zeitwerk](https://github.com/fxn/zeitwerk).\nIf you MP (Monkey Patch) some files and you're not following the [file structure](https://github.com/fxn/zeitwerk?tab=readme-ov-file#file-structure), you might end up with a Zeitwerk error.\n\nSee [#2363](https://github.com/ruby-grape/grape/pull/2363) for more information.\n\n#### Changes in rescue_from\n\nThe `rack_response` method has been deprecated and the `error_response` method has been removed. Use `error!` instead.\n\nSee [#2414](https://github.com/ruby-grape/grape/pull/2414) for more information.\n\n#### Change in parameters precedence\n\nWhen using together with `Grape::Extensions::Hash::ParamBuilder`, `route_param` takes higher precedence over a regular parameter defined with same name, which now matches the default param builder behavior.\n\nThis was a regression introduced by [#2326](https://github.com/ruby-grape/grape/pull/2326) in Grape v1.8.0.\n\n```ruby\nGrape.configure do |config|\n  config.param_builder = Grape::Extensions::Hash::ParamBuilder\nend\n\nparams do\n  requires :foo, type: String\nend\nroute_param :foo do\n  get do\n    { value: params[:foo] }\n  end\nend\n```\n\nRequest:\n\n```bash\ncurl -X POST -H \"Content-Type: application/json\" localhost:9292/bar -d '{\"foo\": \"baz\"}'\n```\n\nResponse prior to v1.8.0:\n\n```json\n{\n  \"value\": \"bar\"\n}\n```\n\nv1.8.0..v2.0.0:\n\n```json\n{\n  \"value\": \"baz\"\n}\n```\n\nv2.1.0+:\n\n```json\n{\n  \"value\": \"bar\"\n}\n```\n\nSee [#2378](https://github.com/ruby-grape/grape/pull/2378) for details.\n\n#### Grape::Router::Route.route_xxx methods have been removed\n\n- `route_method` is accessible through `request_method`\n- `route_path` is accessible through `path`\n- Any other `route_xyz` are accessible through `options[xyz]`\n\n#### Instance variables scope\n\nDue to the changes done in [#2377](https://github.com/ruby-grape/grape/pull/2377), the instance variables defined inside each of the endpoints (or inside a `before` validator) are now accessible inside the `rescue_from`. The behavior of the instance variables was undefined until `2.1.0`.\n\nIf you were using the same variable name defined inside an endpoint or `before` validator inside a `rescue_from` handler, you need to take in mind that you can start getting different values or you can be overriding values.\n\nBefore:\n```ruby\nclass TwitterAPI < Grape::API\n  before do\n    @var = 1\n  end\n\n  get '/' do\n    puts @var # => 1\n    raise\n  end\n\n  rescue_from :all do\n    puts @var # => nil\n  end\nend\n```\n\nAfter:\n```ruby\nclass TwitterAPI < Grape::API\n  before do\n    @var = 1\n  end\n\n  get '/' do\n    puts @var # => 1\n    raise\n  end\n\n  rescue_from :all do\n    puts @var # => 1\n  end\nend\n```\n\n#### Recognizing Path\n\nGrape now considers the types of the configured `route_params` in order to determine the endpoint that matches with the performed request.\n\nSo taking into account this `Grape::API` class\n\n```ruby\nclass Books < Grape::API\n  resource :books do\n    route_param :id, type: Integer do\n      # GET /books/:id\n      get do\n        #...\n      end\n    end\n\n    resource :share do\n      # POST /books/share\n      post do\n      # ....\n      end\n    end\n  end\nend\n```\n\nBefore:\n```ruby\nAPI.recognize_path '/books/1' # => /books/:id\nAPI.recognize_path '/books/share' # => /books/:id\nAPI.recognize_path '/books/other' # => /books/:id\n```\n\nAfter:\n```ruby\nAPI.recognize_path '/books/1' # => /books/:id\nAPI.recognize_path '/books/share' # => /books/share\nAPI.recognize_path '/books/other' # => nil\n```\n\nThis implies that before this changes, when you performed `/books/other` and it matched with the `/books/:id` endpoint, you get a `400 Bad Request` response because the type of the provided `:id` param was not an `Integer`. However, after upgrading to version `2.1.0` you will get a `404 Not Found` response, because there is not a defined endpoint that matches with `/books/other`.\n\nSee [#2379](https://github.com/ruby-grape/grape/pull/2379) for more information.\n\n### Upgrading to >= 2.0.0\n\n#### Headers\n\nAs per [rack/rack#1592](https://github.com/rack/rack/issues/1592) Rack 3 is following the HTTP/2+ semantics which require header names to be lower case. To avoid compatibility issues, starting with Grape 1.9.0, headers will be cased based on what version of Rack you are using.\n\nGiven this request:\n\n```shell\ncurl -H \"Content-Type: application/json\" -H \"Secret-Password: foo\" ...\n```\n\nIf you are using Rack 3 in your application then the headers will be set to:\n\n```ruby\n{ \"content-type\" => \"application/json\", \"secret-password\" => \"foo\"}\n```\n\nThis means if you are checking for header values in your application, you would need to change your code to use downcased keys.\n\n```ruby\nget do\n  # This would use headers['Secret-Password'] in Rack < 3\n  error!('Unauthorized', 401) unless headers['secret-password'] == 'swordfish'\nend\n```\n\nSee [#2355](https://github.com/ruby-grape/grape/pull/2355) for more information.\n\n#### Digest auth deprecation\n\nDigest auth has been removed along with the deprecation of `Rack::Auth::Digest` in Rack 3.\n\nSee [#2294](https://github.com/ruby-grape/grape/issues/2294) for more information.\n\n### Upgrading to >= 1.7.0\n\n#### Exceptions renaming\n\nThe following exceptions has been renamed for consistency through exceptions naming :\n\n* `MissingGroupTypeError` => `MissingGroupType`\n* `UnsupportedGroupTypeError` => `UnsupportedGroupType`\n\nSee [#2227](https://github.com/ruby-grape/grape/pull/2227) for more information.\n\n#### Handling Multipart Limit Errors\n\nRack supports a configurable limit on the number of files created from multipart parameters (`Rack::Utils.multipart_part_limit`) and raises an error if params are received that create too many files.  If you were handling the Rack error directly, Grape now wraps that error in `Grape::Execeptions::TooManyMultipartFiles`.  Additionally, Grape will return a 413 status code if the exception goes unhandled.\n\n### Upgrading to >= 1.6.0\n\n#### Parameter renaming with :as\n\nPrior to 1.6.0 the [parameter renaming](https://github.com/ruby-grape/grape#renaming) with `:as` was directly touching the request payload ([`#params`](https://github.com/ruby-grape/grape#parameters)) while duplicating the old and the new key to be both available in the hash. This allowed clients to bypass any validation in case they knew the internal name of the parameter.  Unfortunately, in combination with [grape-swagger](https://github.com/ruby-grape/grape-swagger) the internal name (name set with `:as`) of the parameters were documented.\n\nThis behavior was fixed. Parameter renaming is now done when using the [`#declared(params)`](https://github.com/ruby-grape/grape#declared) parameters helper. This stops confusing validation/coercion behavior.\n\nHere comes an illustration of the old and new behaviour as code:\n\n```ruby\n# (1) Rename a to b, while client sends +a+\noptional :a, type: Integer, as: :b\nparams = { a: 1 }\ndeclared(params, include_missing: false)\n# expected => { b: 1 }\n# actual   => { b: 1 }\n\n# (2) Rename a to b, while client sends +b+\noptional :a, type: Integer, as: :b, values: [1, 2, 3]\nparams = { b: '5' }\ndeclared(params, include_missing: false)\n# expected => { }        (>= 1.6.0)\n# actual   => { b: '5' } (uncasted, unvalidated, <= 1.5.3)\n```\n\nAnother implication of this change is the dependent parameter resolution. Prior to 1.6.0 the following code produced a `Grape::Exceptions::UnknownParameter` because `:a` was replaced by `:b`:\n\n```ruby\nparams do\n  optional :a, as: :b\n  given :a do # (<= 1.5.3 you had to reference +:b+ here to make it work)\n    requires :c\n  end\nend\n```\n\nThis code now works without any errors, as the renaming is just an internal behaviour of the `#declared(params)` parameter helper.\n\nSee [#2189](https://github.com/ruby-grape/grape/pull/2189) for more information.\n\n### Upgrading to >= 1.5.3\n\n#### Nil value and coercion\n\nPrior to 1.2.5 version passing a `nil` value for a parameter with a custom coercer would invoke the coercer, and not passing a parameter would not invoke it.\nThis behavior was not tested or documented. Version 1.3.0 quietly changed this behavior, in that `nil` values skipped the coercion. Version 1.5.3 fixes and documents this as follows:\n\n```ruby\nclass Api < Grape::API\n  params do\n    optional :value, type: Integer, coerce_with: ->(val) { val || 0 }\n  end\n\n  get 'example' do\n     params[:my_param]\n  end\n  get '/example', params: { value: nil }\n  # 1.5.2 = nil\n  # 1.5.3 = 0\n  get '/example', params: {}\n  # 1.5.2 = nil\n  # 1.5.3 = nil\nend\n```\nSee [#2164](https://github.com/ruby-grape/grape/pull/2164) for more information.\n\n### Upgrading to >= 1.5.1\n\n#### Dependent params\n\nIf you use [dependent params](https://github.com/ruby-grape/grape#dependent-parameters) with\n`Grape::Extensions::Hash::ParamBuilder`, make sure a parameter to be dependent on is set as a Symbol.\nIf a String is given, a parameter that other parameters depend on won't be found even if it is present.\n\n_Correct_:\n```ruby\ngiven :matrix do\n  # dependent params\nend\n```\n\n_Wrong_:\n```ruby\ngiven 'matrix' do\n  # dependent params\nend\n```\n\n### Upgrading to >= 1.5.0\n\nPrior to 1.3.3, the `declared` helper would always return the complete params structure if `include_missing=true` was set. In 1.3.3 a regression was introduced such that a missing Hash with or without nested parameters would always resolve to `{}`.\n\nIn 1.5.0 this behavior is reverted, so the whole params structure will always be available via `declared`, regardless of whether any params are passed.\n\nThe following rules now apply to the `declared` helper when params are missing and `include_missing=true`:\n\n* Hash params with children will resolve to a Hash with keys for each declared child.\n* Hash params with no children will resolve to `{}`.\n* Set params will resolve to `Set.new`.\n* Array params will resolve to `[]`.\n* All other params will resolve to `nil`.\n\n#### Example\n\n```ruby\nclass Api < Grape::API\n  params do\n    optional :outer, type: Hash do\n      optional :inner, type: Hash do\n        optional :value, type: String\n      end\n    end\n  end\n  get 'example' do\n    declared(params, include_missing: true)\n  end\nend\n```\n\n```\nget '/example'\n# 1.3.3 = {}\n# 1.5.0 = {outer: {inner: {value:null}}}\n```\n\nFor more information see [#2103](https://github.com/ruby-grape/grape/pull/2103).\n\n### Upgrading to >= 1.4.0\n\n#### Reworking stream and file and un-deprecating stream like-objects\n\nPreviously in 0.16 stream-like objects were deprecated. This release restores their functionality for use-cases other than file streaming.\n\nThis release deprecated `file` in favor of `sendfile` to better document its purpose.\n\nTo deliver a file via the Sendfile support in your web server and have the Rack::Sendfile middleware enabled. See [`Rack::Sendfile`](https://www.rubydoc.info/gems/rack/Rack/Sendfile).\n```ruby\nclass API < Grape::API\n  get '/' do\n    sendfile '/path/to/file'\n  end\nend\n```\n\nUse `stream` to stream file content in chunks.\n\n```ruby\nclass API < Grape::API\n  get '/' do\n    stream '/path/to/file'\n  end\nend\n```\n\nOr use `stream` to stream other kinds of content. In the following example a streamer class\nstreams paginated data from a database.\n\n```ruby\nclass MyObject\n  attr_accessor :result\n\n  def initialize(query)\n    @result = query\n  end\n\n  def each\n    yield '['\n    # Do paginated DB fetches and return each page formatted\n    first = false\n    result.find_in_batches do |records|\n      yield process_records(records, first)\n      first = false\n    end\n    yield ']'\n  end\n\n  def process_records(records, first)\n    buffer = +''\n    buffer << ',' unless first\n    buffer << records.map(&:to_json).join(',')\n    buffer\n  end\nend\n\nclass API < Grape::API\n  get '/' do\n    stream MyObject.new(Sprocket.all)\n  end\nend\n```\n\n### Upgrading to >= 1.3.3\n\n#### Nil values for structures\n\nNil values have always been a special case when dealing with types, especially with the following structures:\n\n- Array\n- Hash\n- Set\n\nThe behavior for these structures has changed throughout the latest releases. For example:\n\n```ruby\nclass Api < Grape::API\n  params do\n    require :my_param, type: Array[Integer]\n  end\n\n  get 'example' do\n     params[:my_param]\n  end\n  get '/example', params: { my_param: nil }\n  # 1.3.1 = []\n  # 1.3.2 = nil\nend\n```\n\nFor now on, `nil` values stay `nil` values for all types, including arrays, sets and hashes.\n\nIf you want to have the same behavior as 1.3.1, apply a `default` validator:\n\n```ruby\nclass Api < Grape::API\n  params do\n    require :my_param, type: Array[Integer], default: []\n  end\n\n  get 'example' do\n     params[:my_param]\n  end\n  get '/example', params: { my_param: nil } # => []\nend\n```\n\n#### Default validator\n\nDefault validator is now applied for `nil` values.\n\n```ruby\nclass Api < Grape::API\n  params do\n    requires :my_param, type: Integer, default: 0\n  end\n\n  get 'example' do\n     params[:my_param]\n  end\n  get '/example', params: { my_param: nil } #=> before: nil, after: 0\nend\n```\n\n### Upgrading to >= 1.3.0\n\nYou will need to upgrade to this version if you depend on `rack >= 2.1.0`.\n\n#### Ruby\n\nAfter adding dry-types, Ruby 2.4 or newer is required.\n\n#### Coercion\n\n[Virtus](https://github.com/solnic/virtus) has been replaced by [dry-types](https://dry-rb.org/gems/dry-types/1.2/) for parameter coercion. If your project depends on Virtus outside of Grape, explicitly add it to your `Gemfile`.\n\nHere's an example of how to migrate a custom type from Virtus to dry-types:\n\n```ruby\n# Legacy Grape parser\nclass SecureUriType < Virtus::Attribute\n  def coerce(input)\n    URI.parse value\n  end\n\n  def value_coerced?(input)\n    value.is_a? String\n  end\nend\n\nparams do\n  requires :secure_uri, type: SecureUri\nend\n```\n\nTo use dry-types, we need to:\n\n1. Remove the inheritance of `Virtus::Attribute`\n1. Rename `coerce` to `self.parse`\n1. Rename `value_coerced?` to `self.parsed?`\n\nThe custom type must have a class-level `parse` method to the model. A class-level `parsed?` is needed if the parsed type differs from the defined type. In the example below, since `SecureUri` is not the same as `URI::HTTPS`, `self.parsed?` is needed:\n\n```ruby\n# New dry-types parser\nclass SecureUri\n  def self.parse(value)\n    URI.parse value\n  end\n\n  def self.parsed?(value)\n    value.is_a? URI::HTTPS\n  end\nend\n\nparams do\n  requires :secure_uri, type: SecureUri\nend\n```\n\n#### Coercing to `FalseClass` or `TrueClass` no longer works\n\nPrevious Grape versions allowed this, though it wasn't documented:\n\n```ruby\nrequires :true_value, type: TrueClass\nrequires :bool_value, types: [FalseClass, TrueClass]\n```\n\nThis is no longer supported, if you do this, your values will never be valid. Instead you should do this:\n\n```ruby\nrequires :true_value, type: Boolean # in your endpoint you should validate if this is actually `true`\nrequires :bool_value, type: Boolean\n```\n\n#### Ensure that Array types have explicit coercions\n\nUnlike Virtus, dry-types does not perform any implict coercions. If you have any uses of `Array[String]`, `Array[Integer]`, etc. be sure they use a `coerce_with` block. For example:\n\n```ruby\nrequires :values, type: Array[String]\n```\n\nIt's quite common to pass a comma-separated list, such as `tag1,tag2` as `values`. Previously Virtus would implicitly coerce this to `Array(values)` so that `[\"tag1,tag2\"]` would pass the type checks, but with `dry-types` the values are no longer coerced for you. To fix this, you might do:\n\n```ruby\nrequires :values, type: Array[String], coerce_with: ->(val) { val.split(',').map(&:strip) }\n```\n\nLikewise, for `Array[Integer]`, you might do:\n\n```ruby\nrequires :values, type: Array[Integer], coerce_with: ->(val) { val.split(',').map(&:strip).map(&:to_i) }\n```\n\nFor more information see [#1920](https://github.com/ruby-grape/grape/pull/1920).\n\n### Upgrading to >= 1.2.4\n\n#### Headers in `error!` call\n\nHeaders in `error!` will be merged with `headers` hash. If any header need to be cleared on `error!` call, make sure to move it to the `after` block.\n\n```ruby\nclass SampleApi < Grape::API\n  before do\n    header 'X-Before-Header', 'before_call'\n  end\n\n  get 'ping' do\n    header 'X-App-Header', 'on_call'\n    error! :pong, 400, 'X-Error-Details' => 'Invalid token'\n  end\nend\n```\n**Former behaviour**\n```ruby\n  response.headers['X-Before-Header'] # => nil\n  response.headers['X-App-Header'] # => nil\n  response.headers['X-Error-Details'] # => Invalid token\n```\n\n**Current behaviour**\n```ruby\n  response.headers['X-Before-Header'] # => 'before_call'\n  response.headers['X-App-Header'] # => 'on_call'\n  response.headers['X-Error-Details'] # => Invalid token\n```\n\n### Upgrading to >= 1.2.1\n\n#### Obtaining the name of a mounted class\n\nIn order to make obtaining the name of a mounted class simpler, we've delegated `.to_s` to `base.name`\n\n**Deprecated in 1.2.0**\n```ruby\n  payload[:endpoint].options[:for].name\n```\n**New**\n```ruby\n  payload[:endpoint].options[:for].to_s\n```\n\n### Upgrading to >= 1.2.0\n\n#### Changes in the Grape::API class\n\n##### Patching the class\n\nIn an effort to make APIs re-mountable, The class `Grape::API` no longer refers to an API instance, rather, what used to be `Grape::API` is now `Grape::API::Instance` and `Grape::API` was replaced with a class that can contain several instances of `Grape::API`.\n\nThis changes were done in such a way that no code-changes should be required. However, if experiencing problems, or relying on private methods and internal behaviour too deeply, it is possible to restore the prior behaviour by replacing the references from `Grape::API` to `Grape::API::Instance`.\n\nNote, this is particularly relevant if you are opening the class `Grape::API` for modification.\n\n**Deprecated**\n```ruby\nclass Grape::API\n  # your patched logic\n  ...\nend\n```\n**New**\n```ruby\nclass Grape::API::Instance\n  # your patched logic\n  ...\nend\n```\n\n##### `name` (and other caveats) of the mounted API\n\nAfter the patch, the mounted API is no longer a Named class inheriting from `Grape::API`, it is an anonymous class which inherit from `Grape::API::Instance`.\n\nWhat this means in practice, is:\n\n- Generally: you can access the named class from the instance calling the getter `base`.\n- In particular: If you need the `name`, you can use `base`.`name`.\n\n**Deprecated**\n\n```ruby\n  payload[:endpoint].options[:for].name\n```\n\n**New**\n\n```ruby\n  payload[:endpoint].options[:for].base.name\n```\n\n#### Changes in rescue_from returned object\n\nGrape will now check the object returned from `rescue_from` and ensure that it is a `Rack::Response`. That makes sure response is valid and avoids exposing service information. Change any code that invoked `Rack::Response.new(...).finish` in a custom `rescue_from` block to `Rack::Response.new(...)` to comply with the validation.\n\n```ruby\nclass Twitter::API < Grape::API\n  rescue_from :all do |e|\n    # version prior to 1.2.0\n    Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' }).finish\n    # 1.2.0  version\n    Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' })\n  end\nend\n```\n\nSee [#1757](https://github.com/ruby-grape/grape/pull/1757) and [#1776](https://github.com/ruby-grape/grape/pull/1776) for more information.\n\n### Upgrading to >= 1.1.0\n\n#### Changes in HTTP Response Code for Unsupported Content Type\n\nFor PUT, POST, PATCH, and DELETE requests where a non-empty body and a \"Content-Type\" header is supplied that is not supported by the Grape API, Grape will no longer return a 406 \"Not Acceptable\" HTTP status code and will instead return a 415 \"Unsupported Media Type\" so that the usage of HTTP status code falls more in line with the specification of [RFC 2616](https://www.ietf.org/rfc/rfc2616.txt).\n\n### Upgrading to >= 1.0.0\n\n#### Changes in XML and JSON Parsers\n\nGrape no longer uses `multi_json` or `multi_xml` by default and uses `JSON` and `ActiveSupport::XmlMini` instead. This has no visible impact on JSON processing, but the default behavior of the XML parser has changed. For example, an XML POST containing `<user>Bobby T.</user>` was parsed as `Bobby T.` with `multi_xml`, and as now parsed as `{\"__content__\"=>\"Bobby T.\"}` with `XmlMini`.\n\nIf you were using `MultiJson.load`, `MultiJson.dump` or `MultiXml.parse`, you can substitute those with `Grape::Json.load`, `Grape::Json.dump`, `::Grape::Xml.parse`, or directly with `JSON.load`, `JSON.dump`, `XmlMini.parse`, etc.\n\nTo restore previous behavior, add `multi_json` or `multi_xml` to your `Gemfile` and `require` it.\n\nSee [#1623](https://github.com/ruby-grape/grape/pull/1623) for more information.\n\n#### Changes in Parameter Class\n\nThe default class for `params` has changed from `Hashie::Mash` to `ActiveSupport::HashWithIndifferentAccess` and the `hashie` dependency has been removed. This means that by default you can no longer access parameters by method name.\n\n```ruby\nclass API < Grape::API\n  params do\n    optional :color, type: String\n  end\n  get do\n    params[:color] # use params[:color] instead of params.color\n  end\nend\n```\n\nTo restore the behavior of prior versions, add `hashie` to your `Gemfile` and `include Grape::Extensions::Hashie::Mash::ParamBuilder` in your API.\n\n```ruby\nclass API < Grape::API\n  include Grape::Extensions::Hashie::Mash::ParamBuilder\n\n  params do\n    optional :color, type: String\n  end\n  get do\n    # params.color works\n  end\nend\n```\n\nThis behavior can also be overridden on individual parameter blocks using `build_with`.\n\n```ruby\nparams do\n  build_with Grape::Extensions::Hash::ParamBuilder\n  optional :color, type: String\nend\n```\n\nIf you're constructing your own `Grape::Request` in a middleware, you can pass different parameter handlers to create the desired `params` class with `build_params_with`.\n\n```ruby\ndef request\n  Grape::Request.new(env, build_params_with: Grape::Extensions::Hashie::Mash::ParamBuilder)\nend\n```\n\nSee [#1610](https://github.com/ruby-grape/grape/pull/1610) for more information.\n\n#### The `except`, `except_message`, and `proc` options of the `values` validator are deprecated.\n\nThe new `except_values` validator should be used in place of the `except` and `except_message` options of the `values` validator.\n\nArity one Procs may now be used directly as the `values` option to explicitly test param values.\n\n**Deprecated**\n```ruby\nparams do\n  requires :a, values: { value: 0..99, except: [3] }\n  requires :b, values: { value: 0..99, except: [3], except_message: 'not allowed' }\n  requires :c, values: { except: ['admin'] }\n  requires :d, values: { proc: -> (v) { v.even? } }\nend\n```\n**New**\n```ruby\nparams do\n  requires :a, values: 0..99, except_values: [3]\n  requires :b, values: 0..99, except_values: { value: [3], message: 'not allowed' }\n  requires :c, except_values: ['admin']\n  requires :d, values: -> (v) { v.even? }\nend\n```\n\nSee [#1616](https://github.com/ruby-grape/grape/pull/1616) for more information.\n\n### Upgrading to >= 0.19.1\n\n#### DELETE now defaults to status code 200 for responses with a body, or 204 otherwise\n\nPrior to this version, DELETE requests defaulted to a status code of 204 No Content, even when the response included content. This behavior confused some clients and prevented the formatter middleware from running properly. As of this version, DELETE requests will only default to a 204 No Content status code if no response body is provided, and will default to 200 OK otherwise.\n\nSpecifically, DELETE behaviour has changed as follows:\n\n- In versions < 0.19.0, all DELETE requests defaulted to a 200 OK status code.\n- In version 0.19.0, all DELETE requests defaulted to a 204 No Content status code, even when content was included in the response.\n- As of version 0.19.1, DELETE requests default to a 204 No Content status code, unless content is supplied, in which case they default to a 200 OK status code.\n\nTo achieve the old behavior, one can specify the status code explicitly:\n\n```ruby\ndelete :id do\n  status 204 # or 200, for < 0.19.0 behavior\n  'foo successfully deleted'\nend\n```\n\nOne can also use the new `return_no_content` helper to explicitly return a 204 status code and an empty body for any request type:\n\n```ruby\ndelete :id do\n  return_no_content\n  'this will not be returned'\nend\n```\n\nSee [#1550](https://github.com/ruby-grape/grape/pull/1550) for more information.\n\n### Upgrading to >= 0.18.1\n\n#### Changes in priority of :any routes\n\nPrior to this version, `:any` routes were searched after matching first route and 405 routes. This behavior has changed and `:any` routes are now searched before 405 processing. In the following example the `:any` route will match first when making a request with an unsupported verb.\n\n```ruby\npost :example do\n  'example'\nend\nroute :any, '*path' do\n  error! :not_found, 404\nend\n\nget '/example' #=> before: 405, after: 404\n```\n\n#### Removed param processing from built-in OPTIONS handler\n\nWhen a request is made to the built-in `OPTIONS` handler, only the `before` and `after` callbacks associated with the resource will be run.  The `before_validation` and `after_validation` callbacks and parameter validations will be skipped.\n\nSee [#1505](https://github.com/ruby-grape/grape/pull/1505) for more information.\n\n#### Changed endpoint params validation\n\nGrape now correctly returns validation errors for all params when multiple params are passed to a requires.\nThe following code will return `one is missing, two is missing` when calling the endpoint without parameters.\n\n```ruby\nparams do\n  requires :one, :two\nend\n```\n\nPrior to this version the response would be `one is missing`.\n\nSee [#1510](https://github.com/ruby-grape/grape/pull/1510) for more information.\n\n#### The default status code for DELETE is now 204 instead of 200.\n\nBreaking change: Sets the default response status code for a delete request to 204. A status of 204 makes the response more distinguishable and therefore easier to handle on the client side, particularly because a DELETE request typically returns an empty body as the resource was deleted or voided.\n\nTo achieve the old behavior, one has to set it explicitly:\n```ruby\ndelete :id do\n  status 200\n  'foo successfully deleted'\nend\n```\n\nFor more information see: [#1532](https://github.com/ruby-grape/grape/pull/1532).\n\n### Upgrading to >= 0.17.0\n\n#### Removed official support for Ruby < 2.2.2\n\nGrape is no longer automatically tested against versions of Ruby prior to 2.2.2. This is because of its dependency on activesupport which, with version 5.0.0, now requires at least Ruby 2.2.2.\n\nSee [#1441](https://github.com/ruby-grape/grape/pull/1441) for nmore information.\n\n#### Changed priority of `rescue_from` clauses applying\n\nThe `rescue_from` clauses declared inside a namespace would take a priority over ones declared in the root scope.\nThis could possibly affect those users who use different `rescue_from` clauses in root scope and in namespaces.\n\nSee [#1405](https://github.com/ruby-grape/grape/pull/1405) for more information.\n\n#### Helper methods injected inside `rescue_from` in middleware\n\nHelper methods are injected inside `rescue_from` may cause undesirable effects. For example, definining a helper method called `error!` will take precendence over the built-in `error!` method and should be renamed.\n\nSee [#1451](https://github.com/ruby-grape/grape/issues/1451) for an example.\n\n### Upgrading to >= 0.16.0\n\n#### Replace rack-mount with new router\n\nThe `Route#route_xyz` methods have been deprecated since 0.15.1.\n\nPlease use `Route#xyz` instead.\n\nNote that the `Route#route_method` was replaced by `Route#request_method`.\n\nThe following code would work correctly.\n\n```ruby\nTwitterAPI::versions # yields [ 'v1', 'v2' ]\nTwitterAPI::routes # yields an array of Grape::Route objects\nTwitterAPI::routes[0].version # => 'v1'\nTwitterAPI::routes[0].description # => 'Includes custom settings.'\nTwitterAPI::routes[0].settings[:custom] # => { key: 'value' }\n\nTwitterAPI::routes[0].request_method # => 'GET'\n```\n\n#### `file` method accepts path to file\n\nNow to serve files via Grape just pass the path to the file. Functionality with FileStreamer-like objects is deprecated.\n\nPlease, replace your FileStreamer-like objects with paths of served files.\n\nOld style:\n\n```ruby\nclass FileStreamer\n  def initialize(file_path)\n    @file_path = file_path\n  end\n\n  def each(&blk)\n    File.open(@file_path, 'rb') do |file|\n      file.each(10, &blk)\n    end\n  end\nend\n\n# ...\n\nclass API < Grape::API\n  get '/' do\n    file FileStreamer.new('/path/to/file')\n  end\nend\n```\n\nNew style:\n\n```ruby\nclass API < Grape::API\n  get '/' do\n    file '/path/to/file'\n  end\nend\n```\n\n### Upgrading to >= 0.15.0\n\n#### Changes to availability of `:with` option of `rescue_from` method\n\nThe `:with` option of `rescue_from` does not accept value except Proc, String or Symbol now.\n\nIf you have been depending the old behavior, you should use lambda block instead.\n\n```ruby\nclass API < Grape::API\n  rescue_from :all, with: -> { Rack::Response.new('rescued with a method', 400) }\nend\n```\n\n#### Changes to behavior of `after` method of middleware on error\n\nThe `after` method of the middleware is now also called on error. The following code would work correctly.\n\n```ruby\nclass ErrorMiddleware < Grape::Middleware::Base\n  def after\n    return unless @app_response && @app_response[0] == 500\n    env['rack.logger'].debug(\"Raised error on #{env['PATH_INFO']}\")\n  end\nend\n```\n\nSee [#1147](https://github.com/ruby-grape/grape/issues/1147) and [#1240](https://github.com/ruby-grape/grape/issues/1240) for discussion of the issues.\n\nA warning will be logged if an exception is raised in an `after` callback, which points you to middleware that was not called in the previous version and is called now.\n\n```\ncaught error of type NoMethodError in after callback inside Api::Middleware::SomeMiddleware : undefined method `headers' for nil:NilClass\n```\n\nSee [#1285](https://github.com/ruby-grape/grape/pull/1285) for more information.\n\n#### Changes to Method Not Allowed routes\n\nA `405 Method Not Allowed` error now causes `Grape::Exceptions::MethodNotAllowed` to be raised, which will be rescued via `rescue_from :all`. Restore old behavior with the following error handler.\n\n```ruby\nrescue_from Grape::Exceptions::MethodNotAllowed do |e|\n  error! e.message, e.status, e.headers\nend\n```\n\nSee [#1283](https://github.com/ruby-grape/grape/pull/1283) for more information.\n\n#### Changes to Grape::Exceptions::Validation parameters\n\nWhen raising `Grape::Exceptions::Validation` explicitly, replace `message_key` with `message`.\n\nFor example,\n\n```ruby\nfail Grape::Exceptions::Validation, params: [:oauth_token_secret], message_key: :presence\n```\n\nbecomes\n\n```ruby\nfail Grape::Exceptions::Validation, params: [:oauth_token_secret], message: :presence\n```\n\nSee [#1295](https://github.com/ruby-grape/grape/pull/1295) for more information.\n\n### Upgrading to >= 0.14.0\n\n#### Changes to availability of DSL methods in filters\n\nThe `#declared` method of the route DSL is no longer available in the `before` filter.  Using `declared` in a `before` filter will now raise `Grape::DSL::InsideRoute::MethodNotYetAvailable`.\n\nSee [#1074](https://github.com/ruby-grape/grape/issues/1074) for discussion of the issue.\n\n#### Changes to header versioning and invalid header version handling\n\nIdentical endpoints with different versions now work correctly. A regression introduced in Grape 0.11.0 caused all but the first-mounted version for such an endpoint to wrongly throw an `InvalidAcceptHeader`. As a side effect, requests with a correct vendor but invalid version can no longer be rescued from a `rescue_from` block.\n\nSee [#1114](https://github.com/ruby-grape/grape/pull/1114) for more information.\n\n#### Bypasses formatters when status code indicates no content\n\nTo be consistent with rack and it's handling of standard responses associated with no content, both default and custom formatters will now be bypassed when processing responses for status codes defined [by rack](https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L567)\n\nSee [#1190](https://github.com/ruby-grape/grape/pull/1190) for more information.\n\n#### Redirects respond as plain text with message\n\n`#redirect` now uses `text/plain` regardless of whether that format has been enabled. This prevents formatters from attempting to serialize the message body and allows for a descriptive message body to be provided - and optionally overridden - that better fulfills the theme of the HTTP spec.\n\nSee [#1194](https://github.com/ruby-grape/grape/pull/1194) for more information.\n\n### Upgrading to >= 0.12.0\n\n#### Changes in middleware\n\nThe Rack response object is no longer converted to an array by the formatter, enabling streaming. If your custom middleware is accessing `@app_response`, update it to expect a `Rack::Response` instance instead of an array.\n\nFor example,\n\n```ruby\nclass CacheBusterMiddleware < Grape::Middleware::Base\n  def after\n    @app_response[1]['Expires'] = Time.at(0).utc.to_s\n    @app_response\n  end\nend\n```\n\nbecomes\n\n```ruby\nclass CacheBusterMiddleware < Grape::Middleware::Base\n  def after\n    @app_response.headers['Expires'] = Time.at(0).utc.to_s\n    @app_response\n  end\nend\n```\n\nSee [#1029](https://github.com/ruby-grape/grape/pull/1029) for more information.\n\nThere is a known issue because of this change. When Grape is used with an older than 1.2.4 version of [warden](https://github.com/hassox/warden) there may be raised the following exception having the [rack-mount](https://github.com/jm/rack-mount) gem's lines as last ones in the backtrace:\n\n```\nNoMethodError: undefined method `[]' for nil:NilClass\n```\n\nThe issue can be solved by upgrading warden to 1.2.4 version.\n\nSee [#1151](https://github.com/ruby-grape/grape/issues/1151) for more information.\n\n#### Changes in present\n\nUsing `present` with objects that responded to `merge` would cause early evaluation of the represented object, with unexpected side-effects, such as missing parameters or environment within rendering code. Grape now only merges represented objects with a previously rendered body, usually when multiple `present` calls are made in the same route.\n\nSee [grape-with-roar#5](https://github.com/dblock/grape-with-roar/issues/5) and [#1023](https://github.com/ruby-grape/grape/issues/1023).\n\n#### Changes to regexp validator\n\nParameters with `nil` value will now pass `regexp` validation. To disallow `nil` value for an endpoint, add `allow_blank: false`.\n\n```ruby\nparams do\n  requires :email, allow_blank: false, regexp: /.+@.+/\nend\n```\n\nSee [#957](https://github.com/ruby-grape/grape/pull/957) for more information.\n\n#### Replace error_response with error! in rescue_from blocks\n\nNote: `error_response` is being deprecated, not removed.\n\n```ruby\ndef error!(message, status = options[:default_status], headers = {}, backtrace = [])\n  headers = { 'Content-Type' => content_type }.merge(headers)\n  rack_response(format_message(message, backtrace), status, headers)\nend\n```\n\nFor example,\n\n```\nerror_response({ message: { message: 'No such page.', id: 'missing_page' }, status: 404, headers: { 'Content-Type' => 'api/error' })\n```\n\nbecomes\n\n```\nerror!({ message: 'No such page.', id: 'missing_page' }, 404, { 'Content-Type' => 'api/error' })\n```\n\n`error!` also supports just passing a message. `error!('Server error.')` and `format: :json` returns the following JSON response\n\n```\n{ 'error': 'Server error.' }\n```\n\nwith a status code of 500 and a Content Type of text/error.\n\nOptionally, also replace `Rack::Response.new` with `error!.`\nThe following are equivalent:\n\n```\nRack::Response.new([ e.message ], 500, { \"Content-type\" => \"text/error\" }).finish\nerror!(e)\n```\n\nSee [#889](https://github.com/ruby-grape/grape/issues/889) for more information.\n\n#### Changes to routes when using `format`\n\nVersion 0.10.0 has introduced a change via [#809](https://github.com/ruby-grape/grape/pull/809) whereas routes no longer got file-type suffixes added if you declared a single API `format`. This has been reverted, it's now again possible to call API with proper suffix when single `format` is defined:\n\n```ruby\nclass API < Grape::API\n  format :json\n\n  get :hello do\n    { hello: 'world' }\n  end\nend\n```\n\nWill respond with JSON to `/hello` **and** `/hello.json`.\n\nWill respond with 404 to `/hello.xml`, `/hello.txt` etc.\n\nSee the [#1001](https://github.com/ruby-grape/grape/pull/1001) and [#914](https://github.com/ruby-grape/grape/issues/914) for more info.\n\n### Upgrading to >= 0.11.0\n\n#### Added Rack 1.6.0 support\n\nGrape now supports, but doesn't require Rack 1.6.0. If you encounter an issue with parsing requests larger than 128KB, explictly require Rack 1.6.0 in your Gemfile.\n\n```ruby\ngem 'rack', '~> 1.6.0'\n```\n\nSee [#559](https://github.com/ruby-grape/grape/issues/559) for more information.\n\n#### Removed route_info\n\nKey route_info is excluded from params.\n\nSee [#879](https://github.com/ruby-grape/grape/pull/879) for more information.\n\n\n#### Fix callbacks within a version block\n\nCallbacks defined in a version block are only called for the routes defined in that block. This was a regression introduced in Grape 0.10.0, and is fixed in this version.\n\nSee [#901](https://github.com/ruby-grape/grape/pull/901) for more information.\n\n\n#### Make type of group of parameters required\n\nGroups of parameters now require their type to be set explicitly as Array or Hash.\nNot setting the type now results in MissingGroupTypeError, unsupported type will raise UnsupportedTypeError.\n\nSee [#886](https://github.com/ruby-grape/grape/pull/886) for more information.\n\n### Upgrading to >= 0.10.1\n\n#### Changes to `declared(params, include_missing: false)`\n\nAttributes with `nil` values or with values that evaluate to `false` are no longer considered *missing* and will be returned when `include_missing` is set to `false`.\n\nSee [#864](https://github.com/ruby-grape/grape/pull/864) for more information.\n\n### Upgrading to >= 0.10.0\n\n#### Changes to content-types\n\nThe following content-types have been removed:\n\n* atom (application/atom+xml)\n* rss (application/rss+xml)\n* jsonapi (application/jsonapi)\n\nThis is because they have never been properly supported.\n\n#### Changes to desc\n\nNew block syntax:\n\nFormer:\n\n```ruby\n  desc \"some descs\",\n    detail: 'more details',\n    entity: API::Entities::Entity,\n    params: API::Entities::Status.documentation,\n    named: 'a name',\n    headers: [XAuthToken: {\n      description: 'Valdates your identity',\n      required: true\n    }\n  get nil, http_codes: [\n    [401, 'Unauthorized', API::Entities::BaseError],\n    [404, 'not found', API::Entities::Error]\n  ] do\n```\n\nNow:\n\n```ruby\ndesc \"some descs\" do\n  detail 'more details'\n  params API::Entities::Status.documentation\n  success API::Entities::Entity\n  failure [\n    [401, 'Unauthorized', API::Entities::BaseError],\n    [404, 'not found', API::Entities::Error]\n  ]\n  named 'a name'\n  headers [\n    XAuthToken: {\n      description: 'Valdates your identity',\n      required: true\n    },\n    XOptionalHeader: {\n      description: 'Not really needed',\n      required: false\n    }\n  ]\nend\n```\n\n#### Changes to Route Options and Descriptions\n\nA common hack to extend Grape with custom DSL methods was manipulating `@last_description`.\n\n``` ruby\nmodule Grape\n  module Extensions\n    module SortExtension\n      def sort(value)\n        @last_description ||= {}\n        @last_description[:sort] ||= {}\n        @last_description[:sort].merge! value\n        value\n      end\n    end\n\n    Grape::API.extend self\n  end\nend\n```\n\nYou could access this value from within the API with `route.route_sort` or, more generally, via `env['api.endpoint'].options[:route_options][:sort]`.\n\nThis will no longer work, use the documented and supported `route_setting`.\n\n``` ruby\nmodule Grape\n  module Extensions\n    module SortExtension\n      def sort(value)\n        route_setting :sort, sort: value\n        value\n      end\n    end\n\n    Grape::API.extend self\n  end\nend\n```\n\nTo retrieve this value at runtime from within an API, use `env['api.endpoint'].route_setting(:sort)` and when introspecting a mounted API, use `route.route_settings[:sort]`.\n\n#### Accessing Class Variables from Helpers\n\nIt used to be possible to fetch an API class variable from a helper function. For example:\n\n```ruby\n@@static_variable = 42\n\nhelpers do\n  def get_static_variable\n    @@static_variable\n  end\nend\n\nget do\n  get_static_variable\nend\n```\n\nThis will no longer work. Use a class method instead of a helper.\n\n```ruby\n@@static_variable = 42\n\ndef self.get_static_variable\n  @@static_variable\nend\n\nget do\n  get_static_variable\nend\n```\n\nFor more information see [#836](https://github.com/ruby-grape/grape/issues/836).\n\n#### Changes to Custom Validators\n\nTo implement a custom validator, you need to inherit from `Grape::Validations::Base` instead of `Grape::Validations::Validator`.\n\nFor more information see [Custom Validators](https://github.com/ruby-grape/grape#custom-validators) in the documentation.\n\n#### Changes to Raising Grape::Exceptions::Validation\n\nIn previous versions raising `Grape::Exceptions::Validation` required a single `param`.\n\n```ruby\nraise Grape::Exceptions::Validation, param: :id, message_key: :presence\n```\n\nThe `param` argument has been deprecated and is now an array of `params`, accepting multiple values.\n\n```ruby\nraise Grape::Exceptions::Validation, params: [:id], message_key: :presence\n```\n\n#### Changes to routes when using `format`\n\nRoutes will no longer get file-type suffixes added if you declare a single API `format`. For example,\n\n```ruby\nclass API < Grape::API\n  format :json\n\n  get :hello do\n    { hello: 'world' }\n  end\nend\n```\n\nPre-0.10.0, this would respond with JSON to `/hello`, `/hello.json`, `/hello.xml`, `/hello.txt`, etc.\n\nNow, this will only respond with JSON to `/hello`, but will be a 404 when trying to access `/hello.json`, `/hello.xml`, `/hello.txt`, etc.\n\nIf you declare further `content_type`s, this behavior will be circumvented. For example, the following API will respond with JSON to `/hello`, `/hello.json`, `/hello.xml`, `/hello.txt`, etc.\n\n```ruby\nclass API < Grape::API\n  format :json\n  content_type :json, 'application/json'\n\n  get :hello do\n    { hello: 'world' }\n  end\nend\n```\n\nSee the [the updated API Formats documentation](https://github.com/ruby-grape/grape#api-formats) and [#809](https://github.com/ruby-grape/grape/pull/809) for more info.\n\n#### Changes to Evaluation of Permitted Parameter Values\n\nPermitted and default parameter values are now only evaluated lazily for each request when declared as a proc. The following code would raise an error at startup time.\n\n```ruby\nparams do\n  optional :v, values: -> { [:x, :y] }, default: -> { :z }\nend\n```\n\nRemove the proc to get the previous behavior.\n\n```ruby\nparams do\n  optional :v, values: [:x, :y], default: :z\nend\n```\n\nSee [#801](https://github.com/ruby-grape/grape/issues/801) for more information.\n\n#### Changes to version\n\nIf version is used with a block, the callbacks defined within that version block are not scoped to that individual block. In other words, the callback would be inherited by all versions blocks that follow the first one e.g\n\n```ruby\nclass API < Grape::API\n  resource :foo do\n    version 'v1', :using => :path do\n      before do\n        @output ||= 'hello1'\n      end\n      get '/' do\n        @output += '-v1'\n      end\n    end\n\n    version 'v2', :using => :path do\n      before do\n        @output ||= 'hello2'\n      end\n      get '/:id' do\n        @output += '-v2'\n      end\n    end\n  end\nend\n```\n\nwhen making a API call `GET /foo/v2/1`, the API would set instance variable `@output` to `hello1-v2`\n\nSee [#898](https://github.com/ruby-grape/grape/issues/898) for more information.\n\n\n### Upgrading to >= 0.9.0\n\n#### Changes in Authentication\n\nThe following middleware classes have been removed:\n\n* `Grape::Middleware::Auth::Basic`\n* `Grape::Middleware::Auth::Digest`\n* `Grape::Middleware::Auth::OAuth2`\n\nWhen you use theses classes directly like:\n\n```ruby\n module API\n   class Root < Grape::API\n     class Protected < Grape::API\n       use Grape::Middleware::Auth::OAuth2,\n           token_class: 'AccessToken',\n           parameter: %w(access_token api_key)\n\n```\n\nyou have to replace these classes.\n\nAs replacement can be used\n\n* `Grape::Middleware::Auth::Basic`  => [`Rack::Auth::Basic`](https://github.com/rack/rack/blob/master/lib/rack/auth/basic.rb)\n* `Grape::Middleware::Auth::Digest` => [`Rack::Auth::Digest::MD5`](https://github.com/rack/rack/blob/master/lib/rack/auth/digest/md5.rb)\n* `Grape::Middleware::Auth::OAuth2` => [warden-oauth2](https://github.com/opperator/warden-oauth2) or [rack-oauth2](https://github.com/nov/rack-oauth2)\n\nIf this is not possible you can extract the middleware files from [grape v0.7.0](https://github.com/ruby-grape/grape/tree/v0.7.0/lib/grape/middleware/auth) and host these files within your application\n\nSee [#703](https://github.com/ruby-grape/Grape/pull/703) for more information.\n\n### Upgrading to >= 0.7.0\n\n#### Changes in Exception Handling\n\nAssume you have the following exception classes defined.\n\n```ruby\nclass ParentError < StandardError; end\nclass ChildError < ParentError; end\n```\n\nIn Grape <= 0.6.1, the `rescue_from` keyword only handled the exact exception being raised. The following code would rescue `ParentError`, but not `ChildError`.\n\n```ruby\nrescue_from ParentError do |e|\n  # only rescue ParentError\nend\n```\n\nThis made it impossible to rescue an exception hieararchy, which is a more sensible default. In Grape 0.7.0 or newer, both `ParentError` and `ChildError` are rescued.\n\n```ruby\nrescue_from ParentError do |e|\n  # rescue both ParentError and ChildError\nend\n```\n\nTo only rescue the base exception class, set `rescue_subclasses: false`.\n\n```ruby\nrescue_from ParentError, rescue_subclasses: false do |e|\n  # only rescue ParentError\nend\n```\n\nSee [#544](https://github.com/ruby-grape/grape/pull/544) for more information.\n\n\n#### Changes in the Default HTTP Status Code\n\nIn Grape <= 0.6.1, the default status code returned from `error!` was 403.\n\n```ruby\nerror! \"You may not reticulate this spline!\" # yields HTTP error 403\n```\n\nThis was a bad default value, since 403 means \"Forbidden\". Change any call to `error!` that does not specify a status code to specify one. The new default value is a more sensible default of 500, which is \"Internal Server Error\".\n\n```ruby\nerror! \"You may not reticulate this spline!\", 403 # yields HTTP error 403\n```\n\nYou may also use `default_error_status` to change the global default.\n\n```ruby\ndefault_error_status 400\n```\n\nSee [#525](https://github.com/ruby-grape/Grape/pull/525) for more information.\n\n\n#### Changes in Parameter Declaration and Validation\n\nIn Grape <= 0.6.1, `group`, `optional` and `requires` keywords with a block accepted either an `Array` or a `Hash`.\n\n```ruby\nparams do\n  requires :id, type: Integer\n  group :name do\n    requires :first_name\n    requires :last_name\n  end\nend\n```\n\nThis caused the ambiguity and unexpected errors described in [#543](https://github.com/ruby-grape/Grape/issues/543).\n\nIn Grape 0.7.0, the `group`, `optional` and `requires` keywords take an additional `type` attribute which defaults to `Array`. This means that without a `type` attribute, these nested parameters will no longer accept a single hash, only an array (of hashes).\n\nWhereas in 0.6.1 the API above accepted the following json, it no longer does in 0.7.0.\n\n```json\n{\n  \"id\": 1,\n  \"name\": {\n    \"first_name\": \"John\",\n    \"last_name\" : \"Doe\"\n  }\n}\n```\n\nThe `params` block should now read as follows.\n\n```ruby\nparams do\n  requires :id, type: Integer\n  requires :name, type: Hash do\n    requires :first_name\n    requires :last_name\n  end\nend\n```\n\nSee [#545](https://github.com/ruby-grape/Grape/pull/545) for more information.\n\n\n### Upgrading to 0.6.0\n\nIn Grape <= 0.5.0, only the first validation error was raised and processing aborted. Validation errors are now collected and a single `Grape::Exceptions::ValidationErrors` exception is raised. You can access the collection of validation errors as `.errors`.\n\n```ruby\nrescue_from Grape::Exceptions::Validations do |e|\n  Rack::Response.new({\n    status: 422,\n    message: e.message,\n    errors: e.errors\n  }.to_json, 422)\nend\n```\n\nFor more information see [#462](https://github.com/ruby-grape/grape/issues/462).\n"
  },
  {
    "path": "benchmark/compile_many_routes.rb",
    "content": "# frozen_string_literal: true\n\n$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))\nrequire 'grape'\nrequire 'benchmark/ips'\n\nclass API < Grape::API\n  prefix :api\n  version 'v1', using: :path\n\n  2000.times do |index|\n    get \"/test#{index}/\" do\n      'hello'\n    end\n  end\nend\n\nBenchmark.ips do |ips|\n  ips.report('Compiling 2000 routes') do\n    API.compile!\n  end\nend\n"
  },
  {
    "path": "benchmark/issue_mounting.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'bundler/inline'\n\ngemfile(true) do\n  source 'https://rubygems.org'\n  gem 'grape'\n  gem 'rack'\n  gem 'minitest'\n  gem 'rack-test'\nend\n\nrequire 'minitest/autorun'\nrequire 'rack/test'\nrequire 'grape'\n\nclass GrapeAPIBugTest < Minitest::Test\n  include Rack::Test::Methods\n\n  RootAPI = Class.new(Grape::API) do\n    format :json\n\n    delete :test do\n      status 200\n      []\n    end\n  end\n\n  def test_v1_users_via_api\n    env = Rack::MockRequest.env_for('/test', method: Rack::DELETE)\n    response = Rack::MockResponse[*RootAPI.call(env)]\n\n    assert_equal '[]', response.body\n    assert_equal 200, response.status\n  end\nend\n"
  },
  {
    "path": "benchmark/large_model.rb",
    "content": "# frozen_string_literal: true\n\n# gem 'grape', '=1.0.1'\n\nrequire 'grape'\nrequire 'ruby-prof'\nrequire 'hashie'\n\nclass API < Grape::API\n  # include Grape::Extensions::Hash::ParamBuilder\n  # include Grape::Extensions::Hashie::Mash::ParamBuilder\n\n  rescue_from do |e|\n    warn \"\\n\\n#{e.class} (#{e.message}):\\n    #{e.backtrace.join(\"\\n    \")}\\n\\n\"\n  end\n\n  prefix :api\n  version 'v1', using: :path\n  content_type :json, 'application/json; charset=UTF-8'\n  default_format :json\n\n  def self.vrp_request_timewindow(this)\n    this.optional(:id, types: String)\n    this.optional(:start, types: [String, Float, Integer])\n    this.optional(:end, types: [String, Float, Integer])\n    this.optional(:day_index, type: Integer, values: 0..6)\n    this.at_least_one_of :start, :end, :day_index\n  end\n\n  def self.vrp_request_indice_range(this)\n    this.optional(:start, type: Integer)\n    this.optional(:end, type: Integer)\n  end\n\n  def self.vrp_request_point(this)\n    this.requires(:id, type: String, allow_blank: false)\n    this.optional(:location, type: Hash, allow_blank: false) do\n      requires(:lat, type: Float, allow_blank: false)\n      requires(:lon, type: Float, allow_blank: false)\n    end\n  end\n\n  def self.vrp_request_unit(this)\n    this.requires(:id, type: String, allow_blank: false)\n    this.optional(:label, type: String)\n    this.optional(:counting, type: Boolean)\n  end\n\n  def self.vrp_request_activity(this)\n    this.optional(:duration, types: [String, Float, Integer])\n    this.optional(:additional_value, type: Integer)\n    this.optional(:setup_duration, types: [String, Float, Integer])\n    this.optional(:late_multiplier, type: Float)\n    this.optional(:timewindow_start_day_shift_number, documentation: { hidden: true }, type: Integer)\n    this.requires(:point_id, type: String, allow_blank: false)\n    this.optional(:timewindows, type: Array) do\n      API.vrp_request_timewindow(self)\n    end\n  end\n\n  def self.vrp_request_quantity(this)\n    this.optional(:id, type: String)\n    this.requires(:unit_id, type: String, allow_blank: false)\n    this.optional(:value, type: Float)\n  end\n\n  def self.vrp_request_capacity(this)\n    this.optional(:id, type: String)\n    this.requires(:unit_id, type: String, allow_blank: false)\n    this.requires(:limit, type: Float, allow_blank: false)\n    this.optional(:initial, type: Float)\n    this.optional(:overload_multiplier, type: Float)\n  end\n\n  def self.vrp_request_vehicle(this)\n    this.requires(:id, type: String, allow_blank: false)\n    this.optional(:cost_fixed, type: Float)\n    this.optional(:cost_distance_multiplier, type: Float)\n    this.optional(:cost_time_multiplier, type: Float)\n\n    this.optional :router_dimension, type: String, values: %w[time distance]\n    this.optional(:skills, type: Array[Array[String]], coerce_with: ->(val) { val.is_a?(String) ? [val.split(',').map(&:strip)] : val })\n\n    this.optional(:unavailable_work_day_indices, type: Array[Integer])\n\n    this.optional(:free_approach, type: Boolean)\n    this.optional(:free_return, type: Boolean)\n\n    this.optional(:start_point_id, type: String)\n    this.optional(:end_point_id, type: String)\n    this.optional(:capacities, type: Array) do\n      API.vrp_request_capacity(self)\n    end\n\n    this.optional(:sequence_timewindows, type: Array) do\n      API.vrp_request_timewindow(self)\n    end\n  end\n\n  def self.vrp_request_service(this)\n    this.requires(:id, type: String, allow_blank: false)\n    this.optional(:priority, type: Integer, values: 0..8)\n    this.optional(:exclusion_cost, type: Integer)\n\n    this.optional(:visits_number, type: Integer, coerce_with: ->(val) { val.to_i.positive? && val.to_i }, default: 1, allow_blank: false)\n\n    this.optional(:unavailable_visit_indices, type: Array[Integer])\n    this.optional(:unavailable_visit_day_indices, type: Array[Integer])\n\n    this.optional(:minimum_lapse, type: Float)\n    this.optional(:maximum_lapse, type: Float)\n\n    this.optional(:sticky_vehicle_ids, type: Array[String])\n    this.optional(:skills, type: Array[String])\n\n    this.optional(:type, type: Symbol)\n    this.optional(:activity, type: Hash) do\n      API.vrp_request_activity(self)\n    end\n    this.optional(:quantities, type: Array) do\n      API.vrp_request_quantity(self)\n    end\n  end\n\n  def self.vrp_request_configuration(this)\n    this.optional(:preprocessing, type: Hash) do\n      API.vrp_request_preprocessing(self)\n    end\n    this.optional(:resolution, type: Hash) do\n      API.vrp_request_resolution(self)\n    end\n    this.optional(:restitution, type: Hash) do\n      API.vrp_request_restitution(self)\n    end\n    this.optional(:schedule, type: Hash) do\n      API.vrp_request_schedule(self)\n    end\n  end\n\n  def self.vrp_request_partition(this)\n    this.requires(:method, type: String, values: %w[hierarchical_tree balanced_kmeans])\n    this.optional(:metric, type: Symbol)\n    this.optional(:entity, type: Symbol, values: %i[vehicle work_day], coerce_with: lambda(&:to_sym))\n    this.optional(:threshold, type: Integer)\n  end\n\n  def self.vrp_request_preprocessing(this)\n    this.optional(:max_split_size, type: Integer)\n    this.optional(:partition_method, type: String, documentation: { hidden: true })\n    this.optional(:partition_metric, type: Symbol, documentation: { hidden: true })\n    this.optional(:kmeans_centroids, type: Array[Integer])\n    this.optional(:cluster_threshold, type: Float)\n    this.optional(:force_cluster, type: Boolean)\n    this.optional(:prefer_short_segment, type: Boolean)\n    this.optional(:neighbourhood_size, type: Integer)\n    this.optional(:partitions, type: Array) do\n      API.vrp_request_partition(self)\n    end\n    this.optional(:first_solution_strategy, type: Array[String])\n  end\n\n  def self.vrp_request_resolution(this)\n    this.optional(:duration, type: Integer, allow_blank: false)\n    this.optional(:iterations, type: Integer, allow_blank: false)\n    this.optional(:iterations_without_improvment, type: Integer, allow_blank: false)\n    this.optional(:stable_iterations, type: Integer, allow_blank: false)\n    this.optional(:stable_coefficient, type: Float, allow_blank: false)\n    this.optional(:initial_time_out, type: Integer, allow_blank: false, documentation: { hidden: true })\n    this.optional(:minimum_duration, type: Integer, allow_blank: false)\n    this.optional(:time_out_multiplier, type: Integer)\n    this.optional(:vehicle_limit, type: Integer)\n    this.optional(:solver_parameter, type: Integer, documentation: { hidden: true })\n    this.optional(:solver, type: Boolean, default: true)\n    this.optional(:same_point_day, type: Boolean)\n    this.optional(:allow_partial_assignment, type: Boolean, default: true)\n    this.optional(:split_number, type: Integer)\n    this.optional(:evaluate_only, type: Boolean)\n    this.optional(:several_solutions, type: Integer, allow_blank: false, default: 1)\n    this.optional(:batch_heuristic, type: Boolean, default: false)\n    this.optional(:variation_ratio, type: Integer)\n    this.optional(:repetition, type: Integer, documentation: { hidden: true })\n    this.at_least_one_of :duration, :iterations, :iterations_without_improvment, :stable_iterations, :stable_coefficient, :initial_time_out, :minimum_duration\n    this.mutually_exclusive :initial_time_out, :minimum_duration\n  end\n\n  def self.vrp_request_restitution(this)\n    this.optional(:geometry, type: Boolean)\n    this.optional(:geometry_polyline, type: Boolean)\n    this.optional(:intermediate_solutions, type: Boolean)\n    this.optional(:csv, type: Boolean)\n    this.optional(:allow_empty_result, type: Boolean)\n  end\n\n  def self.vrp_request_schedule(this)\n    this.optional(:range_indices, type: Hash) do\n      API.vrp_request_indice_range(self)\n    end\n    this.optional(:unavailable_indices, type: Array[Integer])\n  end\n\n  params do\n    optional(:vrp, type: Hash, documentation: { param_type: 'body' }) do\n      optional(:name, type: String)\n\n      optional(:points, type: Array) do\n        API.vrp_request_point(self)\n      end\n\n      optional(:units, type: Array) do\n        API.vrp_request_unit(self)\n      end\n\n      requires(:vehicles, type: Array) do\n        API.vrp_request_vehicle(self)\n      end\n\n      optional(:services, type: Array, allow_blank: false) do\n        API.vrp_request_service(self)\n      end\n\n      optional(:configuration, type: Hash) do\n        API.vrp_request_configuration(self)\n      end\n    end\n  end\n  post '/' do\n    {\n      skills_v1: params[:vrp][:vehicles].first[:skills],\n      skills_v2: params[:vrp][:vehicles].last[:skills]\n    }\n  end\nend\nputs Grape::VERSION\n\noptions = {\n  method: Rack::POST,\n  params: JSON.parse(File.read('benchmark/resource/vrp_example.json'))\n}\n\nenv = Rack::MockRequest.env_for('/api/v1', options)\n\nstart = Time.now\nresult = RubyProf.profile do\n  response = API.call env\n  puts response.last\nend\nputs Time.now - start\nprinter = RubyProf::FlatPrinter.new(result)\nFile.open('test_prof.out', 'w+') { |f| printer.print(f, {}) }\n"
  },
  {
    "path": "benchmark/nested_params.rb",
    "content": "# frozen_string_literal: true\n\n$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))\nrequire 'grape'\nrequire 'benchmark/ips'\n\nclass API < Grape::API\n  prefix :api\n  version 'v1', using: :path\n\n  params do\n    requires :address, type: Hash do\n      requires :street, type: String\n      requires :postal_code, type: Integer\n      optional :city, type: String\n    end\n  end\n  post '/' do\n    'hello'\n  end\nend\n\noptions = {\n  method: Rack::POST,\n  params: {\n    address: {\n      street: 'Alexis Pl.',\n      postal_code: '90210',\n      city: 'Beverly Hills'\n    }\n  }\n}\n\nenv = Rack::MockRequest.env_for('/api/v1', options)\n\n10.times do |i|\n  env[\"HTTP_HEADER#{i}\"] = '123'\nend\n\nBenchmark.ips do |ips|\n  ips.report('POST with nested params') do\n    API.call env\n  end\nend\n"
  },
  {
    "path": "benchmark/remounting.rb",
    "content": "# frozen_string_literal: true\n\n$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))\nrequire 'grape'\nrequire 'benchmark/memory'\n\nclass VotingApi < Grape::API\n  logger Logger.new($stdout)\n\n  helpers do\n    def logger\n      VotingApi.logger\n    end\n  end\n\n  namespace 'votes' do\n    get do\n      logger\n    end\n  end\nend\n\nclass PostApi < Grape::API\n  mount VotingApi\nend\n\nclass CommentAPI < Grape::API\n  mount VotingApi\nend\n\nenv = Rack::MockRequest.env_for('/votes', method: Rack::GET)\n\nBenchmark.memory do |api|\n  calls = 1000\n\n  api.report('using Array') do\n    VotingApi.instance_variable_set(:@setup, [])\n    calls.times { PostApi.call(env) }\n    puts \" setup size: #{VotingApi.instance_variable_get(:@setup).size}\"\n  end\n\n  api.report('using Set') do\n    VotingApi.instance_variable_set(:@setup, Set.new)\n    calls.times { PostApi.call(env) }\n    puts \" setup size: #{VotingApi.instance_variable_get(:@setup).size}\"\n  end\n\n  api.compare!\nend\n"
  },
  {
    "path": "benchmark/resource/vrp_example.json",
    "content": "{\"vrp\":{\"points\":[{\"id\":\"1002100\",\"location\":{\"lat\":48.865,\"lon\":2.3054}},{\"id\":\"1103548\",\"location\":{\"lat\":48.8711,\"lon\":2.3079}},{\"id\":\"1142617\",\"location\":{\"lat\":48.8756,\"lon\":2.302}},{\"id\":\"1147052\",\"location\":{\"lat\":48.8758,\"lon\":2.3074}},{\"id\":\"1104396\",\"location\":{\"lat\":48.8776,\"lon\":2.3056}},{\"id\":\"1139292\",\"location\":{\"lat\":48.8767,\"lon\":2.3032}},{\"id\":\"1139149\",\"location\":{\"lat\":48.8767,\"lon\":2.3073}},{\"id\":\"1118656\",\"location\":{\"lat\":48.8732,\"lon\":2.3049}},{\"id\":\"1123712\",\"location\":{\"lat\":48.8755,\"lon\":2.3023}},{\"id\":\"1120539\",\"location\":{\"lat\":48.8739,\"lon\":2.303}},{\"id\":\"1109631\",\"location\":{\"lat\":48.8774,\"lon\":2.3047}},{\"id\":\"1139151\",\"location\":{\"lat\":48.8767,\"lon\":2.3071}},{\"id\":\"1005088\",\"location\":{\"lat\":48.8714,\"lon\":2.307}},{\"id\":\"1054022\",\"location\":{\"lat\":48.8735,\"lon\":2.3095}},{\"id\":\"1052132\",\"location\":{\"lat\":48.8733,\"lon\":2.3058}},{\"id\":\"1080067\",\"location\":{\"lat\":48.8755,\"lon\":2.3024}},{\"id\":\"1080537\",\"location\":{\"lat\":48.8732,\"lon\":2.3057}},{\"id\":\"1001821\",\"location\":{\"lat\":48.8721,\"lon\":2.3043}},{\"id\":\"1033652\",\"location\":{\"lat\":48.8758,\"lon\":2.3031}},{\"id\":\"1127811\",\"location\":{\"lat\":48.8768,\"lon\":2.3091}},{\"id\":\"1031446\",\"location\":{\"lat\":48.8723,\"lon\":2.3033}},{\"id\":\"1004332\",\"location\":{\"lat\":48.8733,\"lon\":2.3056}},{\"id\":\"1030348\",\"location\":{\"lat\":48.875,\"lon\":2.3051}},{\"id\":\"1062118\",\"location\":{\"lat\":48.873,\"lon\":2.305}},{\"id\":\"1035112\",\"location\":{\"lat\":48.8755,\"lon\":2.3023}},{\"id\":\"1001140\",\"location\":{\"lat\":48.8776,\"lon\":2.3038}},{\"id\":\"1144968\",\"location\":{\"lat\":48.8749,\"lon\":2.304}},{\"id\":\"1136835\",\"location\":{\"lat\":48.8732,\"lon\":2.3051}},{\"id\":\"1133790\",\"location\":{\"lat\":48.879,\"lon\":2.3043}},{\"id\":\"1133878\",\"location\":{\"lat\":48.8785,\"lon\":2.3039}},{\"id\":\"1007882\",\"location\":{\"lat\":48.8738,\"lon\":2.2965}},{\"id\":\"1020596\",\"location\":{\"lat\":48.8664,\"lon\":2.31}},{\"id\":\"1064282\",\"location\":{\"lat\":48.8731,\"lon\":2.3072}},{\"id\":\"1134687\",\"location\":{\"lat\":48.8759,\"lon\":2.3077}},{\"id\":\"1135600\",\"location\":{\"lat\":48.8768,\"lon\":2.3092}},{\"id\":\"1133576\",\"location\":{\"lat\":48.8768,\"lon\":2.3091}},{\"id\":\"1138821\",\"location\":{\"lat\":48.8749,\"lon\":2.3035}},{\"id\":\"1066596\",\"location\":{\"lat\":48.8722,\"lon\":2.2967}},{\"id\":\"1080091\",\"location\":{\"lat\":48.8787,\"lon\":2.3051}},{\"id\":\"1094392\",\"location\":{\"lat\":48.8732,\"lon\":2.3131}},{\"id\":\"1071805\",\"location\":{\"lat\":48.8755,\"lon\":2.3022}},{\"id\":\"1064291\",\"location\":{\"lat\":48.8731,\"lon\":2.3072}},{\"id\":\"1137046\",\"location\":{\"lat\":48.8732,\"lon\":2.3051}},{\"id\":\"1131694\",\"location\":{\"lat\":48.8744,\"lon\":2.2984}},{\"id\":\"1005035\",\"location\":{\"lat\":48.8786,\"lon\":2.3131}},{\"id\":\"1004005\",\"location\":{\"lat\":48.8733,\"lon\":2.3062}},{\"id\":\"1041519\",\"location\":{\"lat\":48.8755,\"lon\":2.3022}},{\"id\":\"1148428\",\"location\":{\"lat\":0.0,\"lon\":0.0}},{\"id\":\"1119178\",\"location\":{\"lat\":48.8726,\"lon\":2.304}},{\"id\":\"1030515\",\"location\":{\"lat\":48.8789,\"lon\":2.303}},{\"id\":\"1130633\",\"location\":{\"lat\":48.8755,\"lon\":2.3023}},{\"id\":\"1132792\",\"location\":{\"lat\":48.8744,\"lon\":2.2984}},{\"id\":\"1124356\",\"location\":{\"lat\":48.8753,\"lon\":2.3047}},{\"id\":\"1121089\",\"location\":{\"lat\":48.8769,\"lon\":2.3074}},{\"id\":\"1102925\",\"location\":{\"lat\":48.8732,\"lon\":2.3131}},{\"id\":\"1102928\",\"location\":{\"lat\":48.8732,\"lon\":2.3131}},{\"id\":\"1105871\",\"location\":{\"lat\":48.872,\"lon\":2.3039}},{\"id\":\"1116088\",\"location\":{\"lat\":48.8768,\"lon\":2.3091}},{\"id\":\"1109290\",\"location\":{\"lat\":48.8747,\"lon\":2.2982}},{\"id\":\"1131649\",\"location\":{\"lat\":48.8775,\"lon\":2.2997}},{\"id\":\"1136697\",\"location\":{\"lat\":48.8732,\"lon\":2.3051}},{\"id\":\"1030517\",\"location\":{\"lat\":48.8751,\"lon\":2.3064}},{\"id\":\"1132871\",\"location\":{\"lat\":48.8732,\"lon\":2.3051}},{\"id\":\"1148306\",\"location\":{\"lat\":0.0,\"lon\":0.0}},{\"id\":\"1126467\",\"location\":{\"lat\":48.8768,\"lon\":2.3091}},{\"id\":\"1130723\",\"location\":{\"lat\":48.8768,\"lon\":2.3006}},{\"id\":\"1099009\",\"location\":{\"lat\":48.874,\"lon\":2.2984}},{\"id\":\"1095726\",\"location\":{\"lat\":48.8777,\"lon\":2.2994}},{\"id\":\"1005056\",\"location\":{\"lat\":48.8776,\"lon\":2.3038}},{\"id\":\"1122952\",\"location\":{\"lat\":48.8738,\"lon\":2.3005}},{\"id\":\"1126324\",\"location\":{\"lat\":48.8768,\"lon\":2.3091}},{\"id\":\"1124513\",\"location\":{\"lat\":48.8732,\"lon\":2.3051}},{\"id\":\"1124103\",\"location\":{\"lat\":48.873,\"lon\":2.3047}},{\"id\":\"1131394\",\"location\":{\"lat\":48.8747,\"lon\":2.3239}},{\"id\":\"1133951\",\"location\":{\"lat\":48.8704,\"lon\":2.3211}},{\"id\":\"1137715\",\"location\":{\"lat\":48.8698,\"lon\":2.3182}},{\"id\":\"1132589\",\"location\":{\"lat\":48.8739,\"lon\":2.3214}},{\"id\":\"1145751\",\"location\":{\"lat\":48.8715,\"lon\":2.3236}},{\"id\":\"1070749\",\"location\":{\"lat\":48.8712,\"lon\":2.3194}},{\"id\":\"1070735\",\"location\":{\"lat\":48.8703,\"lon\":2.3176}},{\"id\":\"1002504\",\"location\":{\"lat\":48.8696,\"lon\":2.3188}},{\"id\":\"1007287\",\"location\":{\"lat\":48.8707,\"lon\":2.3199}},{\"id\":\"1005919\",\"location\":{\"lat\":48.8698,\"lon\":2.3178}},{\"id\":\"1143914\",\"location\":{\"lat\":48.8693,\"lon\":2.3201}},{\"id\":\"1144594\",\"location\":{\"lat\":48.8764,\"lon\":2.3083}},{\"id\":\"1127546\",\"location\":{\"lat\":48.8692,\"lon\":2.3209}},{\"id\":\"1123348\",\"location\":{\"lat\":48.8742,\"lon\":2.3171}},{\"id\":\"1103574\",\"location\":{\"lat\":48.8711,\"lon\":2.3185}},{\"id\":\"1087334\",\"location\":{\"lat\":48.8724,\"lon\":2.3183}},{\"id\":\"1088315\",\"location\":{\"lat\":48.8762,\"lon\":2.3135}},{\"id\":\"1054230\",\"location\":{\"lat\":48.8697,\"lon\":2.3198}},{\"id\":\"1058540\",\"location\":{\"lat\":48.8701,\"lon\":2.3209}},{\"id\":\"1106440\",\"location\":{\"lat\":48.87,\"lon\":2.3185}},{\"id\":\"1120609\",\"location\":{\"lat\":48.8729,\"lon\":2.3228}},{\"id\":\"1119750\",\"location\":{\"lat\":48.8693,\"lon\":2.3195}},{\"id\":\"1107065\",\"location\":{\"lat\":48.8708,\"lon\":2.3202}},{\"id\":\"1096970\",\"location\":{\"lat\":48.8733,\"lon\":2.3193}},{\"id\":\"1124357\",\"location\":{\"lat\":48.8716,\"lon\":2.3216}},{\"id\":\"1130453\",\"location\":{\"lat\":48.8763,\"lon\":2.3139}},{\"id\":\"1121283\",\"location\":{\"lat\":48.8733,\"lon\":2.3213}},{\"id\":\"1143992\",\"location\":{\"lat\":48.8713,\"lon\":2.3226}},{\"id\":\"1020782\",\"location\":{\"lat\":48.8717,\"lon\":2.3198}},{\"id\":\"1109136\",\"location\":{\"lat\":48.8732,\"lon\":2.3214}},{\"id\":\"1107406\",\"location\":{\"lat\":48.87,\"lon\":2.3189}},{\"id\":\"1001454\",\"location\":{\"lat\":48.8717,\"lon\":2.322}},{\"id\":\"1031405\",\"location\":{\"lat\":48.8733,\"lon\":2.3181}},{\"id\":\"1099019\",\"location\":{\"lat\":48.8712,\"lon\":2.3184}},{\"id\":\"1040631\",\"location\":{\"lat\":48.8722,\"lon\":2.3231}},{\"id\":\"1030463\",\"location\":{\"lat\":48.8725,\"lon\":2.3218}},{\"id\":\"1033191\",\"location\":{\"lat\":48.8736,\"lon\":2.3213}},{\"id\":\"1133959\",\"location\":{\"lat\":48.873,\"lon\":2.3163}},{\"id\":\"1004770\",\"location\":{\"lat\":48.8788,\"lon\":2.3171}},{\"id\":\"1129651\",\"location\":{\"lat\":48.8713,\"lon\":2.3226}},{\"id\":\"1121101\",\"location\":{\"lat\":48.8701,\"lon\":2.3183}},{\"id\":\"1119751\",\"location\":{\"lat\":48.8703,\"lon\":2.3212}},{\"id\":\"1137030\",\"location\":{\"lat\":48.8729,\"lon\":2.3223}},{\"id\":\"1134263\",\"location\":{\"lat\":48.8764,\"lon\":2.3142}},{\"id\":\"1133530\",\"location\":{\"lat\":48.873,\"lon\":2.3176}},{\"id\":\"1142237\",\"location\":{\"lat\":48.8713,\"lon\":2.3226}},{\"id\":\"1030487\",\"location\":{\"lat\":48.8701,\"lon\":2.3191}},{\"id\":\"1004647\",\"location\":{\"lat\":48.874,\"lon\":2.3186}},{\"id\":\"1004716\",\"location\":{\"lat\":48.8737,\"lon\":2.3172}},{\"id\":\"1144936\",\"location\":{\"lat\":48.8772,\"lon\":2.3165}},{\"id\":\"1134666\",\"location\":{\"lat\":48.874,\"lon\":2.3184}},{\"id\":\"1006725\",\"location\":{\"lat\":48.8736,\"lon\":2.3158}},{\"id\":\"1092502\",\"location\":{\"lat\":48.8754,\"lon\":2.323}},{\"id\":\"1008001\",\"location\":{\"lat\":48.8749,\"lon\":2.3158}},{\"id\":\"1144493\",\"location\":{\"lat\":48.873,\"lon\":2.3124}},{\"id\":\"1147114\",\"location\":{\"lat\":48.8738,\"lon\":2.3165}},{\"id\":\"1147721\",\"location\":{\"lat\":0.0,\"lon\":0.0}},{\"id\":\"1003152\",\"location\":{\"lat\":48.8763,\"lon\":2.3205}},{\"id\":\"1110450\",\"location\":{\"lat\":48.8735,\"lon\":2.3142}},{\"id\":\"1070260\",\"location\":{\"lat\":48.8742,\"lon\":2.3206}},{\"id\":\"1132451\",\"location\":{\"lat\":48.8739,\"lon\":2.3193}},{\"id\":\"1122595\",\"location\":{\"lat\":48.8743,\"lon\":2.3212}},{\"id\":\"1134348\",\"location\":{\"lat\":48.8749,\"lon\":2.3211}},{\"id\":\"1127201\",\"location\":{\"lat\":48.8732,\"lon\":2.3131}},{\"id\":\"1138580\",\"location\":{\"lat\":48.8751,\"lon\":2.3211}},{\"id\":\"1143039\",\"location\":{\"lat\":48.8731,\"lon\":2.3135}},{\"id\":\"1132224\",\"location\":{\"lat\":48.8746,\"lon\":2.3226}},{\"id\":\"1095177\",\"location\":{\"lat\":48.877,\"lon\":2.3175}},{\"id\":\"1111407\",\"location\":{\"lat\":48.8745,\"lon\":2.3219}},{\"id\":\"1117925\",\"location\":{\"lat\":48.8739,\"lon\":2.3178}},{\"id\":\"1135294\",\"location\":{\"lat\":48.8737,\"lon\":2.3138}},{\"id\":\"1031534\",\"location\":{\"lat\":48.8735,\"lon\":2.3143}},{\"id\":\"1047944\",\"location\":{\"lat\":48.8739,\"lon\":2.3195}},{\"id\":\"1050281\",\"location\":{\"lat\":48.873,\"lon\":2.3157}},{\"id\":\"1054024\",\"location\":{\"lat\":48.8754,\"lon\":2.3236}},{\"id\":\"1040973\",\"location\":{\"lat\":48.8765,\"lon\":2.3173}},{\"id\":\"1063338\",\"location\":{\"lat\":48.8752,\"lon\":2.3171}},{\"id\":\"1031918\",\"location\":{\"lat\":48.8739,\"lon\":2.3178}},{\"id\":\"1145151\",\"location\":{\"lat\":48.8739,\"lon\":2.3193}},{\"id\":\"1054036\",\"location\":{\"lat\":48.8748,\"lon\":2.3215}},{\"id\":\"1004708\",\"location\":{\"lat\":48.875,\"lon\":2.3203}},{\"id\":\"1002561\",\"location\":{\"lat\":48.8744,\"lon\":2.3174}},{\"id\":\"1005880\",\"location\":{\"lat\":48.8738,\"lon\":2.3161}},{\"id\":\"1144485\",\"location\":{\"lat\":48.8736,\"lon\":2.3139}},{\"id\":\"1116199\",\"location\":{\"lat\":48.8737,\"lon\":2.3142}},{\"id\":\"1123435\",\"location\":{\"lat\":48.8738,\"lon\":2.318}},{\"id\":\"1124213\",\"location\":{\"lat\":48.8743,\"lon\":2.3182}},{\"id\":\"startvehicule1\",\"location\":{\"lat\":48.78,\"lon\":2.43}},{\"id\":\"startvehicule2\",\"location\":{\"lat\":48.78,\"lon\":2.43}},{\"id\":\"endvehicule1\",\"location\":{\"lat\":48.78,\"lon\":2.43}},{\"id\":\"endvehicule2\",\"location\":{\"lat\":48.78,\"lon\":2.43}}],\"units\":[{\"id\":\"kg\",\"label\":\"kg\"},{\"id\":\"l\",\"label\":\"l\"},{\"id\":\"qte\",\"label\":\"qte\"}],\"services\":[{\"id\":\"1002100_EMP_ 28_1FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":19.5},{\"unit_id\":\"l\",\"value\":92.34},{\"unit_id\":\"qte\",\"value\":15.0}],\"visits_number\":3,\"minimum_lapse\":120.0,\"activity\":{\"point_id\":\"1002100\",\"duration\":120,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":45000,\"day_index\":0},{\"start\":48600,\"end\":64800,\"day_index\":0},{\"start\":30600,\"end\":45000,\"day_index\":1},{\"start\":48600,\"end\":64800,\"day_index\":1},{\"start\":30600,\"end\":45000,\"day_index\":2},{\"start\":48600,\"end\":64800,\"day_index\":2},{\"start\":30600,\"end\":45000,\"day_index\":3},{\"start\":48600,\"end\":64800,\"day_index\":3},{\"start\":30600,\"end\":45000,\"day_index\":4},{\"start\":48600,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1103548_TAP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.7},{\"unit_id\":\"l\",\"value\":37.02},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":12,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1103548\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_SAV_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1147052_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.08},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":68.0}],\"visits_number\":3,\"minimum_lapse\":272.0,\"activity\":{\"point_id\":\"1147052\",\"duration\":272,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1104396_SAV_ 84_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.5},{\"unit_id\":\"l\",\"value\":5.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":1,\"minimum_lapse\":20.0,\"activity\":{\"point_id\":\"1104396\",\"duration\":20,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1147052_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.08},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":68.0}],\"visits_number\":3,\"minimum_lapse\":272.0,\"activity\":{\"point_id\":\"1147052\",\"duration\":272,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139292_SNC_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.9},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1139292\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139149_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":2.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1139149\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":72000,\"day_index\":0},{\"start\":32400,\"end\":72000,\"day_index\":1},{\"start\":32400,\"end\":72000,\"day_index\":2},{\"start\":32400,\"end\":72000,\"day_index\":3},{\"start\":32400,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_PCP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1104396_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.5},{\"unit_id\":\"l\",\"value\":5.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":1,\"minimum_lapse\":20.0,\"activity\":{\"point_id\":\"1104396\",\"duration\":20,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1104396_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.5},{\"unit_id\":\"l\",\"value\":5.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":1,\"minimum_lapse\":20.0,\"activity\":{\"point_id\":\"1104396\",\"duration\":20,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1118656_PCP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1118656\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1123712_PCP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1123712\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1120539_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":2.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1120539\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":64800,\"day_index\":0},{\"start\":34200,\"end\":64800,\"day_index\":1},{\"start\":34200,\"end\":64800,\"day_index\":2},{\"start\":34200,\"end\":64800,\"day_index\":3},{\"start\":34200,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1120539_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":2.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1120539\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":64800,\"day_index\":0},{\"start\":34200,\"end\":64800,\"day_index\":1},{\"start\":34200,\"end\":64800,\"day_index\":2},{\"start\":34200,\"end\":64800,\"day_index\":3},{\"start\":34200,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1120539_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":2.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1120539\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":64800,\"day_index\":0},{\"start\":34200,\"end\":64800,\"day_index\":1},{\"start\":34200,\"end\":64800,\"day_index\":2},{\"start\":34200,\"end\":64800,\"day_index\":3},{\"start\":34200,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1109631_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1109631\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1109631_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1109631\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1109631_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1109631\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1118656_PH _ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1118656\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1118656_SAV_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1118656\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139151_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1139151\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":72000,\"day_index\":0},{\"start\":32400,\"end\":72000,\"day_index\":1},{\"start\":32400,\"end\":72000,\"day_index\":2},{\"start\":32400,\"end\":72000,\"day_index\":3},{\"start\":32400,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139151_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1139151\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":72000,\"day_index\":0},{\"start\":32400,\"end\":72000,\"day_index\":1},{\"start\":32400,\"end\":72000,\"day_index\":2},{\"start\":32400,\"end\":72000,\"day_index\":3},{\"start\":32400,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139151_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1139151\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":72000,\"day_index\":0},{\"start\":32400,\"end\":72000,\"day_index\":1},{\"start\":32400,\"end\":72000,\"day_index\":2},{\"start\":32400,\"end\":72000,\"day_index\":3},{\"start\":32400,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005088_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":6.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1005088\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1054022_SNC_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":12,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1054022\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":72000,\"day_index\":0},{\"start\":27000,\"end\":72000,\"day_index\":1},{\"start\":27000,\"end\":72000,\"day_index\":2},{\"start\":27000,\"end\":72000,\"day_index\":3},{\"start\":27000,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1052132_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.7},{\"unit_id\":\"l\",\"value\":179.2},{\"unit_id\":\"qte\",\"value\":14.0}],\"visits_number\":3,\"minimum_lapse\":112.0,\"activity\":{\"point_id\":\"1052132\",\"duration\":112,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":61200,\"day_index\":0},{\"start\":34200,\"end\":61200,\"day_index\":1},{\"start\":34200,\"end\":61200,\"day_index\":2},{\"start\":34200,\"end\":61200,\"day_index\":3},{\"start\":34200,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1052132_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.7},{\"unit_id\":\"l\",\"value\":179.2},{\"unit_id\":\"qte\",\"value\":14.0}],\"visits_number\":3,\"minimum_lapse\":112.0,\"activity\":{\"point_id\":\"1052132\",\"duration\":112,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":61200,\"day_index\":0},{\"start\":34200,\"end\":61200,\"day_index\":1},{\"start\":34200,\"end\":61200,\"day_index\":2},{\"start\":34200,\"end\":61200,\"day_index\":3},{\"start\":34200,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1080067_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":33.0},{\"unit_id\":\"l\",\"value\":93.0},{\"unit_id\":\"qte\",\"value\":15.0}],\"visits_number\":12,\"minimum_lapse\":195.0,\"activity\":{\"point_id\":\"1080067\",\"duration\":195,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":46800,\"day_index\":0},{\"start\":21600,\"end\":46800,\"day_index\":1},{\"start\":21600,\"end\":46800,\"day_index\":2},{\"start\":21600,\"end\":46800,\"day_index\":3},{\"start\":21600,\"end\":46800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1080537_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.55},{\"unit_id\":\"l\",\"value\":140.8},{\"unit_id\":\"qte\",\"value\":11.0}],\"visits_number\":3,\"minimum_lapse\":88.0,\"activity\":{\"point_id\":\"1080537\",\"duration\":88,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":61200,\"day_index\":0},{\"start\":34200,\"end\":61200,\"day_index\":1},{\"start\":34200,\"end\":61200,\"day_index\":2},{\"start\":34200,\"end\":61200,\"day_index\":3},{\"start\":34200,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1080537_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.55},{\"unit_id\":\"l\",\"value\":140.8},{\"unit_id\":\"qte\",\"value\":11.0}],\"visits_number\":3,\"minimum_lapse\":88.0,\"activity\":{\"point_id\":\"1080537\",\"duration\":88,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":61200,\"day_index\":0},{\"start\":34200,\"end\":61200,\"day_index\":1},{\"start\":34200,\"end\":61200,\"day_index\":2},{\"start\":34200,\"end\":61200,\"day_index\":3},{\"start\":34200,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1080537_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.55},{\"unit_id\":\"l\",\"value\":140.8},{\"unit_id\":\"qte\",\"value\":11.0}],\"visits_number\":3,\"minimum_lapse\":88.0,\"activity\":{\"point_id\":\"1080537\",\"duration\":88,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":61200,\"day_index\":0},{\"start\":34200,\"end\":61200,\"day_index\":1},{\"start\":34200,\"end\":61200,\"day_index\":2},{\"start\":34200,\"end\":61200,\"day_index\":3},{\"start\":34200,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1001821_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":15.0,\"activity\":{\"point_id\":\"1001821\",\"duration\":15,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1001821_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":15.0,\"activity\":{\"point_id\":\"1001821\",\"duration\":15,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1033652_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":21.0},{\"unit_id\":\"l\",\"value\":256.0},{\"unit_id\":\"qte\",\"value\":20.0}],\"visits_number\":3,\"minimum_lapse\":160.0,\"activity\":{\"point_id\":\"1033652\",\"duration\":160,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":45000,\"day_index\":0},{\"start\":30600,\"end\":45000,\"day_index\":1},{\"start\":30600,\"end\":45000,\"day_index\":2},{\"start\":30600,\"end\":45000,\"day_index\":3},{\"start\":30600,\"end\":45000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127811_PCP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":29.76},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":96.0}],\"visits_number\":12,\"minimum_lapse\":384.0,\"activity\":{\"point_id\":\"1127811\",\"duration\":384,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1052132_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.7},{\"unit_id\":\"l\",\"value\":179.2},{\"unit_id\":\"qte\",\"value\":14.0}],\"visits_number\":3,\"minimum_lapse\":112.0,\"activity\":{\"point_id\":\"1052132\",\"duration\":112,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":61200,\"day_index\":0},{\"start\":34200,\"end\":61200,\"day_index\":1},{\"start\":34200,\"end\":61200,\"day_index\":2},{\"start\":34200,\"end\":61200,\"day_index\":3},{\"start\":34200,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1031446_TAP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.89},{\"unit_id\":\"l\",\"value\":19.55},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":100.0,\"activity\":{\"point_id\":\"1031446\",\"duration\":100,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005088_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":6.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1005088\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005088_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":6.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1005088\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004332_CLI_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.11},{\"unit_id\":\"l\",\"value\":1.125},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004332\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004332_LPL_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.11},{\"unit_id\":\"l\",\"value\":1.125},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004332\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004332_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.11},{\"unit_id\":\"l\",\"value\":1.125},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004332\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004332_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.11},{\"unit_id\":\"l\",\"value\":1.125},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004332\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004332_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.11},{\"unit_id\":\"l\",\"value\":1.125},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004332\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004332_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.11},{\"unit_id\":\"l\",\"value\":1.125},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004332\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030348_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":12,\"minimum_lapse\":156.0,\"activity\":{\"point_id\":\"1030348\",\"duration\":156,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1033652_PCP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":21.0},{\"unit_id\":\"l\",\"value\":256.0},{\"unit_id\":\"qte\",\"value\":20.0}],\"visits_number\":3,\"minimum_lapse\":160.0,\"activity\":{\"point_id\":\"1033652\",\"duration\":160,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":45000,\"day_index\":0},{\"start\":30600,\"end\":45000,\"day_index\":1},{\"start\":30600,\"end\":45000,\"day_index\":2},{\"start\":30600,\"end\":45000,\"day_index\":3},{\"start\":30600,\"end\":45000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1001821_ASC_ 84_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":15.0,\"activity\":{\"point_id\":\"1001821\",\"duration\":15,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127811_PH _  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":29.76},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":96.0}],\"visits_number\":12,\"minimum_lapse\":384.0,\"activity\":{\"point_id\":\"1127811\",\"duration\":384,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139149_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":2.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1139149\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":72000,\"day_index\":0},{\"start\":32400,\"end\":72000,\"day_index\":1},{\"start\":32400,\"end\":72000,\"day_index\":2},{\"start\":32400,\"end\":72000,\"day_index\":3},{\"start\":32400,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1062118_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1062118\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1062118_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1062118\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1062118_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1062118\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030348_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":12,\"minimum_lapse\":156.0,\"activity\":{\"point_id\":\"1030348\",\"duration\":156,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1062118_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1062118\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1035112_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.3},{\"unit_id\":\"l\",\"value\":3.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1035112\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1035112_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.3},{\"unit_id\":\"l\",\"value\":3.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1035112\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1001140_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":13.2},{\"unit_id\":\"l\",\"value\":37.2},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1001140\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":48600,\"end\":68400,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":48600,\"end\":68400,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":48600,\"end\":68400,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":48600,\"end\":68400,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":48600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1035112_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.3},{\"unit_id\":\"l\",\"value\":3.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1035112\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144968_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.852},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1144968\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144968_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.852},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1144968\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144968_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.852},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1144968\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1136835_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.44},{\"unit_id\":\"l\",\"value\":8.4},{\"unit_id\":\"qte\",\"value\":14.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1136835\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133790_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.4},{\"unit_id\":\"l\",\"value\":49.248},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":64.0,\"activity\":{\"point_id\":\"1133790\",\"duration\":64,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133790_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.4},{\"unit_id\":\"l\",\"value\":49.248},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":64.0,\"activity\":{\"point_id\":\"1133790\",\"duration\":64,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133790_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.4},{\"unit_id\":\"l\",\"value\":49.248},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":64.0,\"activity\":{\"point_id\":\"1133790\",\"duration\":64,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133878_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.4},{\"unit_id\":\"l\",\"value\":49.248},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":64.0,\"activity\":{\"point_id\":\"1133878\",\"duration\":64,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133878_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.4},{\"unit_id\":\"l\",\"value\":49.248},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":64.0,\"activity\":{\"point_id\":\"1133878\",\"duration\":64,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133878_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.4},{\"unit_id\":\"l\",\"value\":49.248},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":64.0,\"activity\":{\"point_id\":\"1133878\",\"duration\":64,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_PCP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1007882_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":26.4},{\"unit_id\":\"l\",\"value\":74.4},{\"unit_id\":\"qte\",\"value\":12.0}],\"visits_number\":6,\"minimum_lapse\":117.0,\"activity\":{\"point_id\":\"1007882\",\"duration\":117,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":43200,\"day_index\":0},{\"start\":21600,\"end\":43200,\"day_index\":1},{\"start\":21600,\"end\":43200,\"day_index\":2},{\"start\":21600,\"end\":43200,\"day_index\":3},{\"start\":21600,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020596_SNC_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.2},{\"unit_id\":\"l\",\"value\":15.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1020596\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1064282_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":8.8},{\"unit_id\":\"l\",\"value\":24.8},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":52.0,\"activity\":{\"point_id\":\"1064282\",\"duration\":52,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":70200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":70200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":70200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":70200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":70200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1064282_SNC_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":8.8},{\"unit_id\":\"l\",\"value\":24.8},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":52.0,\"activity\":{\"point_id\":\"1064282\",\"duration\":52,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":70200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":70200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":70200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":70200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":70200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134687_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":2.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1134687\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":72000,\"day_index\":0},{\"start\":32400,\"end\":72000,\"day_index\":1},{\"start\":32400,\"end\":72000,\"day_index\":2},{\"start\":32400,\"end\":72000,\"day_index\":3},{\"start\":32400,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1135600_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":49.35},{\"unit_id\":\"l\",\"value\":601.6},{\"unit_id\":\"qte\",\"value\":47.0}],\"visits_number\":3,\"minimum_lapse\":376.0,\"activity\":{\"point_id\":\"1135600\",\"duration\":376,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1135600_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":49.35},{\"unit_id\":\"l\",\"value\":601.6},{\"unit_id\":\"qte\",\"value\":47.0}],\"visits_number\":3,\"minimum_lapse\":376.0,\"activity\":{\"point_id\":\"1135600\",\"duration\":376,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133576_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":59.52},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":192.0}],\"visits_number\":3,\"minimum_lapse\":768.0,\"activity\":{\"point_id\":\"1133576\",\"duration\":768,\"setup_duration\":120,\"timewindows\":[{\"start\":23400,\"end\":34200,\"day_index\":0},{\"start\":23400,\"end\":34200,\"day_index\":1},{\"start\":23400,\"end\":34200,\"day_index\":2},{\"start\":23400,\"end\":34200,\"day_index\":3},{\"start\":23400,\"end\":34200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133576_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":59.52},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":192.0}],\"visits_number\":3,\"minimum_lapse\":768.0,\"activity\":{\"point_id\":\"1133576\",\"duration\":768,\"setup_duration\":120,\"timewindows\":[{\"start\":23400,\"end\":34200,\"day_index\":0},{\"start\":23400,\"end\":34200,\"day_index\":1},{\"start\":23400,\"end\":34200,\"day_index\":2},{\"start\":23400,\"end\":34200,\"day_index\":3},{\"start\":23400,\"end\":34200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133576_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":59.52},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":192.0}],\"visits_number\":3,\"minimum_lapse\":768.0,\"activity\":{\"point_id\":\"1133576\",\"duration\":768,\"setup_duration\":120,\"timewindows\":[{\"start\":23400,\"end\":34200,\"day_index\":0},{\"start\":23400,\"end\":34200,\"day_index\":1},{\"start\":23400,\"end\":34200,\"day_index\":2},{\"start\":23400,\"end\":34200,\"day_index\":3},{\"start\":23400,\"end\":34200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1138821_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":26.25},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":125.0}],\"visits_number\":3,\"minimum_lapse\":500.0,\"activity\":{\"point_id\":\"1138821\",\"duration\":500,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1138821_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":26.25},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":125.0}],\"visits_number\":3,\"minimum_lapse\":500.0,\"activity\":{\"point_id\":\"1138821\",\"duration\":500,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1138821_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":26.25},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":125.0}],\"visits_number\":3,\"minimum_lapse\":500.0,\"activity\":{\"point_id\":\"1138821\",\"duration\":500,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134687_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":2.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1134687\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":72000,\"day_index\":0},{\"start\":32400,\"end\":72000,\"day_index\":1},{\"start\":32400,\"end\":72000,\"day_index\":2},{\"start\":32400,\"end\":72000,\"day_index\":3},{\"start\":32400,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139149_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":2.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1139149\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":72000,\"day_index\":0},{\"start\":32400,\"end\":72000,\"day_index\":1},{\"start\":32400,\"end\":72000,\"day_index\":2},{\"start\":32400,\"end\":72000,\"day_index\":3},{\"start\":32400,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134687_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":2.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1134687\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":72000,\"day_index\":0},{\"start\":32400,\"end\":72000,\"day_index\":1},{\"start\":32400,\"end\":72000,\"day_index\":2},{\"start\":32400,\"end\":72000,\"day_index\":3},{\"start\":32400,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1066596_TAP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.32},{\"unit_id\":\"l\",\"value\":13.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":100.0,\"activity\":{\"point_id\":\"1066596\",\"duration\":100,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1064282_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":8.8},{\"unit_id\":\"l\",\"value\":24.8},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":52.0,\"activity\":{\"point_id\":\"1064282\",\"duration\":52,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":70200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":70200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":70200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":70200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":70200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1054022_SNC_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":12,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1054022\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":72000,\"day_index\":0},{\"start\":27000,\"end\":72000,\"day_index\":1},{\"start\":27000,\"end\":72000,\"day_index\":2},{\"start\":27000,\"end\":72000,\"day_index\":3},{\"start\":27000,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1080091_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":64.74},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":204.0}],\"visits_number\":3,\"minimum_lapse\":816.0,\"activity\":{\"point_id\":\"1080091\",\"duration\":816,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1080091_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":64.74},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":204.0}],\"visits_number\":3,\"minimum_lapse\":816.0,\"activity\":{\"point_id\":\"1080091\",\"duration\":816,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1094392_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1094392\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":25200,\"end\":45000,\"day_index\":0},{\"start\":48600,\"end\":64800,\"day_index\":0},{\"start\":25200,\"end\":45000,\"day_index\":1},{\"start\":48600,\"end\":64800,\"day_index\":1},{\"start\":25200,\"end\":45000,\"day_index\":2},{\"start\":48600,\"end\":64800,\"day_index\":2},{\"start\":25200,\"end\":45000,\"day_index\":3},{\"start\":48600,\"end\":64800,\"day_index\":3},{\"start\":25200,\"end\":45000,\"day_index\":4},{\"start\":48600,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1080067_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":33.0},{\"unit_id\":\"l\",\"value\":93.0},{\"unit_id\":\"qte\",\"value\":15.0}],\"visits_number\":12,\"minimum_lapse\":195.0,\"activity\":{\"point_id\":\"1080067\",\"duration\":195,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":46800,\"day_index\":0},{\"start\":21600,\"end\":46800,\"day_index\":1},{\"start\":21600,\"end\":46800,\"day_index\":2},{\"start\":21600,\"end\":46800,\"day_index\":3},{\"start\":21600,\"end\":46800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1080091_CLI_ 84_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":64.74},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":204.0}],\"visits_number\":3,\"minimum_lapse\":816.0,\"activity\":{\"point_id\":\"1080091\",\"duration\":816,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1071805_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":22.0},{\"unit_id\":\"l\",\"value\":62.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":6,\"minimum_lapse\":130.0,\"activity\":{\"point_id\":\"1071805\",\"duration\":130,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":70200,\"day_index\":0},{\"start\":28800,\"end\":70200,\"day_index\":1},{\"start\":28800,\"end\":70200,\"day_index\":2},{\"start\":28800,\"end\":70200,\"day_index\":3},{\"start\":28800,\"end\":70200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1064291_SNC_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.4},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1064291\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":70200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":70200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":70200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":70200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":70200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134687_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":2.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1134687\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":72000,\"day_index\":0},{\"start\":32400,\"end\":72000,\"day_index\":1},{\"start\":32400,\"end\":72000,\"day_index\":2},{\"start\":32400,\"end\":72000,\"day_index\":3},{\"start\":32400,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1136835_CLI_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.44},{\"unit_id\":\"l\",\"value\":8.4},{\"unit_id\":\"qte\",\"value\":14.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1136835\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1001821_CLI_ 84_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":15.0,\"activity\":{\"point_id\":\"1001821\",\"duration\":15,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1103548_TAP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.7},{\"unit_id\":\"l\",\"value\":37.02},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":12,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1103548\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1064291_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.4},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1064291\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":70200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":70200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":70200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":70200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":70200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1066596_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.32},{\"unit_id\":\"l\",\"value\":13.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":100.0,\"activity\":{\"point_id\":\"1066596\",\"duration\":100,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1064291_SNC_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.4},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1064291\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":70200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":70200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":70200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":70200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":70200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1066596_TAP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.32},{\"unit_id\":\"l\",\"value\":13.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":100.0,\"activity\":{\"point_id\":\"1066596\",\"duration\":100,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1064291_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.4},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1064291\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":70200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":70200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":70200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":70200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":70200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137046_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.3},{\"unit_id\":\"l\",\"value\":76.8},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1137046\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137046_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.3},{\"unit_id\":\"l\",\"value\":76.8},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1137046\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1131694_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.7},{\"unit_id\":\"l\",\"value\":55.404},{\"unit_id\":\"qte\",\"value\":9.0}],\"visits_number\":3,\"minimum_lapse\":72.0,\"activity\":{\"point_id\":\"1131694\",\"duration\":72,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":52200,\"end\":61200,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":52200,\"end\":61200,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":52200,\"end\":61200,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":52200,\"end\":61200,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":52200,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137046_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.3},{\"unit_id\":\"l\",\"value\":76.8},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1137046\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1071805_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":22.0},{\"unit_id\":\"l\",\"value\":62.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":6,\"minimum_lapse\":130.0,\"activity\":{\"point_id\":\"1071805\",\"duration\":130,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":70200,\"day_index\":0},{\"start\":28800,\"end\":70200,\"day_index\":1},{\"start\":28800,\"end\":70200,\"day_index\":2},{\"start\":28800,\"end\":70200,\"day_index\":3},{\"start\":28800,\"end\":70200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1080067_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":33.0},{\"unit_id\":\"l\",\"value\":93.0},{\"unit_id\":\"qte\",\"value\":15.0}],\"visits_number\":12,\"minimum_lapse\":195.0,\"activity\":{\"point_id\":\"1080067\",\"duration\":195,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":46800,\"day_index\":0},{\"start\":21600,\"end\":46800,\"day_index\":1},{\"start\":21600,\"end\":46800,\"day_index\":2},{\"start\":21600,\"end\":46800,\"day_index\":3},{\"start\":21600,\"end\":46800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1066596_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.32},{\"unit_id\":\"l\",\"value\":13.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":100.0,\"activity\":{\"point_id\":\"1066596\",\"duration\":100,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005035_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.64},{\"unit_id\":\"l\",\"value\":16.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":3,\"minimum_lapse\":132.0,\"activity\":{\"point_id\":\"1005035\",\"duration\":132,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005035_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.64},{\"unit_id\":\"l\",\"value\":16.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":3,\"minimum_lapse\":132.0,\"activity\":{\"point_id\":\"1005035\",\"duration\":132,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004005_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":12.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":26.0,\"activity\":{\"point_id\":\"1004005\",\"duration\":26,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004005_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":12.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":26.0,\"activity\":{\"point_id\":\"1004005\",\"duration\":26,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004005_CLI_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":12.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":26.0,\"activity\":{\"point_id\":\"1004005\",\"duration\":26,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004005_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":12.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":26.0,\"activity\":{\"point_id\":\"1004005\",\"duration\":26,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1041519_CLI_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.59},{\"unit_id\":\"l\",\"value\":2.625},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":28.0,\"activity\":{\"point_id\":\"1041519\",\"duration\":28,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1054022_SNC_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":12,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1054022\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":72000,\"day_index\":0},{\"start\":27000,\"end\":72000,\"day_index\":1},{\"start\":27000,\"end\":72000,\"day_index\":2},{\"start\":27000,\"end\":72000,\"day_index\":3},{\"start\":27000,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1064282_SNC_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":8.8},{\"unit_id\":\"l\",\"value\":24.8},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":52.0,\"activity\":{\"point_id\":\"1064282\",\"duration\":52,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":70200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":70200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":70200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":70200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":70200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1131694_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.7},{\"unit_id\":\"l\",\"value\":55.404},{\"unit_id\":\"qte\",\"value\":9.0}],\"visits_number\":3,\"minimum_lapse\":72.0,\"activity\":{\"point_id\":\"1131694\",\"duration\":72,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":52200,\"end\":61200,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":52200,\"end\":61200,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":52200,\"end\":61200,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":52200,\"end\":61200,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":52200,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1131694_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.7},{\"unit_id\":\"l\",\"value\":55.404},{\"unit_id\":\"qte\",\"value\":9.0}],\"visits_number\":3,\"minimum_lapse\":72.0,\"activity\":{\"point_id\":\"1131694\",\"duration\":72,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":52200,\"end\":61200,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":52200,\"end\":61200,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":52200,\"end\":61200,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":52200,\"end\":61200,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":52200,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127811_PH _  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":29.76},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":96.0}],\"visits_number\":12,\"minimum_lapse\":384.0,\"activity\":{\"point_id\":\"1127811\",\"duration\":384,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127811_PCP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":29.76},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":96.0}],\"visits_number\":12,\"minimum_lapse\":384.0,\"activity\":{\"point_id\":\"1127811\",\"duration\":384,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1103548_TAP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.7},{\"unit_id\":\"l\",\"value\":37.02},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":12,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1103548\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1148428_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":22.1},{\"unit_id\":\"l\",\"value\":104.652},{\"unit_id\":\"qte\",\"value\":17.0}],\"visits_number\":3,\"minimum_lapse\":136.0,\"activity\":{\"point_id\":\"1148428\",\"duration\":136,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":64800,\"day_index\":0},{\"start\":36000,\"end\":64800,\"day_index\":1},{\"start\":36000,\"end\":64800,\"day_index\":2},{\"start\":36000,\"end\":64800,\"day_index\":3},{\"start\":36000,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1148428_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":22.1},{\"unit_id\":\"l\",\"value\":104.652},{\"unit_id\":\"qte\",\"value\":17.0}],\"visits_number\":3,\"minimum_lapse\":136.0,\"activity\":{\"point_id\":\"1148428\",\"duration\":136,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":64800,\"day_index\":0},{\"start\":36000,\"end\":64800,\"day_index\":1},{\"start\":36000,\"end\":64800,\"day_index\":2},{\"start\":36000,\"end\":64800,\"day_index\":3},{\"start\":36000,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_SAV_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139292_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.9},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1139292\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_CLI_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_PCP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1118656_PH _ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1118656\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004005_TAP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":12.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":26.0,\"activity\":{\"point_id\":\"1004005\",\"duration\":26,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1118656_SAV_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1118656\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119178_LPL_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":250.0}],\"visits_number\":3,\"minimum_lapse\":3250.0,\"activity\":{\"point_id\":\"1119178\",\"duration\":3250,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1123712_PCP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1123712\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1123712_CLI_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1123712\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1123712_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1123712\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1123712_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1123712\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119178_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":250.0}],\"visits_number\":3,\"minimum_lapse\":3250.0,\"activity\":{\"point_id\":\"1119178\",\"duration\":3250,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119178_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":250.0}],\"visits_number\":3,\"minimum_lapse\":3250.0,\"activity\":{\"point_id\":\"1119178\",\"duration\":3250,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119178_CLI_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":250.0}],\"visits_number\":3,\"minimum_lapse\":3250.0,\"activity\":{\"point_id\":\"1119178\",\"duration\":3250,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119178_DIF_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":250.0}],\"visits_number\":3,\"minimum_lapse\":3250.0,\"activity\":{\"point_id\":\"1119178\",\"duration\":3250,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119178_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":250.0}],\"visits_number\":3,\"minimum_lapse\":3250.0,\"activity\":{\"point_id\":\"1119178\",\"duration\":3250,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1118656_PCP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1118656\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1001821_DIF_ 84_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":15.0,\"activity\":{\"point_id\":\"1001821\",\"duration\":15,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005035_LPL_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.64},{\"unit_id\":\"l\",\"value\":16.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":3,\"minimum_lapse\":132.0,\"activity\":{\"point_id\":\"1005035\",\"duration\":132,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030515_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1030515\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":46800,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":46800,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":46800,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":46800,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":46800,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127811_PH _  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":29.76},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":96.0}],\"visits_number\":12,\"minimum_lapse\":384.0,\"activity\":{\"point_id\":\"1127811\",\"duration\":384,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127811_SAV_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":29.76},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":96.0}],\"visits_number\":12,\"minimum_lapse\":384.0,\"activity\":{\"point_id\":\"1127811\",\"duration\":384,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130633_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.8},{\"unit_id\":\"l\",\"value\":94.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1130633\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127811_PCP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":29.76},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":96.0}],\"visits_number\":12,\"minimum_lapse\":384.0,\"activity\":{\"point_id\":\"1127811\",\"duration\":384,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130633_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.8},{\"unit_id\":\"l\",\"value\":94.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1130633\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130633_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.8},{\"unit_id\":\"l\",\"value\":94.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1130633\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130633_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.8},{\"unit_id\":\"l\",\"value\":94.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1130633\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132792_TAP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":16.6},{\"unit_id\":\"l\",\"value\":66.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":6,\"minimum_lapse\":500.0,\"activity\":{\"point_id\":\"1132792\",\"duration\":500,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":61200,\"day_index\":0},{\"start\":21600,\"end\":61200,\"day_index\":1},{\"start\":21600,\"end\":61200,\"day_index\":2},{\"start\":21600,\"end\":61200,\"day_index\":3},{\"start\":21600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130633_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.8},{\"unit_id\":\"l\",\"value\":94.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1130633\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1124356_SNC_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.2},{\"unit_id\":\"l\",\"value\":68.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1124356\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121089_EMP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":19.5},{\"unit_id\":\"l\",\"value\":92.34},{\"unit_id\":\"qte\",\"value\":15.0}],\"visits_number\":6,\"minimum_lapse\":120.0,\"activity\":{\"point_id\":\"1121089\",\"duration\":120,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121089_PCP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":19.5},{\"unit_id\":\"l\",\"value\":92.34},{\"unit_id\":\"qte\",\"value\":15.0}],\"visits_number\":6,\"minimum_lapse\":120.0,\"activity\":{\"point_id\":\"1121089\",\"duration\":120,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1102925_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1102925\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":63000,\"end\":73800,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":63000,\"end\":73800,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":63000,\"end\":73800,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":63000,\"end\":73800,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4},{\"start\":63000,\"end\":73800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1102928_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1102928\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":63000,\"end\":73800,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":63000,\"end\":73800,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":63000,\"end\":73800,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":63000,\"end\":73800,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4},{\"start\":63000,\"end\":73800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1105871_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":41.8},{\"unit_id\":\"l\",\"value\":117.8},{\"unit_id\":\"qte\",\"value\":19.0}],\"visits_number\":6,\"minimum_lapse\":247.0,\"activity\":{\"point_id\":\"1105871\",\"duration\":247,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1105871_CLI_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":41.8},{\"unit_id\":\"l\",\"value\":117.8},{\"unit_id\":\"qte\",\"value\":19.0}],\"visits_number\":6,\"minimum_lapse\":247.0,\"activity\":{\"point_id\":\"1105871\",\"duration\":247,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1105871_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":41.8},{\"unit_id\":\"l\",\"value\":117.8},{\"unit_id\":\"qte\",\"value\":19.0}],\"visits_number\":6,\"minimum_lapse\":247.0,\"activity\":{\"point_id\":\"1105871\",\"duration\":247,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1116088_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":20.29},{\"unit_id\":\"l\",\"value\":37.8},{\"unit_id\":\"qte\",\"value\":64.0}],\"visits_number\":3,\"minimum_lapse\":256.0,\"activity\":{\"point_id\":\"1116088\",\"duration\":256,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1116088_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":20.29},{\"unit_id\":\"l\",\"value\":37.8},{\"unit_id\":\"qte\",\"value\":64.0}],\"visits_number\":3,\"minimum_lapse\":256.0,\"activity\":{\"point_id\":\"1116088\",\"duration\":256,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1105871_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":41.8},{\"unit_id\":\"l\",\"value\":117.8},{\"unit_id\":\"qte\",\"value\":19.0}],\"visits_number\":6,\"minimum_lapse\":247.0,\"activity\":{\"point_id\":\"1105871\",\"duration\":247,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1109290_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":50.6},{\"unit_id\":\"l\",\"value\":142.6},{\"unit_id\":\"qte\",\"value\":23.0}],\"visits_number\":6,\"minimum_lapse\":299.0,\"activity\":{\"point_id\":\"1109290\",\"duration\":299,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1131649_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.36},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":16.0}],\"visits_number\":3,\"minimum_lapse\":64.0,\"activity\":{\"point_id\":\"1131649\",\"duration\":64,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":68400,\"day_index\":0},{\"start\":36000,\"end\":68400,\"day_index\":1},{\"start\":36000,\"end\":68400,\"day_index\":2},{\"start\":36000,\"end\":68400,\"day_index\":3},{\"start\":36000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1131649_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.36},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":16.0}],\"visits_number\":3,\"minimum_lapse\":64.0,\"activity\":{\"point_id\":\"1131649\",\"duration\":64,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":68400,\"day_index\":0},{\"start\":36000,\"end\":68400,\"day_index\":1},{\"start\":36000,\"end\":68400,\"day_index\":2},{\"start\":36000,\"end\":68400,\"day_index\":3},{\"start\":36000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1136697_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":52.8},{\"unit_id\":\"l\",\"value\":148.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":3,\"minimum_lapse\":312.0,\"activity\":{\"point_id\":\"1136697\",\"duration\":312,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1136697_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":52.8},{\"unit_id\":\"l\",\"value\":148.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":3,\"minimum_lapse\":312.0,\"activity\":{\"point_id\":\"1136697\",\"duration\":312,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030517_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.095},{\"unit_id\":\"l\",\"value\":1.53},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1030517\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030348_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":12,\"minimum_lapse\":156.0,\"activity\":{\"point_id\":\"1030348\",\"duration\":156,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1007882_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":26.4},{\"unit_id\":\"l\",\"value\":74.4},{\"unit_id\":\"qte\",\"value\":12.0}],\"visits_number\":6,\"minimum_lapse\":117.0,\"activity\":{\"point_id\":\"1007882\",\"duration\":117,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":43200,\"day_index\":0},{\"start\":21600,\"end\":43200,\"day_index\":1},{\"start\":21600,\"end\":43200,\"day_index\":2},{\"start\":21600,\"end\":43200,\"day_index\":3},{\"start\":21600,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1007882_CLI_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":26.4},{\"unit_id\":\"l\",\"value\":74.4},{\"unit_id\":\"qte\",\"value\":12.0}],\"visits_number\":6,\"minimum_lapse\":117.0,\"activity\":{\"point_id\":\"1007882\",\"duration\":117,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":43200,\"day_index\":0},{\"start\":21600,\"end\":43200,\"day_index\":1},{\"start\":21600,\"end\":43200,\"day_index\":2},{\"start\":21600,\"end\":43200,\"day_index\":3},{\"start\":21600,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1007882_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":26.4},{\"unit_id\":\"l\",\"value\":74.4},{\"unit_id\":\"qte\",\"value\":12.0}],\"visits_number\":6,\"minimum_lapse\":117.0,\"activity\":{\"point_id\":\"1007882\",\"duration\":117,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":43200,\"day_index\":0},{\"start\":21600,\"end\":43200,\"day_index\":1},{\"start\":21600,\"end\":43200,\"day_index\":2},{\"start\":21600,\"end\":43200,\"day_index\":3},{\"start\":21600,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020596_SNC_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.2},{\"unit_id\":\"l\",\"value\":15.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1020596\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030515_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1030515\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":46800,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":46800,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":46800,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":46800,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":46800,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030515_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1030515\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":46800,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":46800,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":46800,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":46800,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":46800,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030515_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1030515\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":46800,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":46800,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":46800,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":46800,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":46800,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030517_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.095},{\"unit_id\":\"l\",\"value\":1.53},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1030517\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005035_DIF_ 84_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.64},{\"unit_id\":\"l\",\"value\":16.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":3,\"minimum_lapse\":132.0,\"activity\":{\"point_id\":\"1005035\",\"duration\":132,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030517_CLI_ 84_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.095},{\"unit_id\":\"l\",\"value\":1.53},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1030517\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030517_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.095},{\"unit_id\":\"l\",\"value\":1.53},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1030517\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1136697_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":52.8},{\"unit_id\":\"l\",\"value\":148.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":3,\"minimum_lapse\":312.0,\"activity\":{\"point_id\":\"1136697\",\"duration\":312,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1136697_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":52.8},{\"unit_id\":\"l\",\"value\":148.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":3,\"minimum_lapse\":312.0,\"activity\":{\"point_id\":\"1136697\",\"duration\":312,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132871_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.8},{\"unit_id\":\"l\",\"value\":94.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1132871\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_PCP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1148306_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.62},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":3,\"minimum_lapse\":88.0,\"activity\":{\"point_id\":\"1148306\",\"duration\":88,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1148306_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.62},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":3,\"minimum_lapse\":88.0,\"activity\":{\"point_id\":\"1148306\",\"duration\":88,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1148306_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.62},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":3,\"minimum_lapse\":88.0,\"activity\":{\"point_id\":\"1148306\",\"duration\":88,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1001140_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":13.2},{\"unit_id\":\"l\",\"value\":37.2},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1001140\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":48600,\"end\":68400,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":48600,\"end\":68400,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":48600,\"end\":68400,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":48600,\"end\":68400,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":48600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030517_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.095},{\"unit_id\":\"l\",\"value\":1.53},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1030517\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139292_SNC_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.9},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1139292\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1136835_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.44},{\"unit_id\":\"l\",\"value\":8.4},{\"unit_id\":\"qte\",\"value\":14.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1136835\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1126467_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1126467\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":30600,\"day_index\":0},{\"start\":21600,\"end\":30600,\"day_index\":1},{\"start\":21600,\"end\":30600,\"day_index\":2},{\"start\":21600,\"end\":30600,\"day_index\":3},{\"start\":21600,\"end\":30600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130633_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.8},{\"unit_id\":\"l\",\"value\":94.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1130633\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1102928_DIF_ 84_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1102928\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":63000,\"end\":73800,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":63000,\"end\":73800,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":63000,\"end\":73800,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":63000,\"end\":73800,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4},{\"start\":63000,\"end\":73800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130633_DIF_ 84_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.8},{\"unit_id\":\"l\",\"value\":94.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1130633\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130633_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.8},{\"unit_id\":\"l\",\"value\":94.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1130633\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1131649_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.36},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":16.0}],\"visits_number\":3,\"minimum_lapse\":64.0,\"activity\":{\"point_id\":\"1131649\",\"duration\":64,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":68400,\"day_index\":0},{\"start\":36000,\"end\":68400,\"day_index\":1},{\"start\":36000,\"end\":68400,\"day_index\":2},{\"start\":36000,\"end\":68400,\"day_index\":3},{\"start\":36000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132792_TAP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":16.6},{\"unit_id\":\"l\",\"value\":66.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":6,\"minimum_lapse\":500.0,\"activity\":{\"point_id\":\"1132792\",\"duration\":500,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":61200,\"day_index\":0},{\"start\":21600,\"end\":61200,\"day_index\":1},{\"start\":21600,\"end\":61200,\"day_index\":2},{\"start\":21600,\"end\":61200,\"day_index\":3},{\"start\":21600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1136697_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":52.8},{\"unit_id\":\"l\",\"value\":148.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":3,\"minimum_lapse\":312.0,\"activity\":{\"point_id\":\"1136697\",\"duration\":312,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1136697_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":52.8},{\"unit_id\":\"l\",\"value\":148.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":3,\"minimum_lapse\":312.0,\"activity\":{\"point_id\":\"1136697\",\"duration\":312,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1131649_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.36},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":16.0}],\"visits_number\":3,\"minimum_lapse\":64.0,\"activity\":{\"point_id\":\"1131649\",\"duration\":64,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":68400,\"day_index\":0},{\"start\":36000,\"end\":68400,\"day_index\":1},{\"start\":36000,\"end\":68400,\"day_index\":2},{\"start\":36000,\"end\":68400,\"day_index\":3},{\"start\":36000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130633_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.8},{\"unit_id\":\"l\",\"value\":94.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1130633\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130633_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.8},{\"unit_id\":\"l\",\"value\":94.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1130633\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130633_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.8},{\"unit_id\":\"l\",\"value\":94.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1130633\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1116088_DIF_ 84_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":20.29},{\"unit_id\":\"l\",\"value\":37.8},{\"unit_id\":\"qte\",\"value\":64.0}],\"visits_number\":3,\"minimum_lapse\":256.0,\"activity\":{\"point_id\":\"1116088\",\"duration\":256,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1105871_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":41.8},{\"unit_id\":\"l\",\"value\":117.8},{\"unit_id\":\"qte\",\"value\":19.0}],\"visits_number\":6,\"minimum_lapse\":247.0,\"activity\":{\"point_id\":\"1105871\",\"duration\":247,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1109290_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":50.6},{\"unit_id\":\"l\",\"value\":142.6},{\"unit_id\":\"qte\",\"value\":23.0}],\"visits_number\":6,\"minimum_lapse\":299.0,\"activity\":{\"point_id\":\"1109290\",\"duration\":299,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121089_PCP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":19.5},{\"unit_id\":\"l\",\"value\":92.34},{\"unit_id\":\"qte\",\"value\":15.0}],\"visits_number\":6,\"minimum_lapse\":120.0,\"activity\":{\"point_id\":\"1121089\",\"duration\":120,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121089_EMP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":19.5},{\"unit_id\":\"l\",\"value\":92.34},{\"unit_id\":\"qte\",\"value\":15.0}],\"visits_number\":6,\"minimum_lapse\":120.0,\"activity\":{\"point_id\":\"1121089\",\"duration\":120,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1124356_SNC_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.2},{\"unit_id\":\"l\",\"value\":68.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1124356\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127811_PCP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":29.76},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":96.0}],\"visits_number\":12,\"minimum_lapse\":384.0,\"activity\":{\"point_id\":\"1127811\",\"duration\":384,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127811_PH _  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":29.76},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":96.0}],\"visits_number\":12,\"minimum_lapse\":384.0,\"activity\":{\"point_id\":\"1127811\",\"duration\":384,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127811_SAV_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":29.76},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":96.0}],\"visits_number\":12,\"minimum_lapse\":384.0,\"activity\":{\"point_id\":\"1127811\",\"duration\":384,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1136697_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":52.8},{\"unit_id\":\"l\",\"value\":148.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":3,\"minimum_lapse\":312.0,\"activity\":{\"point_id\":\"1136697\",\"duration\":312,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1136697_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":52.8},{\"unit_id\":\"l\",\"value\":148.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":3,\"minimum_lapse\":312.0,\"activity\":{\"point_id\":\"1136697\",\"duration\":312,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132871_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.8},{\"unit_id\":\"l\",\"value\":94.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1132871\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1066596_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.32},{\"unit_id\":\"l\",\"value\":13.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":100.0,\"activity\":{\"point_id\":\"1066596\",\"duration\":100,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1080067_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":33.0},{\"unit_id\":\"l\",\"value\":93.0},{\"unit_id\":\"qte\",\"value\":15.0}],\"visits_number\":12,\"minimum_lapse\":195.0,\"activity\":{\"point_id\":\"1080067\",\"duration\":195,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":46800,\"day_index\":0},{\"start\":21600,\"end\":46800,\"day_index\":1},{\"start\":21600,\"end\":46800,\"day_index\":2},{\"start\":21600,\"end\":46800,\"day_index\":3},{\"start\":21600,\"end\":46800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1071805_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":22.0},{\"unit_id\":\"l\",\"value\":62.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":6,\"minimum_lapse\":130.0,\"activity\":{\"point_id\":\"1071805\",\"duration\":130,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":70200,\"day_index\":0},{\"start\":28800,\"end\":70200,\"day_index\":1},{\"start\":28800,\"end\":70200,\"day_index\":2},{\"start\":28800,\"end\":70200,\"day_index\":3},{\"start\":28800,\"end\":70200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1066596_TAP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.32},{\"unit_id\":\"l\",\"value\":13.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":100.0,\"activity\":{\"point_id\":\"1066596\",\"duration\":100,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1066596_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.32},{\"unit_id\":\"l\",\"value\":13.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":100.0,\"activity\":{\"point_id\":\"1066596\",\"duration\":100,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1064291_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.4},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1064291\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":70200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":70200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":70200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":70200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":70200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1064291_SNC_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.4},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1064291\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":70200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":70200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":70200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":70200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":70200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1064291_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.4},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1064291\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":70200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":70200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":70200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":70200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":70200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004005_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":12.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":26.0,\"activity\":{\"point_id\":\"1004005\",\"duration\":26,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1064282_SNC_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":8.8},{\"unit_id\":\"l\",\"value\":24.8},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":52.0,\"activity\":{\"point_id\":\"1064282\",\"duration\":52,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":70200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":70200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":70200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":70200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":70200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1116088_CLI_ 84_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":20.29},{\"unit_id\":\"l\",\"value\":37.8},{\"unit_id\":\"qte\",\"value\":64.0}],\"visits_number\":3,\"minimum_lapse\":256.0,\"activity\":{\"point_id\":\"1116088\",\"duration\":256,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1054022_SNC_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":12,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1054022\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":72000,\"day_index\":0},{\"start\":27000,\"end\":72000,\"day_index\":1},{\"start\":27000,\"end\":72000,\"day_index\":2},{\"start\":27000,\"end\":72000,\"day_index\":3},{\"start\":27000,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030517_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.095},{\"unit_id\":\"l\",\"value\":1.53},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1030517\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_PCP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1148306_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.62},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":3,\"minimum_lapse\":88.0,\"activity\":{\"point_id\":\"1148306\",\"duration\":88,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1148306_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.62},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":3,\"minimum_lapse\":88.0,\"activity\":{\"point_id\":\"1148306\",\"duration\":88,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1148306_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.62},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":3,\"minimum_lapse\":88.0,\"activity\":{\"point_id\":\"1148306\",\"duration\":88,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030348_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":12,\"minimum_lapse\":156.0,\"activity\":{\"point_id\":\"1030348\",\"duration\":156,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1001140_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":13.2},{\"unit_id\":\"l\",\"value\":37.2},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1001140\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":48600,\"end\":68400,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":48600,\"end\":68400,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":48600,\"end\":68400,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":48600,\"end\":68400,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":48600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030517_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.095},{\"unit_id\":\"l\",\"value\":1.53},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1030517\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030517_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.095},{\"unit_id\":\"l\",\"value\":1.53},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1030517\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030517_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.095},{\"unit_id\":\"l\",\"value\":1.53},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1030517\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1041519_CLI_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.59},{\"unit_id\":\"l\",\"value\":2.625},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":28.0,\"activity\":{\"point_id\":\"1041519\",\"duration\":28,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004005_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":12.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":26.0,\"activity\":{\"point_id\":\"1004005\",\"duration\":26,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1116088_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":20.29},{\"unit_id\":\"l\",\"value\":37.8},{\"unit_id\":\"qte\",\"value\":64.0}],\"visits_number\":3,\"minimum_lapse\":256.0,\"activity\":{\"point_id\":\"1116088\",\"duration\":256,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1105871_CLI_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":41.8},{\"unit_id\":\"l\",\"value\":117.8},{\"unit_id\":\"qte\",\"value\":19.0}],\"visits_number\":6,\"minimum_lapse\":247.0,\"activity\":{\"point_id\":\"1105871\",\"duration\":247,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139292_SNC_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.9},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1139292\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139151_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1139151\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":72000,\"day_index\":0},{\"start\":32400,\"end\":72000,\"day_index\":1},{\"start\":32400,\"end\":72000,\"day_index\":2},{\"start\":32400,\"end\":72000,\"day_index\":3},{\"start\":32400,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139151_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1139151\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":72000,\"day_index\":0},{\"start\":32400,\"end\":72000,\"day_index\":1},{\"start\":32400,\"end\":72000,\"day_index\":2},{\"start\":32400,\"end\":72000,\"day_index\":3},{\"start\":32400,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139151_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1139151\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":72000,\"day_index\":0},{\"start\":32400,\"end\":72000,\"day_index\":1},{\"start\":32400,\"end\":72000,\"day_index\":2},{\"start\":32400,\"end\":72000,\"day_index\":3},{\"start\":32400,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005088_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":6.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1005088\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005088_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":6.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1005088\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005088_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":6.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1005088\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139149_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":2.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1139149\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":72000,\"day_index\":0},{\"start\":32400,\"end\":72000,\"day_index\":1},{\"start\":32400,\"end\":72000,\"day_index\":2},{\"start\":32400,\"end\":72000,\"day_index\":3},{\"start\":32400,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_PCP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1147052_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.08},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":68.0}],\"visits_number\":3,\"minimum_lapse\":272.0,\"activity\":{\"point_id\":\"1147052\",\"duration\":272,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1147052_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.08},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":68.0}],\"visits_number\":3,\"minimum_lapse\":272.0,\"activity\":{\"point_id\":\"1147052\",\"duration\":272,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1109631_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1109631\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1109631_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1109631\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1118656_PH _ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1118656\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1118656_SAV_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1118656\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1118656_PCP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1118656\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1104396_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.5},{\"unit_id\":\"l\",\"value\":5.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":1,\"minimum_lapse\":20.0,\"activity\":{\"point_id\":\"1104396\",\"duration\":20,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1104396_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.5},{\"unit_id\":\"l\",\"value\":5.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":1,\"minimum_lapse\":20.0,\"activity\":{\"point_id\":\"1104396\",\"duration\":20,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1103548_TAP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.7},{\"unit_id\":\"l\",\"value\":37.02},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":12,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1103548\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_SAV_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004332_CLI_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.11},{\"unit_id\":\"l\",\"value\":1.125},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004332\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004332_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.11},{\"unit_id\":\"l\",\"value\":1.125},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004332\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004332_LPL_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.11},{\"unit_id\":\"l\",\"value\":1.125},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004332\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004332_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.11},{\"unit_id\":\"l\",\"value\":1.125},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004332\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1080537_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.55},{\"unit_id\":\"l\",\"value\":140.8},{\"unit_id\":\"qte\",\"value\":11.0}],\"visits_number\":3,\"minimum_lapse\":88.0,\"activity\":{\"point_id\":\"1080537\",\"duration\":88,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":61200,\"day_index\":0},{\"start\":34200,\"end\":61200,\"day_index\":1},{\"start\":34200,\"end\":61200,\"day_index\":2},{\"start\":34200,\"end\":61200,\"day_index\":3},{\"start\":34200,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1001821_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":15.0,\"activity\":{\"point_id\":\"1001821\",\"duration\":15,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1001821_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":15.0,\"activity\":{\"point_id\":\"1001821\",\"duration\":15,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1103548_TAP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.7},{\"unit_id\":\"l\",\"value\":37.02},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":12,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1103548\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1102925_DIF_ 84_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1102925\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":63000,\"end\":73800,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":63000,\"end\":73800,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":63000,\"end\":73800,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":63000,\"end\":73800,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4},{\"start\":63000,\"end\":73800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1102925_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1102925\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":63000,\"end\":73800,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":63000,\"end\":73800,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":63000,\"end\":73800,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":63000,\"end\":73800,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4},{\"start\":63000,\"end\":73800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1102928_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1102928\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":63000,\"end\":73800,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":63000,\"end\":73800,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":63000,\"end\":73800,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":63000,\"end\":73800,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4},{\"start\":63000,\"end\":73800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1105871_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":41.8},{\"unit_id\":\"l\",\"value\":117.8},{\"unit_id\":\"qte\",\"value\":19.0}],\"visits_number\":6,\"minimum_lapse\":247.0,\"activity\":{\"point_id\":\"1105871\",\"duration\":247,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1105871_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":41.8},{\"unit_id\":\"l\",\"value\":117.8},{\"unit_id\":\"qte\",\"value\":19.0}],\"visits_number\":6,\"minimum_lapse\":247.0,\"activity\":{\"point_id\":\"1105871\",\"duration\":247,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1080537_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.55},{\"unit_id\":\"l\",\"value\":140.8},{\"unit_id\":\"qte\",\"value\":11.0}],\"visits_number\":3,\"minimum_lapse\":88.0,\"activity\":{\"point_id\":\"1080537\",\"duration\":88,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":61200,\"day_index\":0},{\"start\":34200,\"end\":61200,\"day_index\":1},{\"start\":34200,\"end\":61200,\"day_index\":2},{\"start\":34200,\"end\":61200,\"day_index\":3},{\"start\":34200,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1116088_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":20.29},{\"unit_id\":\"l\",\"value\":37.8},{\"unit_id\":\"qte\",\"value\":64.0}],\"visits_number\":3,\"minimum_lapse\":256.0,\"activity\":{\"point_id\":\"1116088\",\"duration\":256,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1080537_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.55},{\"unit_id\":\"l\",\"value\":140.8},{\"unit_id\":\"qte\",\"value\":11.0}],\"visits_number\":3,\"minimum_lapse\":88.0,\"activity\":{\"point_id\":\"1080537\",\"duration\":88,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":61200,\"day_index\":0},{\"start\":34200,\"end\":61200,\"day_index\":1},{\"start\":34200,\"end\":61200,\"day_index\":2},{\"start\":34200,\"end\":61200,\"day_index\":3},{\"start\":34200,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1052132_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.7},{\"unit_id\":\"l\",\"value\":179.2},{\"unit_id\":\"qte\",\"value\":14.0}],\"visits_number\":3,\"minimum_lapse\":112.0,\"activity\":{\"point_id\":\"1052132\",\"duration\":112,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":61200,\"day_index\":0},{\"start\":34200,\"end\":61200,\"day_index\":1},{\"start\":34200,\"end\":61200,\"day_index\":2},{\"start\":34200,\"end\":61200,\"day_index\":3},{\"start\":34200,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004332_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.11},{\"unit_id\":\"l\",\"value\":1.125},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004332\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004332_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.11},{\"unit_id\":\"l\",\"value\":1.125},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004332\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030348_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":12,\"minimum_lapse\":156.0,\"activity\":{\"point_id\":\"1030348\",\"duration\":156,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1031446_TAP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.89},{\"unit_id\":\"l\",\"value\":19.55},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":100.0,\"activity\":{\"point_id\":\"1031446\",\"duration\":100,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1033652_PCP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":21.0},{\"unit_id\":\"l\",\"value\":256.0},{\"unit_id\":\"qte\",\"value\":20.0}],\"visits_number\":3,\"minimum_lapse\":160.0,\"activity\":{\"point_id\":\"1033652\",\"duration\":160,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":45000,\"day_index\":0},{\"start\":30600,\"end\":45000,\"day_index\":1},{\"start\":30600,\"end\":45000,\"day_index\":2},{\"start\":30600,\"end\":45000,\"day_index\":3},{\"start\":30600,\"end\":45000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1033652_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":21.0},{\"unit_id\":\"l\",\"value\":256.0},{\"unit_id\":\"qte\",\"value\":20.0}],\"visits_number\":3,\"minimum_lapse\":160.0,\"activity\":{\"point_id\":\"1033652\",\"duration\":160,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":45000,\"day_index\":0},{\"start\":30600,\"end\":45000,\"day_index\":1},{\"start\":30600,\"end\":45000,\"day_index\":2},{\"start\":30600,\"end\":45000,\"day_index\":3},{\"start\":30600,\"end\":45000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1052132_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.7},{\"unit_id\":\"l\",\"value\":179.2},{\"unit_id\":\"qte\",\"value\":14.0}],\"visits_number\":3,\"minimum_lapse\":112.0,\"activity\":{\"point_id\":\"1052132\",\"duration\":112,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":61200,\"day_index\":0},{\"start\":34200,\"end\":61200,\"day_index\":1},{\"start\":34200,\"end\":61200,\"day_index\":2},{\"start\":34200,\"end\":61200,\"day_index\":3},{\"start\":34200,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1054022_SNC_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":12,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1054022\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":72000,\"day_index\":0},{\"start\":27000,\"end\":72000,\"day_index\":1},{\"start\":27000,\"end\":72000,\"day_index\":2},{\"start\":27000,\"end\":72000,\"day_index\":3},{\"start\":27000,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1052132_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.7},{\"unit_id\":\"l\",\"value\":179.2},{\"unit_id\":\"qte\",\"value\":14.0}],\"visits_number\":3,\"minimum_lapse\":112.0,\"activity\":{\"point_id\":\"1052132\",\"duration\":112,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":61200,\"day_index\":0},{\"start\":34200,\"end\":61200,\"day_index\":1},{\"start\":34200,\"end\":61200,\"day_index\":2},{\"start\":34200,\"end\":61200,\"day_index\":3},{\"start\":34200,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1080067_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":33.0},{\"unit_id\":\"l\",\"value\":93.0},{\"unit_id\":\"qte\",\"value\":15.0}],\"visits_number\":12,\"minimum_lapse\":195.0,\"activity\":{\"point_id\":\"1080067\",\"duration\":195,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":46800,\"day_index\":0},{\"start\":21600,\"end\":46800,\"day_index\":1},{\"start\":21600,\"end\":46800,\"day_index\":2},{\"start\":21600,\"end\":46800,\"day_index\":3},{\"start\":21600,\"end\":46800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130723_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.45},{\"unit_id\":\"l\",\"value\":115.2},{\"unit_id\":\"qte\",\"value\":9.0}],\"visits_number\":3,\"minimum_lapse\":72.0,\"activity\":{\"point_id\":\"1130723\",\"duration\":72,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004005_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":12.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":26.0,\"activity\":{\"point_id\":\"1004005\",\"duration\":26,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1007882_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":26.4},{\"unit_id\":\"l\",\"value\":74.4},{\"unit_id\":\"qte\",\"value\":12.0}],\"visits_number\":6,\"minimum_lapse\":117.0,\"activity\":{\"point_id\":\"1007882\",\"duration\":117,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":43200,\"day_index\":0},{\"start\":21600,\"end\":43200,\"day_index\":1},{\"start\":21600,\"end\":43200,\"day_index\":2},{\"start\":21600,\"end\":43200,\"day_index\":3},{\"start\":21600,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099009_DIF_ 84_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":1,\"minimum_lapse\":160.0,\"activity\":{\"point_id\":\"1099009\",\"duration\":160,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099009_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":1,\"minimum_lapse\":160.0,\"activity\":{\"point_id\":\"1099009\",\"duration\":160,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099009_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":1,\"minimum_lapse\":160.0,\"activity\":{\"point_id\":\"1099009\",\"duration\":160,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099009_CLI_168_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":1,\"minimum_lapse\":160.0,\"activity\":{\"point_id\":\"1099009\",\"duration\":160,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1103548_TAP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.7},{\"unit_id\":\"l\",\"value\":37.02},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":12,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1103548\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099009_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":1,\"minimum_lapse\":160.0,\"activity\":{\"point_id\":\"1099009\",\"duration\":160,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1105871_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":41.8},{\"unit_id\":\"l\",\"value\":117.8},{\"unit_id\":\"qte\",\"value\":19.0}],\"visits_number\":6,\"minimum_lapse\":247.0,\"activity\":{\"point_id\":\"1105871\",\"duration\":247,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1109290_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":50.6},{\"unit_id\":\"l\",\"value\":142.6},{\"unit_id\":\"qte\",\"value\":23.0}],\"visits_number\":6,\"minimum_lapse\":299.0,\"activity\":{\"point_id\":\"1109290\",\"duration\":299,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099009_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":1,\"minimum_lapse\":160.0,\"activity\":{\"point_id\":\"1099009\",\"duration\":160,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1095726_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1095726\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1095726_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1095726\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1095726_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1095726\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030348_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":12,\"minimum_lapse\":156.0,\"activity\":{\"point_id\":\"1030348\",\"duration\":156,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005056_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":12.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":26.0,\"activity\":{\"point_id\":\"1005056\",\"duration\":26,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005056_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":12.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":26.0,\"activity\":{\"point_id\":\"1005056\",\"duration\":26,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1033652_PCP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":21.0},{\"unit_id\":\"l\",\"value\":256.0},{\"unit_id\":\"qte\",\"value\":20.0}],\"visits_number\":3,\"minimum_lapse\":160.0,\"activity\":{\"point_id\":\"1033652\",\"duration\":160,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":45000,\"day_index\":0},{\"start\":30600,\"end\":45000,\"day_index\":1},{\"start\":30600,\"end\":45000,\"day_index\":2},{\"start\":30600,\"end\":45000,\"day_index\":3},{\"start\":30600,\"end\":45000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1054022_SNC_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":12,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1054022\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":72000,\"day_index\":0},{\"start\":27000,\"end\":72000,\"day_index\":1},{\"start\":27000,\"end\":72000,\"day_index\":2},{\"start\":27000,\"end\":72000,\"day_index\":3},{\"start\":27000,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1080067_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":33.0},{\"unit_id\":\"l\",\"value\":93.0},{\"unit_id\":\"qte\",\"value\":15.0}],\"visits_number\":12,\"minimum_lapse\":195.0,\"activity\":{\"point_id\":\"1080067\",\"duration\":195,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":46800,\"day_index\":0},{\"start\":21600,\"end\":46800,\"day_index\":1},{\"start\":21600,\"end\":46800,\"day_index\":2},{\"start\":21600,\"end\":46800,\"day_index\":3},{\"start\":21600,\"end\":46800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1080067_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":33.0},{\"unit_id\":\"l\",\"value\":93.0},{\"unit_id\":\"qte\",\"value\":15.0}],\"visits_number\":12,\"minimum_lapse\":195.0,\"activity\":{\"point_id\":\"1080067\",\"duration\":195,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":46800,\"day_index\":0},{\"start\":21600,\"end\":46800,\"day_index\":1},{\"start\":21600,\"end\":46800,\"day_index\":2},{\"start\":21600,\"end\":46800,\"day_index\":3},{\"start\":21600,\"end\":46800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1095726_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1095726\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1095726_LPL_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1095726\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121089_EMP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":19.5},{\"unit_id\":\"l\",\"value\":92.34},{\"unit_id\":\"qte\",\"value\":15.0}],\"visits_number\":6,\"minimum_lapse\":120.0,\"activity\":{\"point_id\":\"1121089\",\"duration\":120,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121089_PCP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":19.5},{\"unit_id\":\"l\",\"value\":92.34},{\"unit_id\":\"qte\",\"value\":15.0}],\"visits_number\":6,\"minimum_lapse\":120.0,\"activity\":{\"point_id\":\"1121089\",\"duration\":120,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121089_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":19.5},{\"unit_id\":\"l\",\"value\":92.34},{\"unit_id\":\"qte\",\"value\":15.0}],\"visits_number\":6,\"minimum_lapse\":120.0,\"activity\":{\"point_id\":\"1121089\",\"duration\":120,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1122952_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":15.4},{\"unit_id\":\"l\",\"value\":43.4},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":91.0,\"activity\":{\"point_id\":\"1122952\",\"duration\":91,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1126324_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.45},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":45.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1126324\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":36000,\"day_index\":0},{\"start\":32400,\"end\":36000,\"day_index\":1},{\"start\":32400,\"end\":36000,\"day_index\":2},{\"start\":32400,\"end\":36000,\"day_index\":3},{\"start\":32400,\"end\":36000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1126324_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.45},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":45.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1126324\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":36000,\"day_index\":0},{\"start\":32400,\"end\":36000,\"day_index\":1},{\"start\":32400,\"end\":36000,\"day_index\":2},{\"start\":32400,\"end\":36000,\"day_index\":3},{\"start\":32400,\"end\":36000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1126324_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.45},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":45.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1126324\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":36000,\"day_index\":0},{\"start\":32400,\"end\":36000,\"day_index\":1},{\"start\":32400,\"end\":36000,\"day_index\":2},{\"start\":32400,\"end\":36000,\"day_index\":3},{\"start\":32400,\"end\":36000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127811_PH _  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":29.76},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":96.0}],\"visits_number\":12,\"minimum_lapse\":384.0,\"activity\":{\"point_id\":\"1127811\",\"duration\":384,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127811_SAV_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":29.76},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":96.0}],\"visits_number\":12,\"minimum_lapse\":384.0,\"activity\":{\"point_id\":\"1127811\",\"duration\":384,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1126467_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1126467\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":30600,\"day_index\":0},{\"start\":21600,\"end\":30600,\"day_index\":1},{\"start\":21600,\"end\":30600,\"day_index\":2},{\"start\":21600,\"end\":30600,\"day_index\":3},{\"start\":21600,\"end\":30600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1126467_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1126467\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":30600,\"day_index\":0},{\"start\":21600,\"end\":30600,\"day_index\":1},{\"start\":21600,\"end\":30600,\"day_index\":2},{\"start\":21600,\"end\":30600,\"day_index\":3},{\"start\":21600,\"end\":30600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130723_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.45},{\"unit_id\":\"l\",\"value\":115.2},{\"unit_id\":\"qte\",\"value\":9.0}],\"visits_number\":3,\"minimum_lapse\":72.0,\"activity\":{\"point_id\":\"1130723\",\"duration\":72,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132792_TAP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":16.6},{\"unit_id\":\"l\",\"value\":66.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":6,\"minimum_lapse\":500.0,\"activity\":{\"point_id\":\"1132792\",\"duration\":500,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":61200,\"day_index\":0},{\"start\":21600,\"end\":61200,\"day_index\":1},{\"start\":21600,\"end\":61200,\"day_index\":2},{\"start\":21600,\"end\":61200,\"day_index\":3},{\"start\":21600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1126324_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.45},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":45.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1126324\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":36000,\"day_index\":0},{\"start\":32400,\"end\":36000,\"day_index\":1},{\"start\":32400,\"end\":36000,\"day_index\":2},{\"start\":32400,\"end\":36000,\"day_index\":3},{\"start\":32400,\"end\":36000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030348_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":12,\"minimum_lapse\":156.0,\"activity\":{\"point_id\":\"1030348\",\"duration\":156,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127811_PCP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":29.76},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":96.0}],\"visits_number\":12,\"minimum_lapse\":384.0,\"activity\":{\"point_id\":\"1127811\",\"duration\":384,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1124513_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":2.94},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1124513\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":68400,\"day_index\":0},{\"start\":36000,\"end\":68400,\"day_index\":1},{\"start\":36000,\"end\":68400,\"day_index\":2},{\"start\":36000,\"end\":68400,\"day_index\":3},{\"start\":36000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1122952_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":15.4},{\"unit_id\":\"l\",\"value\":43.4},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":91.0,\"activity\":{\"point_id\":\"1122952\",\"duration\":91,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1124103_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.9},{\"unit_id\":\"l\",\"value\":18.468},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1124103\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1124103_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.9},{\"unit_id\":\"l\",\"value\":18.468},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1124103\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1122952_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":15.4},{\"unit_id\":\"l\",\"value\":43.4},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":91.0,\"activity\":{\"point_id\":\"1122952\",\"duration\":91,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1124356_SNC_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.2},{\"unit_id\":\"l\",\"value\":68.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1124356\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1124103_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.9},{\"unit_id\":\"l\",\"value\":18.468},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1124103\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1124513_BOB_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":2.94},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1124513\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":68400,\"day_index\":0},{\"start\":36000,\"end\":68400,\"day_index\":1},{\"start\":36000,\"end\":68400,\"day_index\":2},{\"start\":36000,\"end\":68400,\"day_index\":3},{\"start\":36000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1124513_CLI_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":2.94},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1124513\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":68400,\"day_index\":0},{\"start\":36000,\"end\":68400,\"day_index\":1},{\"start\":36000,\"end\":68400,\"day_index\":2},{\"start\":36000,\"end\":68400,\"day_index\":3},{\"start\":36000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1124513_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":2.94},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1124513\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":68400,\"day_index\":0},{\"start\":36000,\"end\":68400,\"day_index\":1},{\"start\":36000,\"end\":68400,\"day_index\":2},{\"start\":36000,\"end\":68400,\"day_index\":3},{\"start\":36000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1124513_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":2.94},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1124513\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":68400,\"day_index\":0},{\"start\":36000,\"end\":68400,\"day_index\":1},{\"start\":36000,\"end\":68400,\"day_index\":2},{\"start\":36000,\"end\":68400,\"day_index\":3},{\"start\":36000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004005_CLI_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":12.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":26.0,\"activity\":{\"point_id\":\"1004005\",\"duration\":26,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030348_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":12,\"minimum_lapse\":156.0,\"activity\":{\"point_id\":\"1030348\",\"duration\":156,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1031446_TAP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.89},{\"unit_id\":\"l\",\"value\":19.55},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":100.0,\"activity\":{\"point_id\":\"1031446\",\"duration\":100,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137046_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.3},{\"unit_id\":\"l\",\"value\":76.8},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1137046\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1131694_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.7},{\"unit_id\":\"l\",\"value\":55.404},{\"unit_id\":\"qte\",\"value\":9.0}],\"visits_number\":3,\"minimum_lapse\":72.0,\"activity\":{\"point_id\":\"1131694\",\"duration\":72,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":52200,\"end\":61200,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":52200,\"end\":61200,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":52200,\"end\":61200,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":52200,\"end\":61200,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":52200,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1131694_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.7},{\"unit_id\":\"l\",\"value\":55.404},{\"unit_id\":\"qte\",\"value\":9.0}],\"visits_number\":3,\"minimum_lapse\":72.0,\"activity\":{\"point_id\":\"1131694\",\"duration\":72,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":52200,\"end\":61200,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":52200,\"end\":61200,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":52200,\"end\":61200,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":52200,\"end\":61200,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":52200,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137046_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.3},{\"unit_id\":\"l\",\"value\":76.8},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1137046\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1131694_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.7},{\"unit_id\":\"l\",\"value\":55.404},{\"unit_id\":\"qte\",\"value\":9.0}],\"visits_number\":3,\"minimum_lapse\":72.0,\"activity\":{\"point_id\":\"1131694\",\"duration\":72,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":52200,\"end\":61200,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":52200,\"end\":61200,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":52200,\"end\":61200,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":52200,\"end\":61200,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":52200,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127811_PCP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":29.76},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":96.0}],\"visits_number\":12,\"minimum_lapse\":384.0,\"activity\":{\"point_id\":\"1127811\",\"duration\":384,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1123712_PCP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1123712\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1123712_CLI_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1123712\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127811_PH _  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":29.76},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":96.0}],\"visits_number\":12,\"minimum_lapse\":384.0,\"activity\":{\"point_id\":\"1127811\",\"duration\":384,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137046_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.3},{\"unit_id\":\"l\",\"value\":76.8},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1137046\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005035_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.64},{\"unit_id\":\"l\",\"value\":16.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":3,\"minimum_lapse\":132.0,\"activity\":{\"point_id\":\"1005035\",\"duration\":132,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005035_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.64},{\"unit_id\":\"l\",\"value\":16.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":3,\"minimum_lapse\":132.0,\"activity\":{\"point_id\":\"1005035\",\"duration\":132,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1007882_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":26.4},{\"unit_id\":\"l\",\"value\":74.4},{\"unit_id\":\"qte\",\"value\":12.0}],\"visits_number\":6,\"minimum_lapse\":117.0,\"activity\":{\"point_id\":\"1007882\",\"duration\":117,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":43200,\"day_index\":0},{\"start\":21600,\"end\":43200,\"day_index\":1},{\"start\":21600,\"end\":43200,\"day_index\":2},{\"start\":21600,\"end\":43200,\"day_index\":3},{\"start\":21600,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1007882_CLI_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":26.4},{\"unit_id\":\"l\",\"value\":74.4},{\"unit_id\":\"qte\",\"value\":12.0}],\"visits_number\":6,\"minimum_lapse\":117.0,\"activity\":{\"point_id\":\"1007882\",\"duration\":117,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":43200,\"day_index\":0},{\"start\":21600,\"end\":43200,\"day_index\":1},{\"start\":21600,\"end\":43200,\"day_index\":2},{\"start\":21600,\"end\":43200,\"day_index\":3},{\"start\":21600,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020596_SNC_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.2},{\"unit_id\":\"l\",\"value\":15.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1020596\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030515_SNC_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1030515\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":46800,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":46800,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":46800,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":46800,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":46800,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030515_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1030515\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":46800,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":46800,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":46800,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":46800,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":46800,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030515_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1030515\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":46800,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":46800,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":46800,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":46800,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":46800,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030515_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1030515\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":46800,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":46800,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":46800,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":46800,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":46800,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005035_LPL_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.64},{\"unit_id\":\"l\",\"value\":16.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":3,\"minimum_lapse\":132.0,\"activity\":{\"point_id\":\"1005035\",\"duration\":132,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004005_TAP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":12.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":26.0,\"activity\":{\"point_id\":\"1004005\",\"duration\":26,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1123712_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1123712\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1123712_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1123712\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119178_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":250.0}],\"visits_number\":3,\"minimum_lapse\":3250.0,\"activity\":{\"point_id\":\"1119178\",\"duration\":3250,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119178_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":250.0}],\"visits_number\":3,\"minimum_lapse\":3250.0,\"activity\":{\"point_id\":\"1119178\",\"duration\":3250,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_PCP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139292_SNC_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.9},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1139292\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139292_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.9},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1139292\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139292_CLI_ 84_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.9},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1139292\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139292_DIF_ 84_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.9},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1139292\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1148428_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":22.1},{\"unit_id\":\"l\",\"value\":104.652},{\"unit_id\":\"qte\",\"value\":17.0}],\"visits_number\":3,\"minimum_lapse\":136.0,\"activity\":{\"point_id\":\"1148428\",\"duration\":136,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":64800,\"day_index\":0},{\"start\":36000,\"end\":64800,\"day_index\":1},{\"start\":36000,\"end\":64800,\"day_index\":2},{\"start\":36000,\"end\":64800,\"day_index\":3},{\"start\":36000,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1031446_ASC_ 84_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.89},{\"unit_id\":\"l\",\"value\":19.55},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":100.0,\"activity\":{\"point_id\":\"1031446\",\"duration\":100,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139292_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.9},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1139292\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004332_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.11},{\"unit_id\":\"l\",\"value\":1.125},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004332\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_CLI_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1148428_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":22.1},{\"unit_id\":\"l\",\"value\":104.652},{\"unit_id\":\"qte\",\"value\":17.0}],\"visits_number\":3,\"minimum_lapse\":136.0,\"activity\":{\"point_id\":\"1148428\",\"duration\":136,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":64800,\"day_index\":0},{\"start\":36000,\"end\":64800,\"day_index\":1},{\"start\":36000,\"end\":64800,\"day_index\":2},{\"start\":36000,\"end\":64800,\"day_index\":3},{\"start\":36000,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119178_LPL_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":250.0}],\"visits_number\":3,\"minimum_lapse\":3250.0,\"activity\":{\"point_id\":\"1119178\",\"duration\":3250,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119178_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":250.0}],\"visits_number\":3,\"minimum_lapse\":3250.0,\"activity\":{\"point_id\":\"1119178\",\"duration\":3250,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119178_DIF_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":250.0}],\"visits_number\":3,\"minimum_lapse\":3250.0,\"activity\":{\"point_id\":\"1119178\",\"duration\":3250,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119178_CLI_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":250.0}],\"visits_number\":3,\"minimum_lapse\":3250.0,\"activity\":{\"point_id\":\"1119178\",\"duration\":3250,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1118656_PCP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1118656\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1118656_SAV_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1118656\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1118656_PH _ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.88},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":48.0}],\"visits_number\":6,\"minimum_lapse\":192.0,\"activity\":{\"point_id\":\"1118656\",\"duration\":192,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1103548_TAP_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.7},{\"unit_id\":\"l\",\"value\":37.02},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":12,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1103548\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1148428_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":22.1},{\"unit_id\":\"l\",\"value\":104.652},{\"unit_id\":\"qte\",\"value\":17.0}],\"visits_number\":3,\"minimum_lapse\":136.0,\"activity\":{\"point_id\":\"1148428\",\"duration\":136,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":64800,\"day_index\":0},{\"start\":36000,\"end\":64800,\"day_index\":1},{\"start\":36000,\"end\":64800,\"day_index\":2},{\"start\":36000,\"end\":64800,\"day_index\":3},{\"start\":36000,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142617_SAV_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1142617\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1139292_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.9},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1139292\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1148428_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":22.1},{\"unit_id\":\"l\",\"value\":104.652},{\"unit_id\":\"qte\",\"value\":17.0}],\"visits_number\":3,\"minimum_lapse\":136.0,\"activity\":{\"point_id\":\"1148428\",\"duration\":136,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":64800,\"day_index\":0},{\"start\":36000,\"end\":64800,\"day_index\":1},{\"start\":36000,\"end\":64800,\"day_index\":2},{\"start\":36000,\"end\":64800,\"day_index\":3},{\"start\":36000,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1031446_TAP_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.89},{\"unit_id\":\"l\",\"value\":19.55},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":100.0,\"activity\":{\"point_id\":\"1031446\",\"duration\":100,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1131394_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.475},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1131394\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1131394_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.475},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1131394\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133951_SNC_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.6},{\"unit_id\":\"l\",\"value\":136.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":6,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1133951\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133951_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.6},{\"unit_id\":\"l\",\"value\":136.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":6,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1133951\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137715_BOB_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":22.0},{\"unit_id\":\"l\",\"value\":62.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":130.0,\"activity\":{\"point_id\":\"1137715\",\"duration\":130,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137715_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":22.0},{\"unit_id\":\"l\",\"value\":62.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":130.0,\"activity\":{\"point_id\":\"1137715\",\"duration\":130,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132589_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":48.4},{\"unit_id\":\"l\",\"value\":136.4},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":12,\"minimum_lapse\":286.0,\"activity\":{\"point_id\":\"1132589\",\"duration\":286,\"setup_duration\":120,\"timewindows\":[{\"start\":23400,\"end\":30600,\"day_index\":0},{\"start\":23400,\"end\":30600,\"day_index\":1},{\"start\":23400,\"end\":30600,\"day_index\":2},{\"start\":23400,\"end\":30600,\"day_index\":3},{\"start\":23400,\"end\":30600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1131394_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.475},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1131394\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137715_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":22.0},{\"unit_id\":\"l\",\"value\":62.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":130.0,\"activity\":{\"point_id\":\"1137715\",\"duration\":130,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1145751_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1145751\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1145751_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1145751\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1145751_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1145751\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070749_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.35},{\"unit_id\":\"l\",\"value\":89.6},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1070749\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070735_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.7},{\"unit_id\":\"l\",\"value\":7.0},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":28.0,\"activity\":{\"point_id\":\"1070735\",\"duration\":28,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1007287_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":15.5},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":50.0}],\"visits_number\":3,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1007287\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_PH _ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1143914_TAP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":8.94},{\"unit_id\":\"l\",\"value\":33.9},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1143914\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144594_TAP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.64},{\"unit_id\":\"l\",\"value\":26.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1144594\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127546_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.8},{\"unit_id\":\"l\",\"value\":0.75},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1127546\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127546_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.8},{\"unit_id\":\"l\",\"value\":0.75},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1127546\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127546_BOB_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.8},{\"unit_id\":\"l\",\"value\":0.75},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1127546\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1123348_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1123348\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1103574_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.305},{\"unit_id\":\"l\",\"value\":14.7},{\"unit_id\":\"qte\",\"value\":23.0}],\"visits_number\":3,\"minimum_lapse\":92.0,\"activity\":{\"point_id\":\"1103574\",\"duration\":92,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1087334_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":60.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1087334\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1087334_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":60.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1087334\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1088315_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.8},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1088315\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1088315_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.8},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1088315\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1087334_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":60.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1087334\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1054230_DIF_ 84_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":1,\"minimum_lapse\":80.0,\"activity\":{\"point_id\":\"1054230\",\"duration\":80,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":61200,\"day_index\":0},{\"start\":36000,\"end\":61200,\"day_index\":1},{\"start\":36000,\"end\":61200,\"day_index\":2},{\"start\":36000,\"end\":61200,\"day_index\":3},{\"start\":36000,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1058540_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.2},{\"unit_id\":\"l\",\"value\":68.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1058540\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":72000,\"day_index\":0},{\"start\":30600,\"end\":72000,\"day_index\":1},{\"start\":30600,\"end\":72000,\"day_index\":2},{\"start\":30600,\"end\":72000,\"day_index\":3},{\"start\":30600,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1054230_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":1,\"minimum_lapse\":80.0,\"activity\":{\"point_id\":\"1054230\",\"duration\":80,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":61200,\"day_index\":0},{\"start\":36000,\"end\":61200,\"day_index\":1},{\"start\":36000,\"end\":61200,\"day_index\":2},{\"start\":36000,\"end\":61200,\"day_index\":3},{\"start\":36000,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1103574_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.305},{\"unit_id\":\"l\",\"value\":14.7},{\"unit_id\":\"qte\",\"value\":23.0}],\"visits_number\":3,\"minimum_lapse\":92.0,\"activity\":{\"point_id\":\"1103574\",\"duration\":92,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070749_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.35},{\"unit_id\":\"l\",\"value\":89.6},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1070749\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1106440_TAP_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.32},{\"unit_id\":\"l\",\"value\":13.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":12,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1106440\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1120609_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1120609\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1123348_EMP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1123348\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1123348_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1123348\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1123348_CLI_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1123348\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119750_EMP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":39.0},{\"unit_id\":\"l\",\"value\":184.68},{\"unit_id\":\"qte\",\"value\":30.0}],\"visits_number\":6,\"minimum_lapse\":240.0,\"activity\":{\"point_id\":\"1119750\",\"duration\":240,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":39600,\"day_index\":0},{\"start\":51300,\"end\":56700,\"day_index\":0},{\"start\":30600,\"end\":39600,\"day_index\":1},{\"start\":51300,\"end\":56700,\"day_index\":1},{\"start\":30600,\"end\":39600,\"day_index\":2},{\"start\":51300,\"end\":56700,\"day_index\":2},{\"start\":30600,\"end\":39600,\"day_index\":3},{\"start\":51300,\"end\":56700,\"day_index\":3},{\"start\":30600,\"end\":39600,\"day_index\":4},{\"start\":51300,\"end\":56700,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107065_SAV_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1107065\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":39600,\"end\":68400,\"day_index\":0},{\"start\":39600,\"end\":68400,\"day_index\":1},{\"start\":39600,\"end\":68400,\"day_index\":2},{\"start\":39600,\"end\":68400,\"day_index\":3},{\"start\":39600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107065_SNC_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1107065\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":39600,\"end\":68400,\"day_index\":0},{\"start\":39600,\"end\":68400,\"day_index\":1},{\"start\":39600,\"end\":68400,\"day_index\":2},{\"start\":39600,\"end\":68400,\"day_index\":3},{\"start\":39600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119750_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":39.0},{\"unit_id\":\"l\",\"value\":184.68},{\"unit_id\":\"qte\",\"value\":30.0}],\"visits_number\":6,\"minimum_lapse\":240.0,\"activity\":{\"point_id\":\"1119750\",\"duration\":240,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":39600,\"day_index\":0},{\"start\":51300,\"end\":56700,\"day_index\":0},{\"start\":30600,\"end\":39600,\"day_index\":1},{\"start\":51300,\"end\":56700,\"day_index\":1},{\"start\":30600,\"end\":39600,\"day_index\":2},{\"start\":51300,\"end\":56700,\"day_index\":2},{\"start\":30600,\"end\":39600,\"day_index\":3},{\"start\":51300,\"end\":56700,\"day_index\":3},{\"start\":30600,\"end\":39600,\"day_index\":4},{\"start\":51300,\"end\":56700,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119750_SAV_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":39.0},{\"unit_id\":\"l\",\"value\":184.68},{\"unit_id\":\"qte\",\"value\":30.0}],\"visits_number\":6,\"minimum_lapse\":240.0,\"activity\":{\"point_id\":\"1119750\",\"duration\":240,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":39600,\"day_index\":0},{\"start\":51300,\"end\":56700,\"day_index\":0},{\"start\":30600,\"end\":39600,\"day_index\":1},{\"start\":51300,\"end\":56700,\"day_index\":1},{\"start\":30600,\"end\":39600,\"day_index\":2},{\"start\":51300,\"end\":56700,\"day_index\":2},{\"start\":30600,\"end\":39600,\"day_index\":3},{\"start\":51300,\"end\":56700,\"day_index\":3},{\"start\":30600,\"end\":39600,\"day_index\":4},{\"start\":51300,\"end\":56700,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1120609_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1120609\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1120609_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1120609\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1054230_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":1,\"minimum_lapse\":80.0,\"activity\":{\"point_id\":\"1054230\",\"duration\":80,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":61200,\"day_index\":0},{\"start\":36000,\"end\":61200,\"day_index\":1},{\"start\":36000,\"end\":61200,\"day_index\":2},{\"start\":36000,\"end\":61200,\"day_index\":3},{\"start\":36000,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070749_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.35},{\"unit_id\":\"l\",\"value\":89.6},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1070749\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1096970_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.0},{\"unit_id\":\"l\",\"value\":10.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1096970\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1124357_SNC_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1124357\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130453_BOB_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":52.8},{\"unit_id\":\"l\",\"value\":148.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":6,\"minimum_lapse\":312.0,\"activity\":{\"point_id\":\"1130453\",\"duration\":312,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132589_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":48.4},{\"unit_id\":\"l\",\"value\":136.4},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":12,\"minimum_lapse\":286.0,\"activity\":{\"point_id\":\"1132589\",\"duration\":286,\"setup_duration\":120,\"timewindows\":[{\"start\":23400,\"end\":30600,\"day_index\":0},{\"start\":23400,\"end\":30600,\"day_index\":1},{\"start\":23400,\"end\":30600,\"day_index\":2},{\"start\":23400,\"end\":30600,\"day_index\":3},{\"start\":23400,\"end\":30600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121283_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.05},{\"unit_id\":\"l\",\"value\":12.8},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1121283\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":64800,\"day_index\":0},{\"start\":36000,\"end\":64800,\"day_index\":1},{\"start\":36000,\"end\":64800,\"day_index\":2},{\"start\":36000,\"end\":64800,\"day_index\":3},{\"start\":36000,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132589_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":48.4},{\"unit_id\":\"l\",\"value\":136.4},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":12,\"minimum_lapse\":286.0,\"activity\":{\"point_id\":\"1132589\",\"duration\":286,\"setup_duration\":120,\"timewindows\":[{\"start\":23400,\"end\":30600,\"day_index\":0},{\"start\":23400,\"end\":30600,\"day_index\":1},{\"start\":23400,\"end\":30600,\"day_index\":2},{\"start\":23400,\"end\":30600,\"day_index\":3},{\"start\":23400,\"end\":30600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1143992_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1143992\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":62100,\"day_index\":0},{\"start\":32400,\"end\":62100,\"day_index\":1},{\"start\":32400,\"end\":62100,\"day_index\":2},{\"start\":32400,\"end\":62100,\"day_index\":3},{\"start\":32400,\"end\":62100,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1143992_EMP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1143992\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":62100,\"day_index\":0},{\"start\":32400,\"end\":62100,\"day_index\":1},{\"start\":32400,\"end\":62100,\"day_index\":2},{\"start\":32400,\"end\":62100,\"day_index\":3},{\"start\":32400,\"end\":62100,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020782_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":68.77},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":217.0}],\"visits_number\":6,\"minimum_lapse\":556.0,\"activity\":{\"point_id\":\"1020782\",\"duration\":556,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":57600,\"day_index\":0},{\"start\":27000,\"end\":57600,\"day_index\":1},{\"start\":27000,\"end\":57600,\"day_index\":2},{\"start\":27000,\"end\":57600,\"day_index\":3},{\"start\":27000,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1143992_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1143992\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":62100,\"day_index\":0},{\"start\":32400,\"end\":62100,\"day_index\":1},{\"start\":32400,\"end\":62100,\"day_index\":2},{\"start\":32400,\"end\":62100,\"day_index\":3},{\"start\":32400,\"end\":62100,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121283_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.05},{\"unit_id\":\"l\",\"value\":12.8},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1121283\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":64800,\"day_index\":0},{\"start\":36000,\"end\":64800,\"day_index\":1},{\"start\":36000,\"end\":64800,\"day_index\":2},{\"start\":36000,\"end\":64800,\"day_index\":3},{\"start\":36000,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121283_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.05},{\"unit_id\":\"l\",\"value\":12.8},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1121283\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":64800,\"day_index\":0},{\"start\":36000,\"end\":64800,\"day_index\":1},{\"start\":36000,\"end\":64800,\"day_index\":2},{\"start\":36000,\"end\":64800,\"day_index\":3},{\"start\":36000,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121283_BOB_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.05},{\"unit_id\":\"l\",\"value\":12.8},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1121283\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":64800,\"day_index\":0},{\"start\":36000,\"end\":64800,\"day_index\":1},{\"start\":36000,\"end\":64800,\"day_index\":2},{\"start\":36000,\"end\":64800,\"day_index\":3},{\"start\":36000,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1109136_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1109136\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_EMP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_CLI_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1109136_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1109136\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_TAP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1109136_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1109136\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020782_SAV_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":68.77},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":217.0}],\"visits_number\":6,\"minimum_lapse\":556.0,\"activity\":{\"point_id\":\"1020782\",\"duration\":556,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":57600,\"day_index\":0},{\"start\":27000,\"end\":57600,\"day_index\":1},{\"start\":27000,\"end\":57600,\"day_index\":2},{\"start\":27000,\"end\":57600,\"day_index\":3},{\"start\":27000,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020782_SNC_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":68.77},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":217.0}],\"visits_number\":6,\"minimum_lapse\":556.0,\"activity\":{\"point_id\":\"1020782\",\"duration\":556,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":57600,\"day_index\":0},{\"start\":27000,\"end\":57600,\"day_index\":1},{\"start\":27000,\"end\":57600,\"day_index\":2},{\"start\":27000,\"end\":57600,\"day_index\":3},{\"start\":27000,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1001454_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":9.0,\"activity\":{\"point_id\":\"1001454\",\"duration\":9,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":68400,\"day_index\":0},{\"start\":32400,\"end\":68400,\"day_index\":1},{\"start\":32400,\"end\":68400,\"day_index\":2},{\"start\":32400,\"end\":68400,\"day_index\":3},{\"start\":32400,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1001454_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":9.0,\"activity\":{\"point_id\":\"1001454\",\"duration\":9,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":68400,\"day_index\":0},{\"start\":32400,\"end\":68400,\"day_index\":1},{\"start\":32400,\"end\":68400,\"day_index\":2},{\"start\":32400,\"end\":68400,\"day_index\":3},{\"start\":32400,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070735_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.7},{\"unit_id\":\"l\",\"value\":7.0},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":28.0,\"activity\":{\"point_id\":\"1070735\",\"duration\":28,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070735_EMP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.7},{\"unit_id\":\"l\",\"value\":7.0},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":28.0,\"activity\":{\"point_id\":\"1070735\",\"duration\":28,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1031405_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.4},{\"unit_id\":\"l\",\"value\":0.375},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1031405\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1031405_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.4},{\"unit_id\":\"l\",\"value\":0.375},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1031405\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107065_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1107065\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":39600,\"end\":68400,\"day_index\":0},{\"start\":39600,\"end\":68400,\"day_index\":1},{\"start\":39600,\"end\":68400,\"day_index\":2},{\"start\":39600,\"end\":68400,\"day_index\":3},{\"start\":39600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107065_PH _ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1107065\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":39600,\"end\":68400,\"day_index\":0},{\"start\":39600,\"end\":68400,\"day_index\":1},{\"start\":39600,\"end\":68400,\"day_index\":2},{\"start\":39600,\"end\":68400,\"day_index\":3},{\"start\":39600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099019_BOB_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":43.364},{\"unit_id\":\"l\",\"value\":123.8},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":6,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1099019\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099019_PH _ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":43.364},{\"unit_id\":\"l\",\"value\":123.8},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":6,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1099019\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1096970_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.0},{\"unit_id\":\"l\",\"value\":10.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1096970\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070749_EMP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.35},{\"unit_id\":\"l\",\"value\":89.6},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1070749\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1096970_CLI_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.0},{\"unit_id\":\"l\",\"value\":10.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1096970\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1040631_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":23.1},{\"unit_id\":\"l\",\"value\":21.0},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":3,\"minimum_lapse\":35.0,\"activity\":{\"point_id\":\"1040631\",\"duration\":35,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":68400,\"day_index\":0},{\"start\":27000,\"end\":68400,\"day_index\":1},{\"start\":27000,\"end\":68400,\"day_index\":2},{\"start\":27000,\"end\":68400,\"day_index\":3},{\"start\":27000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1040631_PH _ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":23.1},{\"unit_id\":\"l\",\"value\":21.0},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":3,\"minimum_lapse\":35.0,\"activity\":{\"point_id\":\"1040631\",\"duration\":35,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":68400,\"day_index\":0},{\"start\":27000,\"end\":68400,\"day_index\":1},{\"start\":27000,\"end\":68400,\"day_index\":2},{\"start\":27000,\"end\":68400,\"day_index\":3},{\"start\":27000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1001454_BOB_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":9.0,\"activity\":{\"point_id\":\"1001454\",\"duration\":9,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":68400,\"day_index\":0},{\"start\":32400,\"end\":68400,\"day_index\":1},{\"start\":32400,\"end\":68400,\"day_index\":2},{\"start\":32400,\"end\":68400,\"day_index\":3},{\"start\":32400,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1106440_TAP_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.32},{\"unit_id\":\"l\",\"value\":13.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":12,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1106440\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030463_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.66},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":46.0}],\"visits_number\":3,\"minimum_lapse\":184.0,\"activity\":{\"point_id\":\"1030463\",\"duration\":184,\"setup_duration\":120,\"timewindows\":[{\"start\":30000,\"end\":68400,\"day_index\":0},{\"start\":30000,\"end\":68400,\"day_index\":1},{\"start\":30000,\"end\":68400,\"day_index\":2},{\"start\":30000,\"end\":68400,\"day_index\":3},{\"start\":30000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030463_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.66},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":46.0}],\"visits_number\":3,\"minimum_lapse\":184.0,\"activity\":{\"point_id\":\"1030463\",\"duration\":184,\"setup_duration\":120,\"timewindows\":[{\"start\":30000,\"end\":68400,\"day_index\":0},{\"start\":30000,\"end\":68400,\"day_index\":1},{\"start\":30000,\"end\":68400,\"day_index\":2},{\"start\":30000,\"end\":68400,\"day_index\":3},{\"start\":30000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030463_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.66},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":46.0}],\"visits_number\":3,\"minimum_lapse\":184.0,\"activity\":{\"point_id\":\"1030463\",\"duration\":184,\"setup_duration\":120,\"timewindows\":[{\"start\":30000,\"end\":68400,\"day_index\":0},{\"start\":30000,\"end\":68400,\"day_index\":1},{\"start\":30000,\"end\":68400,\"day_index\":2},{\"start\":30000,\"end\":68400,\"day_index\":3},{\"start\":30000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030463_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.66},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":46.0}],\"visits_number\":3,\"minimum_lapse\":184.0,\"activity\":{\"point_id\":\"1030463\",\"duration\":184,\"setup_duration\":120,\"timewindows\":[{\"start\":30000,\"end\":68400,\"day_index\":0},{\"start\":30000,\"end\":68400,\"day_index\":1},{\"start\":30000,\"end\":68400,\"day_index\":2},{\"start\":30000,\"end\":68400,\"day_index\":3},{\"start\":30000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1033191_ASC_ 84_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":1,\"minimum_lapse\":400.0,\"activity\":{\"point_id\":\"1033191\",\"duration\":400,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1040631_CLI_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":23.1},{\"unit_id\":\"l\",\"value\":21.0},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":3,\"minimum_lapse\":35.0,\"activity\":{\"point_id\":\"1040631\",\"duration\":35,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":68400,\"day_index\":0},{\"start\":27000,\"end\":68400,\"day_index\":1},{\"start\":27000,\"end\":68400,\"day_index\":2},{\"start\":27000,\"end\":68400,\"day_index\":3},{\"start\":27000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1040631_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":23.1},{\"unit_id\":\"l\",\"value\":21.0},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":3,\"minimum_lapse\":35.0,\"activity\":{\"point_id\":\"1040631\",\"duration\":35,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":68400,\"day_index\":0},{\"start\":27000,\"end\":68400,\"day_index\":1},{\"start\":27000,\"end\":68400,\"day_index\":2},{\"start\":27000,\"end\":68400,\"day_index\":3},{\"start\":27000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1040631_TAP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":23.1},{\"unit_id\":\"l\",\"value\":21.0},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":3,\"minimum_lapse\":35.0,\"activity\":{\"point_id\":\"1040631\",\"duration\":35,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":68400,\"day_index\":0},{\"start\":27000,\"end\":68400,\"day_index\":1},{\"start\":27000,\"end\":68400,\"day_index\":2},{\"start\":27000,\"end\":68400,\"day_index\":3},{\"start\":27000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1054230_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":1,\"minimum_lapse\":80.0,\"activity\":{\"point_id\":\"1054230\",\"duration\":80,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":61200,\"day_index\":0},{\"start\":36000,\"end\":61200,\"day_index\":1},{\"start\":36000,\"end\":61200,\"day_index\":2},{\"start\":36000,\"end\":61200,\"day_index\":3},{\"start\":36000,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133951_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.6},{\"unit_id\":\"l\",\"value\":136.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":6,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1133951\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133951_SNC_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.6},{\"unit_id\":\"l\",\"value\":136.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":6,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1133951\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133959_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1133959\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133951_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.6},{\"unit_id\":\"l\",\"value\":136.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":6,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1133951\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133951_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.6},{\"unit_id\":\"l\",\"value\":136.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":6,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1133951\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133959_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1133959\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133959_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1133959\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132589_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":48.4},{\"unit_id\":\"l\",\"value\":136.4},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":12,\"minimum_lapse\":286.0,\"activity\":{\"point_id\":\"1132589\",\"duration\":286,\"setup_duration\":120,\"timewindows\":[{\"start\":23400,\"end\":30600,\"day_index\":0},{\"start\":23400,\"end\":30600,\"day_index\":1},{\"start\":23400,\"end\":30600,\"day_index\":2},{\"start\":23400,\"end\":30600,\"day_index\":3},{\"start\":23400,\"end\":30600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133959_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1133959\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144594_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.64},{\"unit_id\":\"l\",\"value\":26.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1144594\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144594_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.64},{\"unit_id\":\"l\",\"value\":26.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1144594\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144594_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.64},{\"unit_id\":\"l\",\"value\":26.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1144594\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004770_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.93},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004770\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":48600,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":48600,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":48600,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":48600,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":48600,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004770_BOB_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.93},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004770\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":48600,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":48600,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":48600,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":48600,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":48600,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_CLI_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_ASC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1143914_TAP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":8.94},{\"unit_id\":\"l\",\"value\":33.9},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1143914\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144594_TAP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.64},{\"unit_id\":\"l\",\"value\":26.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1144594\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1129651_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":2.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1129651\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1129651_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":2.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1129651\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121101_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.3},{\"unit_id\":\"l\",\"value\":3.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1121101\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121101_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.3},{\"unit_id\":\"l\",\"value\":3.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1121101\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1109136_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1109136\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_CLI_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_EMP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1109136_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1109136\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_TAP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1087334_DIF_ 42_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":60.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1087334\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004770_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.93},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004770\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":48600,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":48600,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":48600,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":48600,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":48600,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1106440_TAP_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.32},{\"unit_id\":\"l\",\"value\":13.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":12,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1106440\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119751_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.3},{\"unit_id\":\"l\",\"value\":3.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1119751\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119750_EMP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":39.0},{\"unit_id\":\"l\",\"value\":184.68},{\"unit_id\":\"qte\",\"value\":30.0}],\"visits_number\":6,\"minimum_lapse\":240.0,\"activity\":{\"point_id\":\"1119750\",\"duration\":240,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":39600,\"day_index\":0},{\"start\":51300,\"end\":56700,\"day_index\":0},{\"start\":30600,\"end\":39600,\"day_index\":1},{\"start\":51300,\"end\":56700,\"day_index\":1},{\"start\":30600,\"end\":39600,\"day_index\":2},{\"start\":51300,\"end\":56700,\"day_index\":2},{\"start\":30600,\"end\":39600,\"day_index\":3},{\"start\":51300,\"end\":56700,\"day_index\":3},{\"start\":30600,\"end\":39600,\"day_index\":4},{\"start\":51300,\"end\":56700,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107065_SNC_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1107065\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":39600,\"end\":68400,\"day_index\":0},{\"start\":39600,\"end\":68400,\"day_index\":1},{\"start\":39600,\"end\":68400,\"day_index\":2},{\"start\":39600,\"end\":68400,\"day_index\":3},{\"start\":39600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107065_SAV_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1107065\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":39600,\"end\":68400,\"day_index\":0},{\"start\":39600,\"end\":68400,\"day_index\":1},{\"start\":39600,\"end\":68400,\"day_index\":2},{\"start\":39600,\"end\":68400,\"day_index\":3},{\"start\":39600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099019_DIF_ 84_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":43.364},{\"unit_id\":\"l\",\"value\":123.8},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":6,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1099019\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121101_BOB_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.3},{\"unit_id\":\"l\",\"value\":3.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1121101\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121101_CLI_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.3},{\"unit_id\":\"l\",\"value\":3.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1121101\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119750_SAV_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":39.0},{\"unit_id\":\"l\",\"value\":184.68},{\"unit_id\":\"qte\",\"value\":30.0}],\"visits_number\":6,\"minimum_lapse\":240.0,\"activity\":{\"point_id\":\"1119750\",\"duration\":240,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":39600,\"day_index\":0},{\"start\":51300,\"end\":56700,\"day_index\":0},{\"start\":30600,\"end\":39600,\"day_index\":1},{\"start\":51300,\"end\":56700,\"day_index\":1},{\"start\":30600,\"end\":39600,\"day_index\":2},{\"start\":51300,\"end\":56700,\"day_index\":2},{\"start\":30600,\"end\":39600,\"day_index\":3},{\"start\":51300,\"end\":56700,\"day_index\":3},{\"start\":30600,\"end\":39600,\"day_index\":4},{\"start\":51300,\"end\":56700,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119750_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":39.0},{\"unit_id\":\"l\",\"value\":184.68},{\"unit_id\":\"qte\",\"value\":30.0}],\"visits_number\":6,\"minimum_lapse\":240.0,\"activity\":{\"point_id\":\"1119750\",\"duration\":240,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":39600,\"day_index\":0},{\"start\":51300,\"end\":56700,\"day_index\":0},{\"start\":30600,\"end\":39600,\"day_index\":1},{\"start\":51300,\"end\":56700,\"day_index\":1},{\"start\":30600,\"end\":39600,\"day_index\":2},{\"start\":51300,\"end\":56700,\"day_index\":2},{\"start\":30600,\"end\":39600,\"day_index\":3},{\"start\":51300,\"end\":56700,\"day_index\":3},{\"start\":30600,\"end\":39600,\"day_index\":4},{\"start\":51300,\"end\":56700,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119751_EMP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.3},{\"unit_id\":\"l\",\"value\":3.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1119751\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119751_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.3},{\"unit_id\":\"l\",\"value\":3.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1119751\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1054230_BOB_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":1,\"minimum_lapse\":80.0,\"activity\":{\"point_id\":\"1054230\",\"duration\":80,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":61200,\"day_index\":0},{\"start\":36000,\"end\":61200,\"day_index\":1},{\"start\":36000,\"end\":61200,\"day_index\":2},{\"start\":36000,\"end\":61200,\"day_index\":3},{\"start\":36000,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_PH _ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137030_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1137030\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":63000,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":63000,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":63000,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":63000,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134263_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.06},{\"unit_id\":\"l\",\"value\":75.6},{\"unit_id\":\"qte\",\"value\":36.0}],\"visits_number\":3,\"minimum_lapse\":144.0,\"activity\":{\"point_id\":\"1134263\",\"duration\":144,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134263_BOB_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.06},{\"unit_id\":\"l\",\"value\":75.6},{\"unit_id\":\"qte\",\"value\":36.0}],\"visits_number\":3,\"minimum_lapse\":144.0,\"activity\":{\"point_id\":\"1134263\",\"duration\":144,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137030_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1137030\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":63000,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":63000,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":63000,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":63000,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137030_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1137030\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":63000,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":63000,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":63000,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":63000,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134263_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.06},{\"unit_id\":\"l\",\"value\":75.6},{\"unit_id\":\"qte\",\"value\":36.0}],\"visits_number\":3,\"minimum_lapse\":144.0,\"activity\":{\"point_id\":\"1134263\",\"duration\":144,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134263_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.06},{\"unit_id\":\"l\",\"value\":75.6},{\"unit_id\":\"qte\",\"value\":36.0}],\"visits_number\":3,\"minimum_lapse\":144.0,\"activity\":{\"point_id\":\"1134263\",\"duration\":144,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133530_PCP_ 84_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.56},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":36.0}],\"visits_number\":1,\"minimum_lapse\":144.0,\"activity\":{\"point_id\":\"1133530\",\"duration\":144,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137030_EMP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1137030\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":63000,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":63000,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":63000,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":63000,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137030_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1137030\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":63000,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":63000,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":63000,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":63000,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142237_EMP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.1},{\"unit_id\":\"l\",\"value\":43.092},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1142237\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142237_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.1},{\"unit_id\":\"l\",\"value\":43.092},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1142237\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_TAP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121283_BOB_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.05},{\"unit_id\":\"l\",\"value\":12.8},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1121283\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":64800,\"day_index\":0},{\"start\":36000,\"end\":64800,\"day_index\":1},{\"start\":36000,\"end\":64800,\"day_index\":2},{\"start\":36000,\"end\":64800,\"day_index\":3},{\"start\":36000,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1124357_SNC_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1124357\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130453_BOB_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":52.8},{\"unit_id\":\"l\",\"value\":148.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":6,\"minimum_lapse\":312.0,\"activity\":{\"point_id\":\"1130453\",\"duration\":312,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130453_CLI_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":52.8},{\"unit_id\":\"l\",\"value\":148.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":6,\"minimum_lapse\":312.0,\"activity\":{\"point_id\":\"1130453\",\"duration\":312,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130453_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":52.8},{\"unit_id\":\"l\",\"value\":148.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":6,\"minimum_lapse\":312.0,\"activity\":{\"point_id\":\"1130453\",\"duration\":312,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130453_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":52.8},{\"unit_id\":\"l\",\"value\":148.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":6,\"minimum_lapse\":312.0,\"activity\":{\"point_id\":\"1130453\",\"duration\":312,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132589_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":48.4},{\"unit_id\":\"l\",\"value\":136.4},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":12,\"minimum_lapse\":286.0,\"activity\":{\"point_id\":\"1132589\",\"duration\":286,\"setup_duration\":120,\"timewindows\":[{\"start\":23400,\"end\":30600,\"day_index\":0},{\"start\":23400,\"end\":30600,\"day_index\":1},{\"start\":23400,\"end\":30600,\"day_index\":2},{\"start\":23400,\"end\":30600,\"day_index\":3},{\"start\":23400,\"end\":30600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133530_PH _ 84_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.56},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":36.0}],\"visits_number\":1,\"minimum_lapse\":144.0,\"activity\":{\"point_id\":\"1133530\",\"duration\":144,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133530_SAV_ 84_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.56},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":36.0}],\"visits_number\":1,\"minimum_lapse\":144.0,\"activity\":{\"point_id\":\"1133530\",\"duration\":144,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132589_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":48.4},{\"unit_id\":\"l\",\"value\":136.4},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":12,\"minimum_lapse\":286.0,\"activity\":{\"point_id\":\"1132589\",\"duration\":286,\"setup_duration\":120,\"timewindows\":[{\"start\":23400,\"end\":30600,\"day_index\":0},{\"start\":23400,\"end\":30600,\"day_index\":1},{\"start\":23400,\"end\":30600,\"day_index\":2},{\"start\":23400,\"end\":30600,\"day_index\":3},{\"start\":23400,\"end\":30600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132589_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":48.4},{\"unit_id\":\"l\",\"value\":136.4},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":12,\"minimum_lapse\":286.0,\"activity\":{\"point_id\":\"1132589\",\"duration\":286,\"setup_duration\":120,\"timewindows\":[{\"start\":23400,\"end\":30600,\"day_index\":0},{\"start\":23400,\"end\":30600,\"day_index\":1},{\"start\":23400,\"end\":30600,\"day_index\":2},{\"start\":23400,\"end\":30600,\"day_index\":3},{\"start\":23400,\"end\":30600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107065_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1107065\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":39600,\"end\":68400,\"day_index\":0},{\"start\":39600,\"end\":68400,\"day_index\":1},{\"start\":39600,\"end\":68400,\"day_index\":2},{\"start\":39600,\"end\":68400,\"day_index\":3},{\"start\":39600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099019_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":43.364},{\"unit_id\":\"l\",\"value\":123.8},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":6,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1099019\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099019_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":43.364},{\"unit_id\":\"l\",\"value\":123.8},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":6,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1099019\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099019_PH _ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":43.364},{\"unit_id\":\"l\",\"value\":123.8},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":6,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1099019\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099019_BOB_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":43.364},{\"unit_id\":\"l\",\"value\":123.8},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":6,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1099019\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1096970_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.0},{\"unit_id\":\"l\",\"value\":10.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1096970\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_CLI_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107065_PH _ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1107065\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":39600,\"end\":68400,\"day_index\":0},{\"start\":39600,\"end\":68400,\"day_index\":1},{\"start\":39600,\"end\":68400,\"day_index\":2},{\"start\":39600,\"end\":68400,\"day_index\":3},{\"start\":39600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1040631_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":23.1},{\"unit_id\":\"l\",\"value\":21.0},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":3,\"minimum_lapse\":35.0,\"activity\":{\"point_id\":\"1040631\",\"duration\":35,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":68400,\"day_index\":0},{\"start\":27000,\"end\":68400,\"day_index\":1},{\"start\":27000,\"end\":68400,\"day_index\":2},{\"start\":27000,\"end\":68400,\"day_index\":3},{\"start\":27000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1040631_TAP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":23.1},{\"unit_id\":\"l\",\"value\":21.0},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":3,\"minimum_lapse\":35.0,\"activity\":{\"point_id\":\"1040631\",\"duration\":35,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":68400,\"day_index\":0},{\"start\":27000,\"end\":68400,\"day_index\":1},{\"start\":27000,\"end\":68400,\"day_index\":2},{\"start\":27000,\"end\":68400,\"day_index\":3},{\"start\":27000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137030_DIF_ 84_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1137030\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":63000,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":63000,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":63000,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":63000,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142237_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.1},{\"unit_id\":\"l\",\"value\":43.092},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1142237\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142237_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.1},{\"unit_id\":\"l\",\"value\":43.092},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1142237\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1106440_TAP_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.32},{\"unit_id\":\"l\",\"value\":13.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":12,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1106440\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020782_SNC_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":68.77},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":217.0}],\"visits_number\":6,\"minimum_lapse\":556.0,\"activity\":{\"point_id\":\"1020782\",\"duration\":556,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":57600,\"day_index\":0},{\"start\":27000,\"end\":57600,\"day_index\":1},{\"start\":27000,\"end\":57600,\"day_index\":2},{\"start\":27000,\"end\":57600,\"day_index\":3},{\"start\":27000,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020782_SAV_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":68.77},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":217.0}],\"visits_number\":6,\"minimum_lapse\":556.0,\"activity\":{\"point_id\":\"1020782\",\"duration\":556,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":57600,\"day_index\":0},{\"start\":27000,\"end\":57600,\"day_index\":1},{\"start\":27000,\"end\":57600,\"day_index\":2},{\"start\":27000,\"end\":57600,\"day_index\":3},{\"start\":27000,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020782_CLI_ 42_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":68.77},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":217.0}],\"visits_number\":6,\"minimum_lapse\":556.0,\"activity\":{\"point_id\":\"1020782\",\"duration\":556,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":57600,\"day_index\":0},{\"start\":27000,\"end\":57600,\"day_index\":1},{\"start\":27000,\"end\":57600,\"day_index\":2},{\"start\":27000,\"end\":57600,\"day_index\":3},{\"start\":27000,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020782_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":68.77},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":217.0}],\"visits_number\":6,\"minimum_lapse\":556.0,\"activity\":{\"point_id\":\"1020782\",\"duration\":556,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":57600,\"day_index\":0},{\"start\":27000,\"end\":57600,\"day_index\":1},{\"start\":27000,\"end\":57600,\"day_index\":2},{\"start\":27000,\"end\":57600,\"day_index\":3},{\"start\":27000,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020782_INI_ 42_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":68.77},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":217.0}],\"visits_number\":6,\"minimum_lapse\":556.0,\"activity\":{\"point_id\":\"1020782\",\"duration\":556,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":57600,\"day_index\":0},{\"start\":27000,\"end\":57600,\"day_index\":1},{\"start\":27000,\"end\":57600,\"day_index\":2},{\"start\":27000,\"end\":57600,\"day_index\":3},{\"start\":27000,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1040631_PH _ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":23.1},{\"unit_id\":\"l\",\"value\":21.0},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":3,\"minimum_lapse\":35.0,\"activity\":{\"point_id\":\"1040631\",\"duration\":35,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":68400,\"day_index\":0},{\"start\":27000,\"end\":68400,\"day_index\":1},{\"start\":27000,\"end\":68400,\"day_index\":2},{\"start\":27000,\"end\":68400,\"day_index\":3},{\"start\":27000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030487_SNC_ 42_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.8},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":2,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1030487\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1106440_TAP_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.32},{\"unit_id\":\"l\",\"value\":13.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":12,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1106440\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1007287_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":15.5},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":50.0}],\"visits_number\":3,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1007287\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_PH _ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144594_TAP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.64},{\"unit_id\":\"l\",\"value\":26.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1144594\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1145751_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1145751\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1145751_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1145751\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1143914_TAP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":8.94},{\"unit_id\":\"l\",\"value\":33.9},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1143914\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070749_EMP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.35},{\"unit_id\":\"l\",\"value\":89.6},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1070749\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070749_DIF_ 84_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.35},{\"unit_id\":\"l\",\"value\":89.6},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1070749\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070735_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.7},{\"unit_id\":\"l\",\"value\":7.0},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":28.0,\"activity\":{\"point_id\":\"1070735\",\"duration\":28,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070749_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.35},{\"unit_id\":\"l\",\"value\":89.6},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1070749\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070749_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.35},{\"unit_id\":\"l\",\"value\":89.6},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1070749\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070749_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.35},{\"unit_id\":\"l\",\"value\":89.6},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1070749\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1096970_CLI_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.0},{\"unit_id\":\"l\",\"value\":10.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1096970\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1096970_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.0},{\"unit_id\":\"l\",\"value\":10.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1096970\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1096970_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.0},{\"unit_id\":\"l\",\"value\":10.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1096970\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1031405_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.4},{\"unit_id\":\"l\",\"value\":0.375},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1031405\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1031405_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.4},{\"unit_id\":\"l\",\"value\":0.375},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1031405\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070735_EMP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.7},{\"unit_id\":\"l\",\"value\":7.0},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":28.0,\"activity\":{\"point_id\":\"1070735\",\"duration\":28,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1145751_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1145751\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133951_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.6},{\"unit_id\":\"l\",\"value\":136.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":6,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1133951\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1131394_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.475},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1131394\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1131394_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.475},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1131394\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1123348_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1123348\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1123348_CLI_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1123348\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119750_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":39.0},{\"unit_id\":\"l\",\"value\":184.68},{\"unit_id\":\"qte\",\"value\":30.0}],\"visits_number\":6,\"minimum_lapse\":240.0,\"activity\":{\"point_id\":\"1119750\",\"duration\":240,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":39600,\"day_index\":0},{\"start\":51300,\"end\":56700,\"day_index\":0},{\"start\":30600,\"end\":39600,\"day_index\":1},{\"start\":51300,\"end\":56700,\"day_index\":1},{\"start\":30600,\"end\":39600,\"day_index\":2},{\"start\":51300,\"end\":56700,\"day_index\":2},{\"start\":30600,\"end\":39600,\"day_index\":3},{\"start\":51300,\"end\":56700,\"day_index\":3},{\"start\":30600,\"end\":39600,\"day_index\":4},{\"start\":51300,\"end\":56700,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119750_SAV_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":39.0},{\"unit_id\":\"l\",\"value\":184.68},{\"unit_id\":\"qte\",\"value\":30.0}],\"visits_number\":6,\"minimum_lapse\":240.0,\"activity\":{\"point_id\":\"1119750\",\"duration\":240,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":39600,\"day_index\":0},{\"start\":51300,\"end\":56700,\"day_index\":0},{\"start\":30600,\"end\":39600,\"day_index\":1},{\"start\":51300,\"end\":56700,\"day_index\":1},{\"start\":30600,\"end\":39600,\"day_index\":2},{\"start\":51300,\"end\":56700,\"day_index\":2},{\"start\":30600,\"end\":39600,\"day_index\":3},{\"start\":51300,\"end\":56700,\"day_index\":3},{\"start\":30600,\"end\":39600,\"day_index\":4},{\"start\":51300,\"end\":56700,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1120609_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1120609\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1120609_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1120609\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1120609_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.6},{\"unit_id\":\"l\",\"value\":6.0},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1120609\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119750_EMP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":39.0},{\"unit_id\":\"l\",\"value\":184.68},{\"unit_id\":\"qte\",\"value\":30.0}],\"visits_number\":6,\"minimum_lapse\":240.0,\"activity\":{\"point_id\":\"1119750\",\"duration\":240,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":39600,\"day_index\":0},{\"start\":51300,\"end\":56700,\"day_index\":0},{\"start\":30600,\"end\":39600,\"day_index\":1},{\"start\":51300,\"end\":56700,\"day_index\":1},{\"start\":30600,\"end\":39600,\"day_index\":2},{\"start\":51300,\"end\":56700,\"day_index\":2},{\"start\":30600,\"end\":39600,\"day_index\":3},{\"start\":51300,\"end\":56700,\"day_index\":3},{\"start\":30600,\"end\":39600,\"day_index\":4},{\"start\":51300,\"end\":56700,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107065_SAV_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1107065\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":39600,\"end\":68400,\"day_index\":0},{\"start\":39600,\"end\":68400,\"day_index\":1},{\"start\":39600,\"end\":68400,\"day_index\":2},{\"start\":39600,\"end\":68400,\"day_index\":3},{\"start\":39600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1123348_EMP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1123348\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070735_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.7},{\"unit_id\":\"l\",\"value\":7.0},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":28.0,\"activity\":{\"point_id\":\"1070735\",\"duration\":28,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1123348_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1123348\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127546_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.8},{\"unit_id\":\"l\",\"value\":0.75},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1127546\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133951_SNC_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.6},{\"unit_id\":\"l\",\"value\":136.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":6,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1133951\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137715_BOB_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":22.0},{\"unit_id\":\"l\",\"value\":62.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":130.0,\"activity\":{\"point_id\":\"1137715\",\"duration\":130,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137715_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":22.0},{\"unit_id\":\"l\",\"value\":62.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":130.0,\"activity\":{\"point_id\":\"1137715\",\"duration\":130,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137715_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":22.0},{\"unit_id\":\"l\",\"value\":62.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":130.0,\"activity\":{\"point_id\":\"1137715\",\"duration\":130,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132589_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":48.4},{\"unit_id\":\"l\",\"value\":136.4},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":12,\"minimum_lapse\":286.0,\"activity\":{\"point_id\":\"1132589\",\"duration\":286,\"setup_duration\":120,\"timewindows\":[{\"start\":23400,\"end\":30600,\"day_index\":0},{\"start\":23400,\"end\":30600,\"day_index\":1},{\"start\":23400,\"end\":30600,\"day_index\":2},{\"start\":23400,\"end\":30600,\"day_index\":3},{\"start\":23400,\"end\":30600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1131394_CLI_ 84_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.475},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1131394\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1131394_DIF_ 84_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.475},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1131394\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1131394_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.475},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1131394\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127546_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.8},{\"unit_id\":\"l\",\"value\":0.75},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1127546\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127546_BOB_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.8},{\"unit_id\":\"l\",\"value\":0.75},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1127546\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107065_SNC_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1107065\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":39600,\"end\":68400,\"day_index\":0},{\"start\":39600,\"end\":68400,\"day_index\":1},{\"start\":39600,\"end\":68400,\"day_index\":2},{\"start\":39600,\"end\":68400,\"day_index\":3},{\"start\":39600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099019_PH _ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":43.364},{\"unit_id\":\"l\",\"value\":123.8},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":6,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1099019\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107065_PH _ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1107065\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":39600,\"end\":68400,\"day_index\":0},{\"start\":39600,\"end\":68400,\"day_index\":1},{\"start\":39600,\"end\":68400,\"day_index\":2},{\"start\":39600,\"end\":68400,\"day_index\":3},{\"start\":39600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1109136_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1109136\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1109136_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1109136\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121283_BOB_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.05},{\"unit_id\":\"l\",\"value\":12.8},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1121283\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":64800,\"day_index\":0},{\"start\":36000,\"end\":64800,\"day_index\":1},{\"start\":36000,\"end\":64800,\"day_index\":2},{\"start\":36000,\"end\":64800,\"day_index\":3},{\"start\":36000,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1109136_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1109136\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121283_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.05},{\"unit_id\":\"l\",\"value\":12.8},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1121283\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":64800,\"day_index\":0},{\"start\":36000,\"end\":64800,\"day_index\":1},{\"start\":36000,\"end\":64800,\"day_index\":2},{\"start\":36000,\"end\":64800,\"day_index\":3},{\"start\":36000,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121283_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.05},{\"unit_id\":\"l\",\"value\":12.8},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1121283\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":64800,\"day_index\":0},{\"start\":36000,\"end\":64800,\"day_index\":1},{\"start\":36000,\"end\":64800,\"day_index\":2},{\"start\":36000,\"end\":64800,\"day_index\":3},{\"start\":36000,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1124357_SNC_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1124357\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130453_BOB_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":52.8},{\"unit_id\":\"l\",\"value\":148.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":6,\"minimum_lapse\":312.0,\"activity\":{\"point_id\":\"1130453\",\"duration\":312,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121283_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.05},{\"unit_id\":\"l\",\"value\":12.8},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1121283\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":64800,\"day_index\":0},{\"start\":36000,\"end\":64800,\"day_index\":1},{\"start\":36000,\"end\":64800,\"day_index\":2},{\"start\":36000,\"end\":64800,\"day_index\":3},{\"start\":36000,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_TAP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004332_BOB_ 14_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.11},{\"unit_id\":\"l\",\"value\":1.125},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004332\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030348_PH _ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":12,\"minimum_lapse\":156.0,\"activity\":{\"point_id\":\"1030348\",\"duration\":156,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030348_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":12,\"minimum_lapse\":156.0,\"activity\":{\"point_id\":\"1030348\",\"duration\":156,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030348_BOB_  7_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":12,\"minimum_lapse\":156.0,\"activity\":{\"point_id\":\"1030348\",\"duration\":156,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_CLI_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_EMP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132589_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":48.4},{\"unit_id\":\"l\",\"value\":136.4},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":12,\"minimum_lapse\":286.0,\"activity\":{\"point_id\":\"1132589\",\"duration\":286,\"setup_duration\":120,\"timewindows\":[{\"start\":23400,\"end\":30600,\"day_index\":0},{\"start\":23400,\"end\":30600,\"day_index\":1},{\"start\":23400,\"end\":30600,\"day_index\":2},{\"start\":23400,\"end\":30600,\"day_index\":3},{\"start\":23400,\"end\":30600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132589_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":48.4},{\"unit_id\":\"l\",\"value\":136.4},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":12,\"minimum_lapse\":286.0,\"activity\":{\"point_id\":\"1132589\",\"duration\":286,\"setup_duration\":120,\"timewindows\":[{\"start\":23400,\"end\":30600,\"day_index\":0},{\"start\":23400,\"end\":30600,\"day_index\":1},{\"start\":23400,\"end\":30600,\"day_index\":2},{\"start\":23400,\"end\":30600,\"day_index\":3},{\"start\":23400,\"end\":30600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1143992_EMP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1143992\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":62100,\"day_index\":0},{\"start\":32400,\"end\":62100,\"day_index\":1},{\"start\":32400,\"end\":62100,\"day_index\":2},{\"start\":32400,\"end\":62100,\"day_index\":3},{\"start\":32400,\"end\":62100,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1143992_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1143992\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":62100,\"day_index\":0},{\"start\":32400,\"end\":62100,\"day_index\":1},{\"start\":32400,\"end\":62100,\"day_index\":2},{\"start\":32400,\"end\":62100,\"day_index\":3},{\"start\":32400,\"end\":62100,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1040631_TAP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":23.1},{\"unit_id\":\"l\",\"value\":21.0},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":3,\"minimum_lapse\":35.0,\"activity\":{\"point_id\":\"1040631\",\"duration\":35,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":68400,\"day_index\":0},{\"start\":27000,\"end\":68400,\"day_index\":1},{\"start\":27000,\"end\":68400,\"day_index\":2},{\"start\":27000,\"end\":68400,\"day_index\":3},{\"start\":27000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1040631_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":23.1},{\"unit_id\":\"l\",\"value\":21.0},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":3,\"minimum_lapse\":35.0,\"activity\":{\"point_id\":\"1040631\",\"duration\":35,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":68400,\"day_index\":0},{\"start\":27000,\"end\":68400,\"day_index\":1},{\"start\":27000,\"end\":68400,\"day_index\":2},{\"start\":27000,\"end\":68400,\"day_index\":3},{\"start\":27000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030463_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.66},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":46.0}],\"visits_number\":3,\"minimum_lapse\":184.0,\"activity\":{\"point_id\":\"1030463\",\"duration\":184,\"setup_duration\":120,\"timewindows\":[{\"start\":30000,\"end\":68400,\"day_index\":0},{\"start\":30000,\"end\":68400,\"day_index\":1},{\"start\":30000,\"end\":68400,\"day_index\":2},{\"start\":30000,\"end\":68400,\"day_index\":3},{\"start\":30000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030463_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.66},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":46.0}],\"visits_number\":3,\"minimum_lapse\":184.0,\"activity\":{\"point_id\":\"1030463\",\"duration\":184,\"setup_duration\":120,\"timewindows\":[{\"start\":30000,\"end\":68400,\"day_index\":0},{\"start\":30000,\"end\":68400,\"day_index\":1},{\"start\":30000,\"end\":68400,\"day_index\":2},{\"start\":30000,\"end\":68400,\"day_index\":3},{\"start\":30000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030463_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.66},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":46.0}],\"visits_number\":3,\"minimum_lapse\":184.0,\"activity\":{\"point_id\":\"1030463\",\"duration\":184,\"setup_duration\":120,\"timewindows\":[{\"start\":30000,\"end\":68400,\"day_index\":0},{\"start\":30000,\"end\":68400,\"day_index\":1},{\"start\":30000,\"end\":68400,\"day_index\":2},{\"start\":30000,\"end\":68400,\"day_index\":3},{\"start\":30000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030463_CLI_ 84_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.66},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":46.0}],\"visits_number\":3,\"minimum_lapse\":184.0,\"activity\":{\"point_id\":\"1030463\",\"duration\":184,\"setup_duration\":120,\"timewindows\":[{\"start\":30000,\"end\":68400,\"day_index\":0},{\"start\":30000,\"end\":68400,\"day_index\":1},{\"start\":30000,\"end\":68400,\"day_index\":2},{\"start\":30000,\"end\":68400,\"day_index\":3},{\"start\":30000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1030463_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.66},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":46.0}],\"visits_number\":3,\"minimum_lapse\":184.0,\"activity\":{\"point_id\":\"1030463\",\"duration\":184,\"setup_duration\":120,\"timewindows\":[{\"start\":30000,\"end\":68400,\"day_index\":0},{\"start\":30000,\"end\":68400,\"day_index\":1},{\"start\":30000,\"end\":68400,\"day_index\":2},{\"start\":30000,\"end\":68400,\"day_index\":3},{\"start\":30000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1106440_TAP_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.32},{\"unit_id\":\"l\",\"value\":13.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":12,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1106440\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107065_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1107065\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":39600,\"end\":68400,\"day_index\":0},{\"start\":39600,\"end\":68400,\"day_index\":1},{\"start\":39600,\"end\":68400,\"day_index\":2},{\"start\":39600,\"end\":68400,\"day_index\":3},{\"start\":39600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1040631_PH _ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":23.1},{\"unit_id\":\"l\",\"value\":21.0},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":3,\"minimum_lapse\":35.0,\"activity\":{\"point_id\":\"1040631\",\"duration\":35,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":68400,\"day_index\":0},{\"start\":27000,\"end\":68400,\"day_index\":1},{\"start\":27000,\"end\":68400,\"day_index\":2},{\"start\":27000,\"end\":68400,\"day_index\":3},{\"start\":27000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099019_BOB_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":43.364},{\"unit_id\":\"l\",\"value\":123.8},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":6,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1099019\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1040631_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":23.1},{\"unit_id\":\"l\",\"value\":21.0},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":3,\"minimum_lapse\":35.0,\"activity\":{\"point_id\":\"1040631\",\"duration\":35,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":68400,\"day_index\":0},{\"start\":27000,\"end\":68400,\"day_index\":1},{\"start\":27000,\"end\":68400,\"day_index\":2},{\"start\":27000,\"end\":68400,\"day_index\":3},{\"start\":27000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1001454_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":9.0,\"activity\":{\"point_id\":\"1001454\",\"duration\":9,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":68400,\"day_index\":0},{\"start\":32400,\"end\":68400,\"day_index\":1},{\"start\":32400,\"end\":68400,\"day_index\":2},{\"start\":32400,\"end\":68400,\"day_index\":3},{\"start\":32400,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1143992_SAV_ 84_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1143992\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":62100,\"day_index\":0},{\"start\":32400,\"end\":62100,\"day_index\":1},{\"start\":32400,\"end\":62100,\"day_index\":2},{\"start\":32400,\"end\":62100,\"day_index\":3},{\"start\":32400,\"end\":62100,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1143992_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1143992\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":62100,\"day_index\":0},{\"start\":32400,\"end\":62100,\"day_index\":1},{\"start\":32400,\"end\":62100,\"day_index\":2},{\"start\":32400,\"end\":62100,\"day_index\":3},{\"start\":32400,\"end\":62100,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020782_CLI_ 42_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":68.77},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":217.0}],\"visits_number\":6,\"minimum_lapse\":556.0,\"activity\":{\"point_id\":\"1020782\",\"duration\":556,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":57600,\"day_index\":0},{\"start\":27000,\"end\":57600,\"day_index\":1},{\"start\":27000,\"end\":57600,\"day_index\":2},{\"start\":27000,\"end\":57600,\"day_index\":3},{\"start\":27000,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020782_INI_ 42_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":68.77},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":217.0}],\"visits_number\":6,\"minimum_lapse\":556.0,\"activity\":{\"point_id\":\"1020782\",\"duration\":556,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":57600,\"day_index\":0},{\"start\":27000,\"end\":57600,\"day_index\":1},{\"start\":27000,\"end\":57600,\"day_index\":2},{\"start\":27000,\"end\":57600,\"day_index\":3},{\"start\":27000,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020782_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":68.77},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":217.0}],\"visits_number\":6,\"minimum_lapse\":556.0,\"activity\":{\"point_id\":\"1020782\",\"duration\":556,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":57600,\"day_index\":0},{\"start\":27000,\"end\":57600,\"day_index\":1},{\"start\":27000,\"end\":57600,\"day_index\":2},{\"start\":27000,\"end\":57600,\"day_index\":3},{\"start\":27000,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020782_SAV_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":68.77},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":217.0}],\"visits_number\":6,\"minimum_lapse\":556.0,\"activity\":{\"point_id\":\"1020782\",\"duration\":556,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":57600,\"day_index\":0},{\"start\":27000,\"end\":57600,\"day_index\":1},{\"start\":27000,\"end\":57600,\"day_index\":2},{\"start\":27000,\"end\":57600,\"day_index\":3},{\"start\":27000,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020782_SNC_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":68.77},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":217.0}],\"visits_number\":6,\"minimum_lapse\":556.0,\"activity\":{\"point_id\":\"1020782\",\"duration\":556,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":57600,\"day_index\":0},{\"start\":27000,\"end\":57600,\"day_index\":1},{\"start\":27000,\"end\":57600,\"day_index\":2},{\"start\":27000,\"end\":57600,\"day_index\":3},{\"start\":27000,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1001454_BOB_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":9.0,\"activity\":{\"point_id\":\"1001454\",\"duration\":9,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":68400,\"day_index\":0},{\"start\":32400,\"end\":68400,\"day_index\":1},{\"start\":32400,\"end\":68400,\"day_index\":2},{\"start\":32400,\"end\":68400,\"day_index\":3},{\"start\":32400,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1001454_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":9.0,\"activity\":{\"point_id\":\"1001454\",\"duration\":9,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":68400,\"day_index\":0},{\"start\":32400,\"end\":68400,\"day_index\":1},{\"start\":32400,\"end\":68400,\"day_index\":2},{\"start\":32400,\"end\":68400,\"day_index\":3},{\"start\":32400,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1040631_CLI_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":23.1},{\"unit_id\":\"l\",\"value\":21.0},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":3,\"minimum_lapse\":35.0,\"activity\":{\"point_id\":\"1040631\",\"duration\":35,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":68400,\"day_index\":0},{\"start\":27000,\"end\":68400,\"day_index\":1},{\"start\":27000,\"end\":68400,\"day_index\":2},{\"start\":27000,\"end\":68400,\"day_index\":3},{\"start\":27000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1106440_TAP_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.32},{\"unit_id\":\"l\",\"value\":13.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":12,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1106440\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1103574_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.305},{\"unit_id\":\"l\",\"value\":14.7},{\"unit_id\":\"qte\",\"value\":23.0}],\"visits_number\":3,\"minimum_lapse\":92.0,\"activity\":{\"point_id\":\"1103574\",\"duration\":92,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1103574_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.305},{\"unit_id\":\"l\",\"value\":14.7},{\"unit_id\":\"qte\",\"value\":23.0}],\"visits_number\":3,\"minimum_lapse\":92.0,\"activity\":{\"point_id\":\"1103574\",\"duration\":92,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004770_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.93},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004770\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":48600,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":48600,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":48600,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":48600,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":48600,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004770_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.93},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004770\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":48600,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":48600,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":48600,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":48600,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":48600,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1143914_TAP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":8.94},{\"unit_id\":\"l\",\"value\":33.9},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1143914\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004770_BOB_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.93},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1004770\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":48600,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":48600,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":48600,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":48600,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":48600,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144594_TAP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.64},{\"unit_id\":\"l\",\"value\":26.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1144594\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144594_DIF_ 84_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.64},{\"unit_id\":\"l\",\"value\":26.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1144594\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144594_INI_ 84_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.64},{\"unit_id\":\"l\",\"value\":26.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1144594\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144594_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.64},{\"unit_id\":\"l\",\"value\":26.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1144594\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144594_CLI_ 84_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.64},{\"unit_id\":\"l\",\"value\":26.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1144594\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1096970_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.0},{\"unit_id\":\"l\",\"value\":10.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1096970\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_CLI_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_PH _ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_ASC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_CLI_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144594_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.64},{\"unit_id\":\"l\",\"value\":26.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1144594\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144594_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.64},{\"unit_id\":\"l\",\"value\":26.4},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":6,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1144594\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133951_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.6},{\"unit_id\":\"l\",\"value\":136.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":6,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1133951\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133951_SNC_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.6},{\"unit_id\":\"l\",\"value\":136.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":6,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1133951\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107065_SNC_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1107065\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":39600,\"end\":68400,\"day_index\":0},{\"start\":39600,\"end\":68400,\"day_index\":1},{\"start\":39600,\"end\":68400,\"day_index\":2},{\"start\":39600,\"end\":68400,\"day_index\":3},{\"start\":39600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107065_SAV_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1107065\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":39600,\"end\":68400,\"day_index\":0},{\"start\":39600,\"end\":68400,\"day_index\":1},{\"start\":39600,\"end\":68400,\"day_index\":2},{\"start\":39600,\"end\":68400,\"day_index\":3},{\"start\":39600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121101_BOB_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.3},{\"unit_id\":\"l\",\"value\":3.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1121101\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121101_CLI_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.3},{\"unit_id\":\"l\",\"value\":3.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1121101\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119750_SAV_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":39.0},{\"unit_id\":\"l\",\"value\":184.68},{\"unit_id\":\"qte\",\"value\":30.0}],\"visits_number\":6,\"minimum_lapse\":240.0,\"activity\":{\"point_id\":\"1119750\",\"duration\":240,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":39600,\"day_index\":0},{\"start\":51300,\"end\":56700,\"day_index\":0},{\"start\":30600,\"end\":39600,\"day_index\":1},{\"start\":51300,\"end\":56700,\"day_index\":1},{\"start\":30600,\"end\":39600,\"day_index\":2},{\"start\":51300,\"end\":56700,\"day_index\":2},{\"start\":30600,\"end\":39600,\"day_index\":3},{\"start\":51300,\"end\":56700,\"day_index\":3},{\"start\":30600,\"end\":39600,\"day_index\":4},{\"start\":51300,\"end\":56700,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119750_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":39.0},{\"unit_id\":\"l\",\"value\":184.68},{\"unit_id\":\"qte\",\"value\":30.0}],\"visits_number\":6,\"minimum_lapse\":240.0,\"activity\":{\"point_id\":\"1119750\",\"duration\":240,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":39600,\"day_index\":0},{\"start\":51300,\"end\":56700,\"day_index\":0},{\"start\":30600,\"end\":39600,\"day_index\":1},{\"start\":51300,\"end\":56700,\"day_index\":1},{\"start\":30600,\"end\":39600,\"day_index\":2},{\"start\":51300,\"end\":56700,\"day_index\":2},{\"start\":30600,\"end\":39600,\"day_index\":3},{\"start\":51300,\"end\":56700,\"day_index\":3},{\"start\":30600,\"end\":39600,\"day_index\":4},{\"start\":51300,\"end\":56700,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119751_EMP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.3},{\"unit_id\":\"l\",\"value\":3.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1119751\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119751_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.3},{\"unit_id\":\"l\",\"value\":3.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1119751\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119751_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.3},{\"unit_id\":\"l\",\"value\":3.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1119751\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1119750_EMP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":39.0},{\"unit_id\":\"l\",\"value\":184.68},{\"unit_id\":\"qte\",\"value\":30.0}],\"visits_number\":6,\"minimum_lapse\":240.0,\"activity\":{\"point_id\":\"1119750\",\"duration\":240,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":39600,\"day_index\":0},{\"start\":51300,\"end\":56700,\"day_index\":0},{\"start\":30600,\"end\":39600,\"day_index\":1},{\"start\":51300,\"end\":56700,\"day_index\":1},{\"start\":30600,\"end\":39600,\"day_index\":2},{\"start\":51300,\"end\":56700,\"day_index\":2},{\"start\":30600,\"end\":39600,\"day_index\":3},{\"start\":51300,\"end\":56700,\"day_index\":3},{\"start\":30600,\"end\":39600,\"day_index\":4},{\"start\":51300,\"end\":56700,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1096970_DIF_ 84_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":11.0},{\"unit_id\":\"l\",\"value\":10.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1096970\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":57600,\"day_index\":0},{\"start\":28800,\"end\":57600,\"day_index\":1},{\"start\":28800,\"end\":57600,\"day_index\":2},{\"start\":28800,\"end\":57600,\"day_index\":3},{\"start\":28800,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121101_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.3},{\"unit_id\":\"l\",\"value\":3.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1121101\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1129651_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":2.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1129651\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133951_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.6},{\"unit_id\":\"l\",\"value\":136.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":6,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1133951\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133959_DIF_ 84_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1133959\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133959_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1133959\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133951_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.6},{\"unit_id\":\"l\",\"value\":136.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":6,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1133951\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133959_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1133959\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133959_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1133959\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1133959_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1133959\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132589_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":48.4},{\"unit_id\":\"l\",\"value\":136.4},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":12,\"minimum_lapse\":286.0,\"activity\":{\"point_id\":\"1132589\",\"duration\":286,\"setup_duration\":120,\"timewindows\":[{\"start\":23400,\"end\":30600,\"day_index\":0},{\"start\":23400,\"end\":30600,\"day_index\":1},{\"start\":23400,\"end\":30600,\"day_index\":2},{\"start\":23400,\"end\":30600,\"day_index\":3},{\"start\":23400,\"end\":30600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1129651_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":2.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1129651\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121101_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.3},{\"unit_id\":\"l\",\"value\":3.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":12.0,\"activity\":{\"point_id\":\"1121101\",\"duration\":12,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099019_BOB_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":43.364},{\"unit_id\":\"l\",\"value\":123.8},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":6,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1099019\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099019_PH _ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":43.364},{\"unit_id\":\"l\",\"value\":123.8},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":6,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1099019\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099019_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":43.364},{\"unit_id\":\"l\",\"value\":123.8},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":6,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1099019\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002504_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":77.0},{\"unit_id\":\"l\",\"value\":217.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":12,\"minimum_lapse\":455.0,\"activity\":{\"point_id\":\"1002504\",\"duration\":455,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":32400,\"day_index\":0},{\"start\":21600,\"end\":32400,\"day_index\":1},{\"start\":21600,\"end\":32400,\"day_index\":2},{\"start\":21600,\"end\":32400,\"day_index\":3},{\"start\":21600,\"end\":32400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_TAP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1121283_BOB_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.05},{\"unit_id\":\"l\",\"value\":12.8},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1121283\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":64800,\"day_index\":0},{\"start\":36000,\"end\":64800,\"day_index\":1},{\"start\":36000,\"end\":64800,\"day_index\":2},{\"start\":36000,\"end\":64800,\"day_index\":3},{\"start\":36000,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1124357_SNC_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1124357\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130453_BOB_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":52.8},{\"unit_id\":\"l\",\"value\":148.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":6,\"minimum_lapse\":312.0,\"activity\":{\"point_id\":\"1130453\",\"duration\":312,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130453_CLI_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":52.8},{\"unit_id\":\"l\",\"value\":148.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":6,\"minimum_lapse\":312.0,\"activity\":{\"point_id\":\"1130453\",\"duration\":312,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130453_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":52.8},{\"unit_id\":\"l\",\"value\":148.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":6,\"minimum_lapse\":312.0,\"activity\":{\"point_id\":\"1130453\",\"duration\":312,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1130453_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":52.8},{\"unit_id\":\"l\",\"value\":148.8},{\"unit_id\":\"qte\",\"value\":24.0}],\"visits_number\":6,\"minimum_lapse\":312.0,\"activity\":{\"point_id\":\"1130453\",\"duration\":312,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132589_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":48.4},{\"unit_id\":\"l\",\"value\":136.4},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":12,\"minimum_lapse\":286.0,\"activity\":{\"point_id\":\"1132589\",\"duration\":286,\"setup_duration\":120,\"timewindows\":[{\"start\":23400,\"end\":30600,\"day_index\":0},{\"start\":23400,\"end\":30600,\"day_index\":1},{\"start\":23400,\"end\":30600,\"day_index\":2},{\"start\":23400,\"end\":30600,\"day_index\":3},{\"start\":23400,\"end\":30600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005919_BOB_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":89.46},{\"unit_id\":\"l\",\"value\":276.0},{\"unit_id\":\"qte\",\"value\":60.0}],\"visits_number\":12,\"minimum_lapse\":274.0,\"activity\":{\"point_id\":\"1005919\",\"duration\":274,\"setup_duration\":120,\"timewindows\":[{\"start\":24300,\"end\":43200,\"day_index\":0},{\"start\":24300,\"end\":43200,\"day_index\":1},{\"start\":24300,\"end\":43200,\"day_index\":2},{\"start\":24300,\"end\":43200,\"day_index\":3},{\"start\":24300,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142237_EMP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.1},{\"unit_id\":\"l\",\"value\":43.092},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1142237\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1054230_BOB_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":1,\"minimum_lapse\":80.0,\"activity\":{\"point_id\":\"1054230\",\"duration\":80,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":61200,\"day_index\":0},{\"start\":36000,\"end\":61200,\"day_index\":1},{\"start\":36000,\"end\":61200,\"day_index\":2},{\"start\":36000,\"end\":61200,\"day_index\":3},{\"start\":36000,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1054230_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":1,\"minimum_lapse\":80.0,\"activity\":{\"point_id\":\"1054230\",\"duration\":80,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":61200,\"day_index\":0},{\"start\":36000,\"end\":61200,\"day_index\":1},{\"start\":36000,\"end\":61200,\"day_index\":2},{\"start\":36000,\"end\":61200,\"day_index\":3},{\"start\":36000,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1088315_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.8},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1088315\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1087334_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":60.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1087334\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1088315_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.8},{\"unit_id\":\"l\",\"value\":30.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1088315\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1087334_CLI_ 84_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":60.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1087334\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1087334_DIF_ 42_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":60.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1087334\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1087334_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":60.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1087334\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1087334_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":60.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":360.0,\"activity\":{\"point_id\":\"1087334\",\"duration\":360,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1054230_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":1,\"minimum_lapse\":80.0,\"activity\":{\"point_id\":\"1054230\",\"duration\":80,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":61200,\"day_index\":0},{\"start\":36000,\"end\":61200,\"day_index\":1},{\"start\":36000,\"end\":61200,\"day_index\":2},{\"start\":36000,\"end\":61200,\"day_index\":3},{\"start\":36000,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1054230_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.0},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":1,\"minimum_lapse\":80.0,\"activity\":{\"point_id\":\"1054230\",\"duration\":80,\"setup_duration\":120,\"timewindows\":[{\"start\":36000,\"end\":61200,\"day_index\":0},{\"start\":36000,\"end\":61200,\"day_index\":1},{\"start\":36000,\"end\":61200,\"day_index\":2},{\"start\":36000,\"end\":61200,\"day_index\":3},{\"start\":36000,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1058540_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.2},{\"unit_id\":\"l\",\"value\":68.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1058540\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":72000,\"day_index\":0},{\"start\":30600,\"end\":72000,\"day_index\":1},{\"start\":30600,\"end\":72000,\"day_index\":2},{\"start\":30600,\"end\":72000,\"day_index\":3},{\"start\":30600,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1109631_PCP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1109631\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142237_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.1},{\"unit_id\":\"l\",\"value\":43.092},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1142237\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137030_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1137030\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":63000,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":63000,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":63000,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":63000,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020782_SAV_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":68.77},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":217.0}],\"visits_number\":6,\"minimum_lapse\":556.0,\"activity\":{\"point_id\":\"1020782\",\"duration\":556,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":57600,\"day_index\":0},{\"start\":27000,\"end\":57600,\"day_index\":1},{\"start\":27000,\"end\":57600,\"day_index\":2},{\"start\":27000,\"end\":57600,\"day_index\":3},{\"start\":27000,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020782_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":68.77},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":217.0}],\"visits_number\":6,\"minimum_lapse\":556.0,\"activity\":{\"point_id\":\"1020782\",\"duration\":556,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":57600,\"day_index\":0},{\"start\":27000,\"end\":57600,\"day_index\":1},{\"start\":27000,\"end\":57600,\"day_index\":2},{\"start\":27000,\"end\":57600,\"day_index\":3},{\"start\":27000,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020782_DIF_ 42_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":68.77},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":217.0}],\"visits_number\":6,\"minimum_lapse\":556.0,\"activity\":{\"point_id\":\"1020782\",\"duration\":556,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":57600,\"day_index\":0},{\"start\":27000,\"end\":57600,\"day_index\":1},{\"start\":27000,\"end\":57600,\"day_index\":2},{\"start\":27000,\"end\":57600,\"day_index\":3},{\"start\":27000,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1040631_TAP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":23.1},{\"unit_id\":\"l\",\"value\":21.0},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":3,\"minimum_lapse\":35.0,\"activity\":{\"point_id\":\"1040631\",\"duration\":35,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":68400,\"day_index\":0},{\"start\":27000,\"end\":68400,\"day_index\":1},{\"start\":27000,\"end\":68400,\"day_index\":2},{\"start\":27000,\"end\":68400,\"day_index\":3},{\"start\":27000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1040631_PH _ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":23.1},{\"unit_id\":\"l\",\"value\":21.0},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":3,\"minimum_lapse\":35.0,\"activity\":{\"point_id\":\"1040631\",\"duration\":35,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":68400,\"day_index\":0},{\"start\":27000,\"end\":68400,\"day_index\":1},{\"start\":27000,\"end\":68400,\"day_index\":2},{\"start\":27000,\"end\":68400,\"day_index\":3},{\"start\":27000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1040631_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":23.1},{\"unit_id\":\"l\",\"value\":21.0},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":3,\"minimum_lapse\":35.0,\"activity\":{\"point_id\":\"1040631\",\"duration\":35,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":68400,\"day_index\":0},{\"start\":27000,\"end\":68400,\"day_index\":1},{\"start\":27000,\"end\":68400,\"day_index\":2},{\"start\":27000,\"end\":68400,\"day_index\":3},{\"start\":27000,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107065_PH _ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1107065\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":39600,\"end\":68400,\"day_index\":0},{\"start\":39600,\"end\":68400,\"day_index\":1},{\"start\":39600,\"end\":68400,\"day_index\":2},{\"start\":39600,\"end\":68400,\"day_index\":3},{\"start\":39600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107065_PCP_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1107065\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":39600,\"end\":68400,\"day_index\":0},{\"start\":39600,\"end\":68400,\"day_index\":1},{\"start\":39600,\"end\":68400,\"day_index\":2},{\"start\":39600,\"end\":68400,\"day_index\":3},{\"start\":39600,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1099019_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":43.364},{\"unit_id\":\"l\",\"value\":123.8},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":6,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1099019\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1020782_SNC_ 14_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":68.77},{\"unit_id\":\"l\",\"value\":126.0},{\"unit_id\":\"qte\",\"value\":217.0}],\"visits_number\":6,\"minimum_lapse\":556.0,\"activity\":{\"point_id\":\"1020782\",\"duration\":556,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":57600,\"day_index\":0},{\"start\":27000,\"end\":57600,\"day_index\":1},{\"start\":27000,\"end\":57600,\"day_index\":2},{\"start\":27000,\"end\":57600,\"day_index\":3},{\"start\":27000,\"end\":57600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137030_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1137030\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":63000,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":63000,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":63000,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":63000,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1106440_TAP_  7_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.32},{\"unit_id\":\"l\",\"value\":13.2},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":12,\"minimum_lapse\":200.0,\"activity\":{\"point_id\":\"1106440\",\"duration\":200,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142237_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.1},{\"unit_id\":\"l\",\"value\":43.092},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1142237\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137030_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1137030\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":63000,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":63000,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":63000,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":63000,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134263_BOB_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.06},{\"unit_id\":\"l\",\"value\":75.6},{\"unit_id\":\"qte\",\"value\":36.0}],\"visits_number\":3,\"minimum_lapse\":144.0,\"activity\":{\"point_id\":\"1134263\",\"duration\":144,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134263_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.06},{\"unit_id\":\"l\",\"value\":75.6},{\"unit_id\":\"qte\",\"value\":36.0}],\"visits_number\":3,\"minimum_lapse\":144.0,\"activity\":{\"point_id\":\"1134263\",\"duration\":144,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134263_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.06},{\"unit_id\":\"l\",\"value\":75.6},{\"unit_id\":\"qte\",\"value\":36.0}],\"visits_number\":3,\"minimum_lapse\":144.0,\"activity\":{\"point_id\":\"1134263\",\"duration\":144,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134263_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":12.06},{\"unit_id\":\"l\",\"value\":75.6},{\"unit_id\":\"qte\",\"value\":36.0}],\"visits_number\":3,\"minimum_lapse\":144.0,\"activity\":{\"point_id\":\"1134263\",\"duration\":144,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":66600,\"day_index\":0},{\"start\":30600,\"end\":66600,\"day_index\":1},{\"start\":30600,\"end\":66600,\"day_index\":2},{\"start\":30600,\"end\":66600,\"day_index\":3},{\"start\":30600,\"end\":66600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137030_EMP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1137030\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":63000,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":63000,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":63000,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":63000,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1137030_PCP_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1137030\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":63000,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":63000,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":63000,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":63000,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132589_PH _ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":48.4},{\"unit_id\":\"l\",\"value\":136.4},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":12,\"minimum_lapse\":286.0,\"activity\":{\"point_id\":\"1132589\",\"duration\":286,\"setup_duration\":120,\"timewindows\":[{\"start\":23400,\"end\":30600,\"day_index\":0},{\"start\":23400,\"end\":30600,\"day_index\":1},{\"start\":23400,\"end\":30600,\"day_index\":2},{\"start\":23400,\"end\":30600,\"day_index\":3},{\"start\":23400,\"end\":30600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132589_SAV_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":48.4},{\"unit_id\":\"l\",\"value\":136.4},{\"unit_id\":\"qte\",\"value\":22.0}],\"visits_number\":12,\"minimum_lapse\":286.0,\"activity\":{\"point_id\":\"1132589\",\"duration\":286,\"setup_duration\":120,\"timewindows\":[{\"start\":23400,\"end\":30600,\"day_index\":0},{\"start\":23400,\"end\":30600,\"day_index\":1},{\"start\":23400,\"end\":30600,\"day_index\":2},{\"start\":23400,\"end\":30600,\"day_index\":3},{\"start\":23400,\"end\":30600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1142237_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.1},{\"unit_id\":\"l\",\"value\":43.092},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1142237\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1107406_SNC_ 28_4FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1107406\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":61200,\"day_index\":0},{\"start\":28800,\"end\":61200,\"day_index\":1},{\"start\":28800,\"end\":61200,\"day_index\":2},{\"start\":28800,\"end\":61200,\"day_index\":3},{\"start\":28800,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1120539_EMP_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":2.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1120539\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":64800,\"day_index\":0},{\"start\":34200,\"end\":64800,\"day_index\":1},{\"start\":34200,\"end\":64800,\"day_index\":2},{\"start\":34200,\"end\":64800,\"day_index\":3},{\"start\":34200,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1120539_SAV_ 28_4FA\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.2},{\"unit_id\":\"l\",\"value\":2.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":3,\"minimum_lapse\":8.0,\"activity\":{\"point_id\":\"1120539\",\"duration\":8,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":64800,\"day_index\":0},{\"start\":34200,\"end\":64800,\"day_index\":1},{\"start\":34200,\"end\":64800,\"day_index\":2},{\"start\":34200,\"end\":64800,\"day_index\":3},{\"start\":34200,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004647_PH _ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.5},{\"unit_id\":\"l\",\"value\":128.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":6,\"minimum_lapse\":80.0,\"activity\":{\"point_id\":\"1004647\",\"duration\":80,\"setup_duration\":120,\"timewindows\":[{\"start\":37800,\"end\":64800,\"day_index\":0},{\"start\":37800,\"end\":64800,\"day_index\":1},{\"start\":37800,\"end\":64800,\"day_index\":2},{\"start\":37800,\"end\":64800,\"day_index\":3},{\"start\":37800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004647_DIF_ 42_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.5},{\"unit_id\":\"l\",\"value\":128.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":6,\"minimum_lapse\":80.0,\"activity\":{\"point_id\":\"1004647\",\"duration\":80,\"setup_duration\":120,\"timewindows\":[{\"start\":37800,\"end\":64800,\"day_index\":0},{\"start\":37800,\"end\":64800,\"day_index\":1},{\"start\":37800,\"end\":64800,\"day_index\":2},{\"start\":37800,\"end\":64800,\"day_index\":3},{\"start\":37800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004647_SNC_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.5},{\"unit_id\":\"l\",\"value\":128.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":6,\"minimum_lapse\":80.0,\"activity\":{\"point_id\":\"1004647\",\"duration\":80,\"setup_duration\":120,\"timewindows\":[{\"start\":37800,\"end\":64800,\"day_index\":0},{\"start\":37800,\"end\":64800,\"day_index\":1},{\"start\":37800,\"end\":64800,\"day_index\":2},{\"start\":37800,\"end\":64800,\"day_index\":3},{\"start\":37800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004647_PCP_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.5},{\"unit_id\":\"l\",\"value\":128.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":6,\"minimum_lapse\":80.0,\"activity\":{\"point_id\":\"1004647\",\"duration\":80,\"setup_duration\":120,\"timewindows\":[{\"start\":37800,\"end\":64800,\"day_index\":0},{\"start\":37800,\"end\":64800,\"day_index\":1},{\"start\":37800,\"end\":64800,\"day_index\":2},{\"start\":37800,\"end\":64800,\"day_index\":3},{\"start\":37800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004716_BOB_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":6,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1004716\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":43200,\"day_index\":0},{\"start\":30600,\"end\":43200,\"day_index\":1},{\"start\":30600,\"end\":43200,\"day_index\":2},{\"start\":30600,\"end\":43200,\"day_index\":3},{\"start\":30600,\"end\":43200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144936_PCP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.85},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":3,\"minimum_lapse\":140.0,\"activity\":{\"point_id\":\"1144936\",\"duration\":140,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144936_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.85},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":3,\"minimum_lapse\":140.0,\"activity\":{\"point_id\":\"1144936\",\"duration\":140,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144936_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.85},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":3,\"minimum_lapse\":140.0,\"activity\":{\"point_id\":\"1144936\",\"duration\":140,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134666_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1134666\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004647_BOB_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.5},{\"unit_id\":\"l\",\"value\":128.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":6,\"minimum_lapse\":80.0,\"activity\":{\"point_id\":\"1004647\",\"duration\":80,\"setup_duration\":120,\"timewindows\":[{\"start\":37800,\"end\":64800,\"day_index\":0},{\"start\":37800,\"end\":64800,\"day_index\":1},{\"start\":37800,\"end\":64800,\"day_index\":2},{\"start\":37800,\"end\":64800,\"day_index\":3},{\"start\":37800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1006725_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.15},{\"unit_id\":\"l\",\"value\":38.4},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1006725\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":65700,\"day_index\":0},{\"start\":32400,\"end\":65700,\"day_index\":1},{\"start\":32400,\"end\":65700,\"day_index\":2},{\"start\":32400,\"end\":65700,\"day_index\":3},{\"start\":32400,\"end\":65700,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1006725_PCP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.15},{\"unit_id\":\"l\",\"value\":38.4},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1006725\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":65700,\"day_index\":0},{\"start\":32400,\"end\":65700,\"day_index\":1},{\"start\":32400,\"end\":65700,\"day_index\":2},{\"start\":32400,\"end\":65700,\"day_index\":3},{\"start\":32400,\"end\":65700,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1092502_PCP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.01},{\"unit_id\":\"l\",\"value\":12.6},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1092502\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1092502_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.01},{\"unit_id\":\"l\",\"value\":12.6},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1092502\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1092502_BOB_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.01},{\"unit_id\":\"l\",\"value\":12.6},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1092502\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1092502_CLI_ 84_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.01},{\"unit_id\":\"l\",\"value\":12.6},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1092502\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1008001_TAP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.5},{\"unit_id\":\"l\",\"value\":29.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":115.0,\"activity\":{\"point_id\":\"1008001\",\"duration\":115,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1006725_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":3.15},{\"unit_id\":\"l\",\"value\":38.4},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":3,\"minimum_lapse\":24.0,\"activity\":{\"point_id\":\"1006725\",\"duration\":24,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":65700,\"day_index\":0},{\"start\":32400,\"end\":65700,\"day_index\":1},{\"start\":32400,\"end\":65700,\"day_index\":2},{\"start\":32400,\"end\":65700,\"day_index\":3},{\"start\":32400,\"end\":65700,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1008001_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.5},{\"unit_id\":\"l\",\"value\":29.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":115.0,\"activity\":{\"point_id\":\"1008001\",\"duration\":115,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1008001_BOB_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.5},{\"unit_id\":\"l\",\"value\":29.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":115.0,\"activity\":{\"point_id\":\"1008001\",\"duration\":115,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1008001_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.5},{\"unit_id\":\"l\",\"value\":29.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":115.0,\"activity\":{\"point_id\":\"1008001\",\"duration\":115,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144936_SNC_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.85},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":35.0}],\"visits_number\":3,\"minimum_lapse\":140.0,\"activity\":{\"point_id\":\"1144936\",\"duration\":140,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144493_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.3},{\"unit_id\":\"l\",\"value\":76.8},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1144493\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144493_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.3},{\"unit_id\":\"l\",\"value\":76.8},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1144493\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144493_EMP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.3},{\"unit_id\":\"l\",\"value\":76.8},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1144493\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1147114_PCP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.94},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":14.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1147114\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1147114_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.94},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":14.0}],\"visits_number\":3,\"minimum_lapse\":56.0,\"activity\":{\"point_id\":\"1147114\",\"duration\":56,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1147721_BOB_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1147721\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":46800,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":46800,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":46800,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":46800,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":46800,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1147721_EMP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1147721\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":46800,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":46800,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":46800,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":46800,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":46800,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1147721_PCP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1147721\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":46800,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":46800,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":46800,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":46800,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":46800,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1147721_SNC_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1147721\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":46800,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":46800,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":46800,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":46800,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":46800,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1147721_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1147721\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":46800,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":46800,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":46800,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":46800,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":46800,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1147721_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":17.6},{\"unit_id\":\"l\",\"value\":49.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":3,\"minimum_lapse\":104.0,\"activity\":{\"point_id\":\"1147721\",\"duration\":104,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":46800,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":46800,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":46800,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":46800,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":46800,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1003152_SNC_ 42_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.2},{\"unit_id\":\"l\",\"value\":141.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":2,\"minimum_lapse\":270.0,\"activity\":{\"point_id\":\"1003152\",\"duration\":270,\"setup_duration\":120,\"timewindows\":[{\"start\":27900,\"end\":64800,\"day_index\":0},{\"start\":27900,\"end\":64800,\"day_index\":1},{\"start\":27900,\"end\":64800,\"day_index\":2},{\"start\":27900,\"end\":64800,\"day_index\":3},{\"start\":27900,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1110450_BOB_  7_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":46.2},{\"unit_id\":\"l\",\"value\":130.2},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":12,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1110450\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070260_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1070260\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":25200,\"end\":64800,\"day_index\":0},{\"start\":25200,\"end\":64800,\"day_index\":1},{\"start\":25200,\"end\":64800,\"day_index\":2},{\"start\":25200,\"end\":64800,\"day_index\":3},{\"start\":25200,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1110450_PH _  7_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":46.2},{\"unit_id\":\"l\",\"value\":130.2},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":12,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1110450\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1110450_TAP_  7_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":46.2},{\"unit_id\":\"l\",\"value\":130.2},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":12,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1110450\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134666_PCP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1134666\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134666_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1134666\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132451_SNC_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1132451\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132451_EMP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1132451\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132451_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1132451\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132451_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1132451\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":45000,\"day_index\":0},{\"start\":50400,\"end\":64800,\"day_index\":0},{\"start\":34200,\"end\":45000,\"day_index\":1},{\"start\":50400,\"end\":64800,\"day_index\":1},{\"start\":34200,\"end\":45000,\"day_index\":2},{\"start\":50400,\"end\":64800,\"day_index\":2},{\"start\":34200,\"end\":45000,\"day_index\":3},{\"start\":50400,\"end\":64800,\"day_index\":3},{\"start\":34200,\"end\":45000,\"day_index\":4},{\"start\":50400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1122595_BOB_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":28.6},{\"unit_id\":\"l\",\"value\":80.6},{\"unit_id\":\"qte\",\"value\":13.0}],\"visits_number\":3,\"minimum_lapse\":169.0,\"activity\":{\"point_id\":\"1122595\",\"duration\":169,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":39600,\"day_index\":0},{\"start\":28800,\"end\":39600,\"day_index\":1},{\"start\":28800,\"end\":39600,\"day_index\":2},{\"start\":28800,\"end\":39600,\"day_index\":3},{\"start\":28800,\"end\":39600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1122595_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":28.6},{\"unit_id\":\"l\",\"value\":80.6},{\"unit_id\":\"qte\",\"value\":13.0}],\"visits_number\":3,\"minimum_lapse\":169.0,\"activity\":{\"point_id\":\"1122595\",\"duration\":169,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":39600,\"day_index\":0},{\"start\":28800,\"end\":39600,\"day_index\":1},{\"start\":28800,\"end\":39600,\"day_index\":2},{\"start\":28800,\"end\":39600,\"day_index\":3},{\"start\":28800,\"end\":39600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1122595_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":28.6},{\"unit_id\":\"l\",\"value\":80.6},{\"unit_id\":\"qte\",\"value\":13.0}],\"visits_number\":3,\"minimum_lapse\":169.0,\"activity\":{\"point_id\":\"1122595\",\"duration\":169,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":39600,\"day_index\":0},{\"start\":28800,\"end\":39600,\"day_index\":1},{\"start\":28800,\"end\":39600,\"day_index\":2},{\"start\":28800,\"end\":39600,\"day_index\":3},{\"start\":28800,\"end\":39600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1110450_SAV_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":46.2},{\"unit_id\":\"l\",\"value\":130.2},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":12,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1110450\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1003152_TAP_  7_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.2},{\"unit_id\":\"l\",\"value\":141.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":2,\"minimum_lapse\":270.0,\"activity\":{\"point_id\":\"1003152\",\"duration\":270,\"setup_duration\":120,\"timewindows\":[{\"start\":27900,\"end\":64800,\"day_index\":0},{\"start\":27900,\"end\":64800,\"day_index\":1},{\"start\":27900,\"end\":64800,\"day_index\":2},{\"start\":27900,\"end\":64800,\"day_index\":3},{\"start\":27900,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070260_PCP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1070260\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":25200,\"end\":64800,\"day_index\":0},{\"start\":25200,\"end\":64800,\"day_index\":1},{\"start\":25200,\"end\":64800,\"day_index\":2},{\"start\":25200,\"end\":64800,\"day_index\":3},{\"start\":25200,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070260_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1070260\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":25200,\"end\":64800,\"day_index\":0},{\"start\":25200,\"end\":64800,\"day_index\":1},{\"start\":25200,\"end\":64800,\"day_index\":2},{\"start\":25200,\"end\":64800,\"day_index\":3},{\"start\":25200,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134348_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1134348\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134348_SNC_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1134348\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127201_PCP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.28},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":68.0}],\"visits_number\":3,\"minimum_lapse\":272.0,\"activity\":{\"point_id\":\"1127201\",\"duration\":272,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134348_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1134348\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1127201_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":14.28},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":68.0}],\"visits_number\":3,\"minimum_lapse\":272.0,\"activity\":{\"point_id\":\"1127201\",\"duration\":272,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1138580_EMP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.2},{\"unit_id\":\"l\",\"value\":24.624},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":32.0,\"activity\":{\"point_id\":\"1138580\",\"duration\":32,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1138580_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.2},{\"unit_id\":\"l\",\"value\":24.624},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":32.0,\"activity\":{\"point_id\":\"1138580\",\"duration\":32,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1138580_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.2},{\"unit_id\":\"l\",\"value\":24.624},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":32.0,\"activity\":{\"point_id\":\"1138580\",\"duration\":32,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1143039_TAP_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":26.56},{\"unit_id\":\"l\",\"value\":105.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":6,\"minimum_lapse\":800.0,\"activity\":{\"point_id\":\"1143039\",\"duration\":800,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1134348_EMP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.4},{\"unit_id\":\"l\",\"value\":4.0},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1134348\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":30600,\"end\":61200,\"day_index\":0},{\"start\":30600,\"end\":61200,\"day_index\":1},{\"start\":30600,\"end\":61200,\"day_index\":2},{\"start\":30600,\"end\":61200,\"day_index\":3},{\"start\":30600,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132224_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1132224\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":70200,\"day_index\":0},{\"start\":34200,\"end\":70200,\"day_index\":1},{\"start\":34200,\"end\":70200,\"day_index\":2},{\"start\":34200,\"end\":70200,\"day_index\":3},{\"start\":34200,\"end\":70200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132224_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1132224\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":70200,\"day_index\":0},{\"start\":34200,\"end\":70200,\"day_index\":1},{\"start\":34200,\"end\":70200,\"day_index\":2},{\"start\":34200,\"end\":70200,\"day_index\":3},{\"start\":34200,\"end\":70200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1110450_PH _  7_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":46.2},{\"unit_id\":\"l\",\"value\":130.2},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":12,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1110450\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1110450_TAP_  7_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":46.2},{\"unit_id\":\"l\",\"value\":130.2},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":12,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1110450\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1095177_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.2},{\"unit_id\":\"l\",\"value\":51.2},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":32.0,\"activity\":{\"point_id\":\"1095177\",\"duration\":32,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1111407_TAP_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":37.5},{\"unit_id\":\"l\",\"value\":145.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":6,\"minimum_lapse\":500.0,\"activity\":{\"point_id\":\"1111407\",\"duration\":500,\"setup_duration\":120,\"timewindows\":[{\"start\":25200,\"end\":50400,\"day_index\":0},{\"start\":25200,\"end\":50400,\"day_index\":1},{\"start\":25200,\"end\":50400,\"day_index\":2},{\"start\":25200,\"end\":50400,\"day_index\":3},{\"start\":25200,\"end\":50400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1117925_EMP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.8},{\"unit_id\":\"l\",\"value\":36.936},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1117925\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1117925_SNC_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.8},{\"unit_id\":\"l\",\"value\":36.936},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1117925\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1117925_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.8},{\"unit_id\":\"l\",\"value\":36.936},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1117925\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1117925_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.8},{\"unit_id\":\"l\",\"value\":36.936},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1117925\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1132224_BOB_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.1},{\"unit_id\":\"l\",\"value\":1.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1132224\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":34200,\"end\":70200,\"day_index\":0},{\"start\":34200,\"end\":70200,\"day_index\":1},{\"start\":34200,\"end\":70200,\"day_index\":2},{\"start\":34200,\"end\":70200,\"day_index\":3},{\"start\":34200,\"end\":70200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1138580_SNC_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.2},{\"unit_id\":\"l\",\"value\":24.624},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":32.0,\"activity\":{\"point_id\":\"1138580\",\"duration\":32,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1135294_CLI_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.1},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1135294\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1135294_PCP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.1},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1135294\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1135294_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.1},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1135294\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1031534_PH _ 84_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":1,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1031534\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":68400,\"day_index\":0},{\"start\":32400,\"end\":68400,\"day_index\":1},{\"start\":32400,\"end\":68400,\"day_index\":2},{\"start\":32400,\"end\":68400,\"day_index\":3},{\"start\":32400,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1031534_SAV_ 84_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":1,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1031534\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":68400,\"day_index\":0},{\"start\":32400,\"end\":68400,\"day_index\":1},{\"start\":32400,\"end\":68400,\"day_index\":2},{\"start\":32400,\"end\":68400,\"day_index\":3},{\"start\":32400,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1047944_PH _ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.05},{\"unit_id\":\"l\",\"value\":12.8},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":31.0,\"activity\":{\"point_id\":\"1047944\",\"duration\":31,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1047944_BOB_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":1.05},{\"unit_id\":\"l\",\"value\":12.8},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":6,\"minimum_lapse\":31.0,\"activity\":{\"point_id\":\"1047944\",\"duration\":31,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1050281_BOB_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":8.8},{\"unit_id\":\"l\",\"value\":24.8},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":6,\"minimum_lapse\":52.0,\"activity\":{\"point_id\":\"1050281\",\"duration\":52,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1054024_SNC_  7_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":6.3},{\"unit_id\":\"l\",\"value\":102.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":12,\"minimum_lapse\":270.0,\"activity\":{\"point_id\":\"1054024\",\"duration\":270,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":72000,\"day_index\":0},{\"start\":27000,\"end\":72000,\"day_index\":1},{\"start\":27000,\"end\":72000,\"day_index\":2},{\"start\":27000,\"end\":72000,\"day_index\":3},{\"start\":27000,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1050281_PCP_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":8.8},{\"unit_id\":\"l\",\"value\":24.8},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":6,\"minimum_lapse\":52.0,\"activity\":{\"point_id\":\"1050281\",\"duration\":52,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1040973_BOB_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":28.6},{\"unit_id\":\"l\",\"value\":80.6},{\"unit_id\":\"qte\",\"value\":13.0}],\"visits_number\":6,\"minimum_lapse\":186.0,\"activity\":{\"point_id\":\"1040973\",\"duration\":186,\"setup_duration\":120,\"timewindows\":[{\"start\":21600,\"end\":75600,\"day_index\":0},{\"start\":21600,\"end\":75600,\"day_index\":1},{\"start\":21600,\"end\":75600,\"day_index\":2},{\"start\":21600,\"end\":75600,\"day_index\":3},{\"start\":21600,\"end\":75600,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1063338_SNC_  7_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":34.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":12,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1063338\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1031534_CLI_ 84_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":1,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1031534\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":68400,\"day_index\":0},{\"start\":32400,\"end\":68400,\"day_index\":1},{\"start\":32400,\"end\":68400,\"day_index\":2},{\"start\":32400,\"end\":68400,\"day_index\":3},{\"start\":32400,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1070260_BOB_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.25},{\"unit_id\":\"l\",\"value\":64.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":40.0,\"activity\":{\"point_id\":\"1070260\",\"duration\":40,\"setup_duration\":120,\"timewindows\":[{\"start\":25200,\"end\":64800,\"day_index\":0},{\"start\":25200,\"end\":64800,\"day_index\":1},{\"start\":25200,\"end\":64800,\"day_index\":2},{\"start\":25200,\"end\":64800,\"day_index\":3},{\"start\":25200,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1031534_BOB_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":1,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1031534\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":68400,\"day_index\":0},{\"start\":32400,\"end\":68400,\"day_index\":1},{\"start\":32400,\"end\":68400,\"day_index\":2},{\"start\":32400,\"end\":68400,\"day_index\":3},{\"start\":32400,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1031918_BOB_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":22.0},{\"unit_id\":\"l\",\"value\":62.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":6,\"minimum_lapse\":277.0,\"activity\":{\"point_id\":\"1031918\",\"duration\":277,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1135294_SNC_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.1},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":4.0,\"activity\":{\"point_id\":\"1135294\",\"duration\":4,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1138580_BOB_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":5.2},{\"unit_id\":\"l\",\"value\":24.624},{\"unit_id\":\"qte\",\"value\":4.0}],\"visits_number\":3,\"minimum_lapse\":32.0,\"activity\":{\"point_id\":\"1138580\",\"duration\":32,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":64800,\"day_index\":0},{\"start\":32400,\"end\":64800,\"day_index\":1},{\"start\":32400,\"end\":64800,\"day_index\":2},{\"start\":32400,\"end\":64800,\"day_index\":3},{\"start\":32400,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1145151_EMP_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.8},{\"unit_id\":\"l\",\"value\":36.936},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1145151\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1145151_PH _ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.8},{\"unit_id\":\"l\",\"value\":36.936},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1145151\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1054036_SNC_  7_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.2},{\"unit_id\":\"l\",\"value\":68.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":12,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1054036\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":72000,\"day_index\":0},{\"start\":27000,\"end\":72000,\"day_index\":1},{\"start\":27000,\"end\":72000,\"day_index\":2},{\"start\":27000,\"end\":72000,\"day_index\":3},{\"start\":27000,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1003152_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.2},{\"unit_id\":\"l\",\"value\":141.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":2,\"minimum_lapse\":270.0,\"activity\":{\"point_id\":\"1003152\",\"duration\":270,\"setup_duration\":120,\"timewindows\":[{\"start\":27900,\"end\":64800,\"day_index\":0},{\"start\":27900,\"end\":64800,\"day_index\":1},{\"start\":27900,\"end\":64800,\"day_index\":2},{\"start\":27900,\"end\":64800,\"day_index\":3},{\"start\":27900,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1003152_ASC_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.2},{\"unit_id\":\"l\",\"value\":141.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":2,\"minimum_lapse\":270.0,\"activity\":{\"point_id\":\"1003152\",\"duration\":270,\"setup_duration\":120,\"timewindows\":[{\"start\":27900,\"end\":64800,\"day_index\":0},{\"start\":27900,\"end\":64800,\"day_index\":1},{\"start\":27900,\"end\":64800,\"day_index\":2},{\"start\":27900,\"end\":64800,\"day_index\":3},{\"start\":27900,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1003152_TAP_  7_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.2},{\"unit_id\":\"l\",\"value\":141.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":2,\"minimum_lapse\":270.0,\"activity\":{\"point_id\":\"1003152\",\"duration\":270,\"setup_duration\":120,\"timewindows\":[{\"start\":27900,\"end\":64800,\"day_index\":0},{\"start\":27900,\"end\":64800,\"day_index\":1},{\"start\":27900,\"end\":64800,\"day_index\":2},{\"start\":27900,\"end\":64800,\"day_index\":3},{\"start\":27900,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1003152_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.2},{\"unit_id\":\"l\",\"value\":141.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":2,\"minimum_lapse\":270.0,\"activity\":{\"point_id\":\"1003152\",\"duration\":270,\"setup_duration\":120,\"timewindows\":[{\"start\":27900,\"end\":64800,\"day_index\":0},{\"start\":27900,\"end\":64800,\"day_index\":1},{\"start\":27900,\"end\":64800,\"day_index\":2},{\"start\":27900,\"end\":64800,\"day_index\":3},{\"start\":27900,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1031534_SNC_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.1},{\"unit_id\":\"l\",\"value\":25.6},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":1,\"minimum_lapse\":16.0,\"activity\":{\"point_id\":\"1031534\",\"duration\":16,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":68400,\"day_index\":0},{\"start\":32400,\"end\":68400,\"day_index\":1},{\"start\":32400,\"end\":68400,\"day_index\":2},{\"start\":32400,\"end\":68400,\"day_index\":3},{\"start\":32400,\"end\":68400,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1110450_BOB_  7_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":46.2},{\"unit_id\":\"l\",\"value\":130.2},{\"unit_id\":\"qte\",\"value\":21.0}],\"visits_number\":12,\"minimum_lapse\":273.0,\"activity\":{\"point_id\":\"1110450\",\"duration\":273,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004708_LPL_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.4},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":182.0,\"activity\":{\"point_id\":\"1004708\",\"duration\":182,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004708_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":0.4},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":5.0}],\"visits_number\":3,\"minimum_lapse\":182.0,\"activity\":{\"point_id\":\"1004708\",\"duration\":182,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":43200,\"day_index\":0},{\"start\":50400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":43200,\"day_index\":1},{\"start\":50400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":43200,\"day_index\":2},{\"start\":50400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":43200,\"day_index\":3},{\"start\":50400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":43200,\"day_index\":4},{\"start\":50400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1145151_EMP_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.8},{\"unit_id\":\"l\",\"value\":36.936},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1145151\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1054036_SNC_  7_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":4.2},{\"unit_id\":\"l\",\"value\":68.0},{\"unit_id\":\"qte\",\"value\":2.0}],\"visits_number\":12,\"minimum_lapse\":180.0,\"activity\":{\"point_id\":\"1054036\",\"duration\":180,\"setup_duration\":120,\"timewindows\":[{\"start\":27000,\"end\":72000,\"day_index\":0},{\"start\":27000,\"end\":72000,\"day_index\":1},{\"start\":27000,\"end\":72000,\"day_index\":2},{\"start\":27000,\"end\":72000,\"day_index\":3},{\"start\":27000,\"end\":72000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1003152_TAP_  7_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.2},{\"unit_id\":\"l\",\"value\":141.0},{\"unit_id\":\"qte\",\"value\":3.0}],\"visits_number\":2,\"minimum_lapse\":270.0,\"activity\":{\"point_id\":\"1003152\",\"duration\":270,\"setup_duration\":120,\"timewindows\":[{\"start\":27900,\"end\":64800,\"day_index\":0},{\"start\":27900,\"end\":64800,\"day_index\":1},{\"start\":27900,\"end\":64800,\"day_index\":2},{\"start\":27900,\"end\":64800,\"day_index\":3},{\"start\":27900,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1145151_PH _ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.8},{\"unit_id\":\"l\",\"value\":36.936},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1145151\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002561_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.6},{\"unit_id\":\"l\",\"value\":128.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":46.0,\"activity\":{\"point_id\":\"1002561\",\"duration\":46,\"setup_duration\":120,\"timewindows\":[{\"start\":29700,\"end\":64800,\"day_index\":0},{\"start\":29700,\"end\":64800,\"day_index\":1},{\"start\":29700,\"end\":64800,\"day_index\":2},{\"start\":29700,\"end\":64800,\"day_index\":3},{\"start\":29700,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002561_PCP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.6},{\"unit_id\":\"l\",\"value\":128.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":46.0,\"activity\":{\"point_id\":\"1002561\",\"duration\":46,\"setup_duration\":120,\"timewindows\":[{\"start\":29700,\"end\":64800,\"day_index\":0},{\"start\":29700,\"end\":64800,\"day_index\":1},{\"start\":29700,\"end\":64800,\"day_index\":2},{\"start\":29700,\"end\":64800,\"day_index\":3},{\"start\":29700,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005880_EMP_ 42_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.5},{\"unit_id\":\"l\",\"value\":41.72},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":2,\"minimum_lapse\":61.0,\"activity\":{\"point_id\":\"1005880\",\"duration\":61,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005880_SAV_ 42_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.5},{\"unit_id\":\"l\",\"value\":41.72},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":2,\"minimum_lapse\":61.0,\"activity\":{\"point_id\":\"1005880\",\"duration\":61,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1002561_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":9.6},{\"unit_id\":\"l\",\"value\":128.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":3,\"minimum_lapse\":46.0,\"activity\":{\"point_id\":\"1002561\",\"duration\":46,\"setup_duration\":120,\"timewindows\":[{\"start\":29700,\"end\":64800,\"day_index\":0},{\"start\":29700,\"end\":64800,\"day_index\":1},{\"start\":29700,\"end\":64800,\"day_index\":2},{\"start\":29700,\"end\":64800,\"day_index\":3},{\"start\":29700,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1145151_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.8},{\"unit_id\":\"l\",\"value\":36.936},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1145151\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1145151_SNC_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":7.8},{\"unit_id\":\"l\",\"value\":36.936},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":6,\"minimum_lapse\":48.0,\"activity\":{\"point_id\":\"1145151\",\"duration\":48,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144485_LPL_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.56},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":32.0}],\"visits_number\":3,\"minimum_lapse\":416.0,\"activity\":{\"point_id\":\"1144485\",\"duration\":416,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1116199_PCP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":18.46},{\"unit_id\":\"l\",\"value\":42.0},{\"unit_id\":\"qte\",\"value\":76.0}],\"visits_number\":3,\"minimum_lapse\":304.0,\"activity\":{\"point_id\":\"1116199\",\"duration\":304,\"setup_duration\":120,\"timewindows\":[{\"start\":33300,\"end\":61200,\"day_index\":0},{\"start\":33300,\"end\":61200,\"day_index\":1},{\"start\":33300,\"end\":61200,\"day_index\":2},{\"start\":33300,\"end\":61200,\"day_index\":3},{\"start\":33300,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1123435_SNC_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.4},{\"unit_id\":\"l\",\"value\":47.0},{\"unit_id\":\"qte\",\"value\":1.0}],\"visits_number\":3,\"minimum_lapse\":90.0,\"activity\":{\"point_id\":\"1123435\",\"duration\":90,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1124213_BOB_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":13.2},{\"unit_id\":\"l\",\"value\":37.2},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":78.0,\"activity\":{\"point_id\":\"1124213\",\"duration\":78,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1124213_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":13.2},{\"unit_id\":\"l\",\"value\":37.2},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":78.0,\"activity\":{\"point_id\":\"1124213\",\"duration\":78,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1124213_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":13.2},{\"unit_id\":\"l\",\"value\":37.2},{\"unit_id\":\"qte\",\"value\":6.0}],\"visits_number\":3,\"minimum_lapse\":78.0,\"activity\":{\"point_id\":\"1124213\",\"duration\":78,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144485_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.56},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":32.0}],\"visits_number\":3,\"minimum_lapse\":416.0,\"activity\":{\"point_id\":\"1144485\",\"duration\":416,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144485_CLI_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.56},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":32.0}],\"visits_number\":3,\"minimum_lapse\":416.0,\"activity\":{\"point_id\":\"1144485\",\"duration\":416,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1144485_PCP_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":2.56},{\"unit_id\":\"l\",\"value\":0.0},{\"unit_id\":\"qte\",\"value\":32.0}],\"visits_number\":3,\"minimum_lapse\":416.0,\"activity\":{\"point_id\":\"1144485\",\"duration\":416,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1143039_TAP_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":26.56},{\"unit_id\":\"l\",\"value\":105.6},{\"unit_id\":\"qte\",\"value\":8.0}],\"visits_number\":6,\"minimum_lapse\":800.0,\"activity\":{\"point_id\":\"1143039\",\"duration\":800,\"setup_duration\":120,\"timewindows\":[{\"start\":28800,\"end\":64800,\"day_index\":0},{\"start\":28800,\"end\":64800,\"day_index\":1},{\"start\":28800,\"end\":64800,\"day_index\":2},{\"start\":28800,\"end\":64800,\"day_index\":3},{\"start\":28800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005880_PH _ 42_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.5},{\"unit_id\":\"l\",\"value\":41.72},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":2,\"minimum_lapse\":61.0,\"activity\":{\"point_id\":\"1005880\",\"duration\":61,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1005880_SNC_ 42_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.5},{\"unit_id\":\"l\",\"value\":41.72},{\"unit_id\":\"qte\",\"value\":7.0}],\"visits_number\":2,\"minimum_lapse\":61.0,\"activity\":{\"point_id\":\"1005880\",\"duration\":61,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":61200,\"day_index\":0},{\"start\":32400,\"end\":61200,\"day_index\":1},{\"start\":32400,\"end\":61200,\"day_index\":2},{\"start\":32400,\"end\":61200,\"day_index\":3},{\"start\":32400,\"end\":61200,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1031918_BOB_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":22.0},{\"unit_id\":\"l\",\"value\":62.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":6,\"minimum_lapse\":277.0,\"activity\":{\"point_id\":\"1031918\",\"duration\":277,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1031918_PH _ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":22.0},{\"unit_id\":\"l\",\"value\":62.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":6,\"minimum_lapse\":277.0,\"activity\":{\"point_id\":\"1031918\",\"duration\":277,\"setup_duration\":120,\"timewindows\":[{\"start\":32400,\"end\":63000,\"day_index\":0},{\"start\":32400,\"end\":63000,\"day_index\":1},{\"start\":32400,\"end\":63000,\"day_index\":2},{\"start\":32400,\"end\":63000,\"day_index\":3},{\"start\":32400,\"end\":63000,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004647_SNC_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.5},{\"unit_id\":\"l\",\"value\":128.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":6,\"minimum_lapse\":80.0,\"activity\":{\"point_id\":\"1004647\",\"duration\":80,\"setup_duration\":120,\"timewindows\":[{\"start\":37800,\"end\":64800,\"day_index\":0},{\"start\":37800,\"end\":64800,\"day_index\":1},{\"start\":37800,\"end\":64800,\"day_index\":2},{\"start\":37800,\"end\":64800,\"day_index\":3},{\"start\":37800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004647_SAV_ 28_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.5},{\"unit_id\":\"l\",\"value\":128.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":6,\"minimum_lapse\":80.0,\"activity\":{\"point_id\":\"1004647\",\"duration\":80,\"setup_duration\":120,\"timewindows\":[{\"start\":37800,\"end\":64800,\"day_index\":0},{\"start\":37800,\"end\":64800,\"day_index\":1},{\"start\":37800,\"end\":64800,\"day_index\":2},{\"start\":37800,\"end\":64800,\"day_index\":3},{\"start\":37800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004647_PH _ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.5},{\"unit_id\":\"l\",\"value\":128.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":6,\"minimum_lapse\":80.0,\"activity\":{\"point_id\":\"1004647\",\"duration\":80,\"setup_duration\":120,\"timewindows\":[{\"start\":37800,\"end\":64800,\"day_index\":0},{\"start\":37800,\"end\":64800,\"day_index\":1},{\"start\":37800,\"end\":64800,\"day_index\":2},{\"start\":37800,\"end\":64800,\"day_index\":3},{\"start\":37800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"},{\"id\":\"1004647_PCP_ 14_3FF\",\"quantities\":[{\"unit_id\":\"kg\",\"value\":10.5},{\"unit_id\":\"l\",\"value\":128.0},{\"unit_id\":\"qte\",\"value\":10.0}],\"visits_number\":6,\"minimum_lapse\":80.0,\"activity\":{\"point_id\":\"1004647\",\"duration\":80,\"setup_duration\":120,\"timewindows\":[{\"start\":37800,\"end\":64800,\"day_index\":0},{\"start\":37800,\"end\":64800,\"day_index\":1},{\"start\":37800,\"end\":64800,\"day_index\":2},{\"start\":37800,\"end\":64800,\"day_index\":3},{\"start\":37800,\"end\":64800,\"day_index\":4}]},\"type\":\"service\"}],\"vehicles\":[{\"id\":\"vehicule1\",\"start_point_id\":\"startvehicule1\",\"end_point_id\":\"endvehicule1\",\"router_mode\":\"car\",\"speed_multiplier\":0.75,\"cost_time_multiplier\":1.0,\"router_dimension\":\"time\",\"skills\":[[\"vehicule1\"]],\"sequence_timewindows\":[{\"start\":21600,\"end\":45000,\"day_index\":0},{\"start\":21600,\"end\":45000,\"day_index\":1},{\"start\":21600,\"end\":45000,\"day_index\":2},{\"start\":21600,\"end\":45000,\"day_index\":3},{\"start\":21600,\"end\":45000,\"day_index\":4}],\"capacities\":[{\"unit_id\":\"kg\",\"limit\":850.0},{\"unit_id\":\"l\",\"limit\":7435.0},{\"unit_id\":\"qte\",\"limit\":9999.0}],\"unavailable_work_day_indices\":[5,6],\"traffic\":true,\"track\":true,\"motorway\":true,\"toll\":true,\"max_walk_distance\":750,\"approach\":\"unrestricted\"},{\"id\":\"vehicule2\",\"start_point_id\":\"startvehicule2\",\"end_point_id\":\"endvehicule2\",\"router_mode\":\"car\",\"speed_multiplier\":0.75,\"cost_time_multiplier\":1.0,\"router_dimension\":\"time\",\"skills\":\"vehicule1,vehicule2\",\"sequence_timewindows\":[{\"start\":21600,\"end\":45000,\"day_index\":0},{\"start\":21600,\"end\":45000,\"day_index\":1},{\"start\":21600,\"end\":45000,\"day_index\":2},{\"start\":21600,\"end\":45000,\"day_index\":3},{\"start\":21600,\"end\":45000,\"day_index\":4}],\"capacities\":[{\"unit_id\":\"kg\",\"limit\":1210.0},{\"unit_id\":\"l\",\"limit\":6254.0},{\"unit_id\":\"qte\",\"limit\":9999.0}],\"unavailable_work_day_indices\":[5,6],\"traffic\":true,\"track\":true,\"motorway\":true,\"toll\":true,\"max_walk_distance\":750,\"approach\":\"unrestricted\"}],\"configuration\":{\"preprocessing\":{\"use_periodic_heuristic\":true,\"prefer_short_segment\":true,\"partition_method\":\"balanced_kmeans\",\"partition_metric\":\"duration\"},\"resolution\":{\"same_point_day\":true,\"solver_parameter\":-1,\"duration\":225000,\"initial_time_out\":112500,\"time_out_multiplier\":2},\"schedule\":{\"range_indices\":{\"start\":0,\"end\":83}},\"restitution\":{\"csv\":true,\"intermediate_solutions\":false}}}}\n"
  },
  {
    "path": "benchmark/simple.rb",
    "content": "# frozen_string_literal: true\n\n$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))\nrequire 'grape'\nrequire 'benchmark/ips'\n\nclass API < Grape::API\n  prefix :api\n  version 'v1', using: :path\n  get '/' do\n    'hello'\n  end\nend\n\noptions = {\n  method: Rack::GET\n}\n\nenv = Rack::MockRequest.env_for('/api/v1', options)\n\n10.times do |i|\n  env[\"HTTP_HEADER#{i}\"] = '123'\nend\n\nBenchmark.ips do |ips|\n  ips.report('simple') do\n    API.call env\n  end\nend\n"
  },
  {
    "path": "docker/Dockerfile",
    "content": "ARG RUBY_VERSION=4\nFROM ruby:${RUBY_VERSION}-slim\n\nENV BUNDLE_PATH /usr/local/bundle/gems\nENV LIB_PATH /var/grape\n\nRUN apt-get update && \\\n    apt-get install -y --no-install-recommends build-essential curl git pkg-config libyaml-dev libjemalloc2 && \\\n    gem update --system && gem install bundler\n\nENV LD_PRELOAD libjemalloc.so.2\nENV MALLOC_CONF dirty_decay_ms:1000,narenas:2,background_thread:true\nENV RUBYOPT --enable-frozen-string-literal --yjit\n\nWORKDIR $LIB_PATH\n\nCOPY /docker/entrypoint.sh /usr/local/bin/docker-entrypoint.sh\nRUN chmod +x /usr/local/bin/docker-entrypoint.sh\n\nENTRYPOINT [\"docker-entrypoint.sh\"]\n"
  },
  {
    "path": "docker/entrypoint.sh",
    "content": "#!/bin/sh\n\nset -e\n\n# Useful information\necho -e \"$(ruby --version)\\nrubygems $(gem --version)\\n$(bundle version)\"\nif [ -z \"${GEMFILE}\" ]\nthen\n  echo \"Running default Gemfile\"\nelse\n  export BUNDLE_GEMFILE=\"./gemfiles/${GEMFILE}.gemfile\"\n  echo \"Running gemfile: ${GEMFILE}\"\nfi\n\n# Keep gems in the latest possible state\n(bundle check || bundle install) && bundle update && exec bundle exec ${@}\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "volumes:\n  gems:\n\nservices:\n  grape:\n    build:\n      context: .\n      dockerfile: docker/Dockerfile\n      args:\n        - RUBY_VERSION=${RUBY_VERSION:-4.0}\n    stdin_open: true\n    tty: true\n    volumes:\n      - .:/var/grape\n      - gems:/usr/local/bundle\n"
  },
  {
    "path": "gemfiles/dry_validation.gemfile",
    "content": "# frozen_string_literal: true\n\neval_gemfile '../Gemfile'\n\ngem 'dry-validation'\n"
  },
  {
    "path": "gemfiles/grape_entity.gemfile",
    "content": "# frozen_string_literal: true\n\neval_gemfile '../Gemfile'\n\ngem 'grape-entity'\n"
  },
  {
    "path": "gemfiles/hashie.gemfile",
    "content": "# frozen_string_literal: true\n\neval_gemfile '../Gemfile'\n\ngem 'hashie'\n"
  },
  {
    "path": "gemfiles/multi_json.gemfile",
    "content": "# frozen_string_literal: true\n\ngem 'multi_json'\n\neval_gemfile '../Gemfile'\n"
  },
  {
    "path": "gemfiles/multi_xml.gemfile",
    "content": "# frozen_string_literal: true\n\ngem 'multi_xml'\n\neval_gemfile '../Gemfile'\n"
  },
  {
    "path": "gemfiles/rack_2_2.gemfile",
    "content": "# frozen_string_literal: true\n\neval_gemfile '../Gemfile'\n\ngem 'rack', '~> 2.2.0'\n"
  },
  {
    "path": "gemfiles/rack_3_0.gemfile",
    "content": "# frozen_string_literal: true\n\neval_gemfile '../Gemfile'\n\ngem 'rack', '~> 3.0.0'\n"
  },
  {
    "path": "gemfiles/rack_3_1.gemfile",
    "content": "# frozen_string_literal: true\n\neval_gemfile '../Gemfile'\n\ngem 'rack', '~> 3.1.0'\n"
  },
  {
    "path": "gemfiles/rack_3_2.gemfile",
    "content": "# frozen_string_literal: true\n\neval_gemfile '../Gemfile'\n\ngem 'rack', '~> 3.2.0'\n"
  },
  {
    "path": "gemfiles/rack_edge.gemfile",
    "content": "# frozen_string_literal: true\n\neval_gemfile '../Gemfile'\n\ngem 'rack', github: 'rack/rack'\n"
  },
  {
    "path": "gemfiles/rails_7_2.gemfile",
    "content": "# frozen_string_literal: true\n\neval_gemfile '../Gemfile'\n\ngem 'rails', '~> 7.2.0'\ngem 'tzinfo-data', require: false\n"
  },
  {
    "path": "gemfiles/rails_8_0.gemfile",
    "content": "# frozen_string_literal: true\n\neval_gemfile '../Gemfile'\n\ngem 'rails', '~> 8.0.0'\ngem 'tzinfo-data', require: false\n"
  },
  {
    "path": "gemfiles/rails_8_1.gemfile",
    "content": "# frozen_string_literal: true\n\neval_gemfile '../Gemfile'\n\ngem 'rails', '~> 8.1.0'\ngem 'tzinfo-data', require: false\n"
  },
  {
    "path": "gemfiles/rails_edge.gemfile",
    "content": "# frozen_string_literal: true\n\neval_gemfile '../Gemfile'\n\ngem 'rails', github: 'rails/rails'\ngem 'tzinfo-data', require: false\n"
  },
  {
    "path": "grape.gemspec",
    "content": "# frozen_string_literal: true\n\nrequire_relative 'lib/grape/version'\n\nGem::Specification.new do |s|\n  s.name        = 'grape'\n  s.version     = Grape::VERSION\n  s.platform    = Gem::Platform::RUBY\n  s.authors     = ['Michael Bleigh']\n  s.email       = ['michael@intridea.com']\n  s.homepage    = 'https://github.com/ruby-grape/grape'\n  s.summary     = 'A simple Ruby framework for building REST-like APIs.'\n  s.description = 'A Ruby framework for rapid API development with great conventions.'\n  s.license     = 'MIT'\n  s.metadata    = {\n    'bug_tracker_uri' => 'https://github.com/ruby-grape/grape/issues',\n    'changelog_uri' => \"https://github.com/ruby-grape/grape/blob/v#{s.version}/CHANGELOG.md\",\n    'documentation_uri' => \"https://www.rubydoc.info/gems/grape/#{s.version}\",\n    'source_code_uri' => \"https://github.com/ruby-grape/grape/tree/v#{s.version}\",\n    'rubygems_mfa_required' => 'true'\n  }\n\n  s.add_dependency 'activesupport', '>= 7.2'\n  s.add_dependency 'dry-configurable'\n  s.add_dependency 'dry-types', '>= 1.1'\n  s.add_dependency 'mustermann-grape', '~> 1.1.0'\n  s.add_dependency 'rack', '>= 2'\n  s.add_dependency 'zeitwerk'\n\n  s.files = Dir['lib/**/*', 'CHANGELOG.md', 'CONTRIBUTING.md', 'README.md', 'grape.png', 'UPGRADING.md', 'LICENSE', 'grape.gemspec']\n  s.require_paths = ['lib']\n  s.required_ruby_version = '>= 3.2'\nend\n"
  },
  {
    "path": "lib/grape/api/instance.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  class API\n    # The API Instance class, is the engine behind Grape::API. Each class that inherits\n    # from this will represent a different API instance\n    class Instance\n      extend Grape::DSL::Settings\n      extend Grape::DSL::Desc\n      extend Grape::DSL::Validations\n      extend Grape::DSL::Callbacks\n      extend Grape::DSL::Logger\n      extend Grape::DSL::Middleware\n      extend Grape::DSL::RequestResponse\n      extend Grape::DSL::Routing\n      extend Grape::DSL::Helpers\n      extend Grape::Middleware::Auth::DSL\n\n      Boolean = Grape::API::Boolean\n\n      class << self\n        extend Forwardable\n\n        attr_accessor :configuration\n\n        def_delegators :@base, :to_s\n\n        def base=(grape_api)\n          @base = grape_api\n          grape_api.instances << self\n        end\n\n        def base_instance?\n          self == @base.base_instance\n        end\n\n        # A class-level lock to ensure the API is not compiled by multiple\n        # threads simultaneously within the same process.\n        LOCK = Mutex.new\n\n        # Clears all defined routes, endpoints, etc., on this API.\n        def reset!\n          reset_endpoints!\n          reset_routes!\n          reset_validations!\n        end\n\n        # This is the interface point between Rack and Grape; it accepts a request\n        # from Rack and ultimately returns an array of three values: the status,\n        # the headers, and the body. See [the rack specification]\n        # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more.\n        def call(env)\n          compile!\n          @instance.call(env)\n        end\n\n        def compile!\n          return if @instance\n\n          LOCK.synchronize { @instance ||= new }\n        end\n\n        # see Grape::Router#recognize_path\n        def recognize_path(path)\n          compile!\n          @instance.router.recognize_path(path)\n        end\n\n        # Wipe the compiled API so we can recompile after changes were made.\n        def change!\n          @instance = nil\n        end\n\n        protected\n\n        def inherit_settings(other_settings)\n          top_level_setting.inherit_from other_settings.point_in_time_copy\n\n          # Propagate any inherited params down to our endpoints, and reset any\n          # compiled routes.\n          endpoints.each do |e|\n            e.inherit_settings(top_level_setting.namespace_stackable)\n            e.reset_routes!\n          end\n\n          reset_routes!\n        end\n\n        private\n\n        def inherited(subclass)\n          super\n          subclass.reset!\n          subclass.logger logger.clone\n        end\n      end\n\n      attr_reader :router\n\n      # Builds the routes from the defined endpoints, effectively compiling\n      # this API into a usable form.\n      def initialize\n        @router = Router.new\n        add_head_not_allowed_methods_and_options_methods\n        self.class.endpoints.each do |endpoint|\n          endpoint.mount_in(@router)\n        end\n\n        @router.compile!\n        @router.freeze\n      end\n\n      # Handle a request. See Rack documentation for what `env` is.\n      def call(env)\n        status, headers, response = @router.call(env)\n        unless cascade?\n          headers = Grape::Util::Header.new.merge(headers)\n          headers.delete('X-Cascade')\n        end\n\n        [status, headers, response]\n      end\n\n      # Some requests may return a HTTP 404 error if grape cannot find a matching\n      # route. In this case, Grape::Router adds a X-Cascade header to the response\n      # and sets it to 'pass', indicating to grape's parents they should keep\n      # looking for a matching route on other resources.\n      #\n      # In some applications (e.g. mounting grape on rails), one might need to trap\n      # errors from reaching upstream. This is effectivelly done by unsetting\n      # X-Cascade. Default :cascade is true.\n      def cascade?\n        namespace_inheritable = self.class.inheritable_setting.namespace_inheritable\n        return namespace_inheritable[:cascade] if namespace_inheritable.key?(:cascade)\n        return namespace_inheritable[:version_options][:cascade] if namespace_inheritable[:version_options]&.key?(:cascade)\n\n        true\n      end\n\n      reset!\n\n      private\n\n      # For every resource add a 'OPTIONS' route that returns an HTTP 204 response\n      # with a list of HTTP methods that can be called. Also add a route that\n      # will return an HTTP 405 response for any HTTP method that the resource\n      # cannot handle.\n      def add_head_not_allowed_methods_and_options_methods\n        # The paths we collected are prepared (cf. Path#prepare), so they\n        # contain already versioning information when using path versioning.\n        all_routes = self.class.endpoints.flat_map(&:routes)\n\n        # Disable versioning so adding a route won't prepend versioning\n        # informations again.\n        without_root_prefix_and_versioning { collect_route_config_per_pattern(all_routes) }\n      end\n\n      def collect_route_config_per_pattern(all_routes)\n        routes_by_regexp = all_routes.group_by(&:pattern_regexp)\n        namespace_inheritable = self.class.inheritable_setting.namespace_inheritable\n\n        # Build the configuration based on the first endpoint and the collection of methods supported.\n        routes_by_regexp.each_value do |routes|\n          next if routes.any? { |route| route.request_method == '*' }\n\n          last_route = routes.last # Most of the configuration is taken from the last endpoint\n          allowed_methods = routes.map(&:request_method)\n          allowed_methods |= [Rack::HEAD] if !namespace_inheritable[:do_not_route_head] && allowed_methods.include?(Rack::GET)\n\n          allow_header = namespace_inheritable[:do_not_route_options] ? allowed_methods : [Rack::OPTIONS] | allowed_methods\n          last_route.app.options[:options_route_enabled] = true unless namespace_inheritable[:do_not_route_options] || allowed_methods.include?(Rack::OPTIONS)\n\n          greedy_route = Grape::Router::GreedyRoute.new(last_route.pattern, endpoint: last_route.app, allow_header: allow_header)\n          @router.associate_routes(greedy_route)\n        end\n      end\n\n      ROOT_PREFIX_VERSIONING_KEY = %i[version version_options root_prefix].freeze\n      private_constant :ROOT_PREFIX_VERSIONING_KEY\n\n      # Allows definition of endpoints that ignore the versioning configuration\n      # used by the rest of your API.\n      def without_root_prefix_and_versioning\n        inheritable_setting = self.class.inheritable_setting\n        deleted_values = inheritable_setting.namespace_inheritable.delete(*ROOT_PREFIX_VERSIONING_KEY)\n        yield\n      ensure\n        ROOT_PREFIX_VERSIONING_KEY.each_with_index do |key, index|\n          inheritable_setting.namespace_inheritable[key] = deleted_values[index]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/api.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  # The API class is the primary entry point for creating Grape APIs. Users\n  # should subclass this class in order to build an API.\n  class API\n    # Class methods that we want to call on the API rather than on the API object\n    NON_OVERRIDABLE = %i[base= base_instance? call change! configuration compile! inherit_settings recognize_path reset! routes top_level_setting= top_level_setting].freeze\n\n    Helpers = Grape::DSL::Helpers::BaseHelper\n\n    class Boolean\n      def self.build(val)\n        return nil if val != true && val != false\n\n        new\n      end\n    end\n\n    class << self\n      extend Forwardable\n\n      attr_accessor :base_instance, :instances\n\n      delegate_missing_to :base_instance\n\n      # This is the interface point between Rack and Grape; it accepts a request\n      # from Rack and ultimately returns an array of three values: the status,\n      # the headers, and the body. See [the rack specification]\n      # (https://github.com/rack/rack/blob/main/SPEC.rdoc) for more.\n      # NOTE: This will only be called on an API directly mounted on RACK\n      def_delegators :base_instance, :new, :configuration, :call, :change!, :compile!, :recognize_path, :routes\n\n      # Initialize the instance variables on the remountable class, and the base_instance\n      # an instance that will be used to create the set up but will not be mounted\n      def initial_setup(base_instance_parent)\n        @instances = []\n        @setup = []\n        @base_parent = base_instance_parent\n        @base_instance = mount_instance\n      end\n\n      # Redefines all methods so that are forwarded to add_setup and be recorded\n      def override_all_methods!\n        (base_instance.methods - Class.methods - NON_OVERRIDABLE).each do |method_override|\n          define_singleton_method(method_override) do |*args, **kwargs, &block|\n            add_setup(method: method_override, args: args, kwargs: kwargs, block: block)\n          end\n        end\n      end\n\n      # Configure an API from the outside. If a block is given, it'll pass a\n      # configuration hash to the block which you can use to configure your\n      # API. If no block is given, returns the configuration hash.\n      # The configuration set here is accessible from inside an API with\n      # `configuration` as normal.\n      def configure\n        config = @base_instance.configuration\n        if block_given?\n          yield config\n          self\n        else\n          config\n        end\n      end\n\n      # The remountable class can have a configuration hash to provide some dynamic class-level variables.\n      # For instance, a description could be done using: `desc configuration[:description]` if it may vary\n      # depending on where the endpoint is mounted. Use with care, if you find yourself using configuration\n      # too much, you may actually want to provide a new API rather than remount it.\n      def mount_instance(configuration: nil)\n        Class.new(@base_parent).tap do |instance|\n          instance.configuration = Grape::Util::EndpointConfiguration.new(configuration || {})\n          instance.base = self\n          replay_setup_on(instance)\n        end\n      end\n\n      private\n\n      # When inherited, will create a list of all instances (times the API was mounted)\n      # It will listen to the setup required to mount that endpoint, and replicate it on any new instance\n      def inherited(api)\n        super\n\n        api.initial_setup(self == Grape::API ? Grape::API::Instance : @base_instance)\n        api.override_all_methods!\n      end\n\n      # Replays the set up to produce an API as defined in this class, can be called\n      # on classes that inherit from Grape::API\n      def replay_setup_on(instance)\n        @setup.each do |setup_step|\n          replay_step_on(instance, **setup_step)\n        end\n      end\n\n      # Adds a new stage to the set up require to get a Grape::API up and running\n      def add_setup(**step)\n        @setup << step\n        last_response = nil\n        @instances.each do |instance|\n          last_response = replay_step_on(instance, **step)\n        end\n\n        refresh_mount_step if step[:method] != :mount\n        last_response\n      end\n\n      # Updating all previously mounted classes in the case that new methods have been executed.\n      def refresh_mount_step\n        @setup.each do |setup_step|\n          next if setup_step[:method] != :mount\n\n          refresh_mount_step = setup_step.merge(method: :refresh_mounted_api)\n          @setup << refresh_mount_step\n          @instances.each do |instance|\n            replay_step_on(instance, **refresh_mount_step)\n          end\n        end\n      end\n\n      def replay_step_on(instance, method:, args:, kwargs:, block:)\n        return if skip_immediate_run?(instance, args, kwargs)\n\n        eval_args = evaluate_arguments(instance.configuration, *args)\n        eval_kwargs = kwargs.deep_transform_values { |v| evaluate_arguments(instance.configuration, v).first }\n        response = instance.__send__(method, *eval_args, **eval_kwargs, &block)\n        if skip_immediate_run?(instance, [response], kwargs)\n          response\n        else\n          evaluate_arguments(instance.configuration, response).first\n        end\n      end\n\n      # Skips steps that contain arguments to be lazily executed (on re-mount time)\n      def skip_immediate_run?(instance, args, kwargs)\n        instance.base_instance? &&\n          (any_lazy?(args) || args.any? { |arg| arg.is_a?(Hash) && any_lazy?(arg.values) } || any_lazy?(kwargs.values))\n      end\n\n      def any_lazy?(args)\n        args.any? { |argument| argument_lazy?(argument) }\n      end\n\n      def evaluate_arguments(configuration, *args)\n        args.map do |argument|\n          if argument_lazy?(argument)\n            argument.evaluate_from(configuration)\n          elsif argument.is_a?(Hash)\n            argument.transform_values { |value| evaluate_arguments(configuration, value).first }\n          elsif argument.is_a?(Array)\n            evaluate_arguments(configuration, *argument)\n          else\n            argument\n          end\n        end\n      end\n\n      def argument_lazy?(argument)\n        argument.respond_to?(:lazy?) && argument.lazy?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/content_types.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module ContentTypes\n    module_function\n\n    # Content types are listed in order of preference.\n    DEFAULTS = {\n      xml: 'application/xml',\n      serializable_hash: 'application/json',\n      json: 'application/json',\n      binary: 'application/octet-stream',\n      txt: 'text/plain'\n    }.freeze\n\n    MIME_TYPES = Grape::ContentTypes::DEFAULTS.except(:serializable_hash).invert.freeze\n\n    def content_types_for(from_settings)\n      from_settings.presence || DEFAULTS\n    end\n\n    def mime_types_for(from_settings)\n      return MIME_TYPES if from_settings == Grape::ContentTypes::DEFAULTS\n\n      from_settings.invert.transform_keys! { |k| k.include?(';') ? k.split(';', 2).first : k }\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/cookies.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  class Cookies\n    extend Forwardable\n\n    DELETED_COOKIES_ATTRS = {\n      max_age: '0',\n      value: '',\n      expires: Time.at(0)\n    }.freeze\n\n    def_delegators :cookies, :[], :each\n\n    def initialize(rack_cookies)\n      @cookies = rack_cookies\n      @send_cookies = nil\n    end\n\n    def response_cookies\n      return unless @send_cookies\n\n      send_cookies.each do |name|\n        yield name, cookies[name]\n      end\n    end\n\n    def []=(name, value)\n      cookies[name] = value\n      send_cookies << name\n    end\n\n    # see https://github.com/rack/rack/blob/main/lib/rack/utils.rb#L338-L340\n    def delete(name, **opts)\n      self.[]=(name, opts.merge(DELETED_COOKIES_ATTRS))\n    end\n\n    private\n\n    def cookies\n      return @cookies unless @cookies.is_a?(Proc)\n\n      @cookies = @cookies.call.with_indifferent_access\n    end\n\n    def send_cookies\n      @send_cookies ||= Set.new\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/declared_params_handler.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  class DeclaredParamsHandler\n    def initialize(include_missing: true, evaluate_given: false, stringify: false, contract_key_map: nil)\n      @include_missing = include_missing\n      @evaluate_given = evaluate_given\n      @stringify = stringify\n      @contract_key_map = contract_key_map\n    end\n\n    def call(passed_params, declared_params, route_params, renamed_params)\n      recursive_declared(\n        passed_params,\n        declared_params: declared_params,\n        route_params: route_params,\n        renamed_params: renamed_params\n      )\n    end\n\n    private\n\n    def recursive_declared(passed_params, declared_params:, route_params:, renamed_params:, params_nested_path: [])\n      res = if passed_params.is_a?(Array)\n              passed_params.map do |passed_param|\n                recursive_declared(passed_param, declared_params:, params_nested_path:, renamed_params:, route_params:)\n              end\n            else\n              declared_hash(passed_params, declared_params:, params_nested_path:, renamed_params:, route_params:)\n            end\n\n      @contract_key_map&.each { |key_map| key_map.write(passed_params, res) }\n\n      res\n    end\n\n    def declared_hash(passed_params, declared_params:, params_nested_path:, renamed_params:, route_params:)\n      declared_params.each_with_object(passed_params.class.new) do |declared_param_attr, memo|\n        next if @evaluate_given && !declared_param_attr.scope.attr_meets_dependency?(passed_params)\n\n        declared_hash_attr(\n          passed_params,\n          declared_param: declared_param_attr.key,\n          params_nested_path:,\n          memo:,\n          renamed_params:,\n          route_params:\n        )\n      end\n    end\n\n    def declared_hash_attr(passed_params, declared_param:, params_nested_path:, memo:, renamed_params:, route_params:)\n      if declared_param.is_a?(Hash)\n        declared_param.each_pair do |declared_parent_param, declared_children_params|\n          next unless @include_missing || passed_params.key?(declared_parent_param)\n\n          memo_key = build_memo_key(params_nested_path, declared_parent_param, renamed_params)\n          passed_children_params = passed_params[declared_parent_param] || passed_params.class.new\n\n          params_nested_path_dup = params_nested_path.dup\n          params_nested_path_dup << declared_parent_param.to_s\n\n          memo[memo_key] = handle_passed_param(params_nested_path_dup, route_params:, has_passed_children: passed_children_params.any?) do\n            recursive_declared(\n              passed_children_params,\n              declared_params: declared_children_params,\n              params_nested_path: params_nested_path_dup,\n              renamed_params:,\n              route_params:\n            )\n          end\n        end\n      else\n        # If it is not a Hash then it does not have children.\n        # Find its value or set it to nil.\n        return unless @include_missing || (passed_params.respond_to?(:key?) && passed_params.key?(declared_param))\n\n        memo_key = build_memo_key(params_nested_path, declared_param, renamed_params)\n        passed_param = passed_params[declared_param]\n\n        params_nested_path_dup = params_nested_path.dup\n        params_nested_path_dup << declared_param.to_s\n\n        memo[memo_key] = passed_param || handle_passed_param(params_nested_path_dup, route_params:) do\n          passed_param\n        end\n      end\n    end\n\n    def build_memo_key(params_nested_path, declared_param, renamed_params)\n      rename_path = params_nested_path + [declared_param.to_s]\n      renamed_param_name = renamed_params[rename_path]\n\n      param = renamed_param_name || declared_param\n      @stringify ? param.to_s : param.to_sym\n    end\n\n    def handle_passed_param(params_nested_path, route_params:, has_passed_children: false, &_block)\n      return yield if has_passed_children\n\n      key = params_nested_path[0]\n      key += \"[#{params_nested_path[1..].join('][')}]\" if params_nested_path.size > 1\n\n      type = route_params.dig(key, :type)\n      has_children = route_params.keys.any? { |k| k != key && k.start_with?(\"#{key}[\") }\n\n      if type == 'Hash' && !has_children\n        {}\n      elsif type == 'Array' || (type&.start_with?('[') && !type.include?(','))\n        []\n      elsif type == 'Set' || type&.start_with?('#<Set', 'Set')\n        Set.new\n      else\n        yield\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/dry_types.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module DryTypes\n    # https://dry-rb.org/gems/dry-types/main/getting-started/\n    # limit to what Grape is using\n    include Dry.Types(:params, :coercible, :strict)\n\n    class StrictCache < Grape::Util::Cache\n      MAPPING = {\n        Grape::API::Boolean => DryTypes::Strict::Bool,\n        BigDecimal => DryTypes::Strict::Decimal,\n        Numeric => DryTypes::Strict::Integer | DryTypes::Strict::Float | DryTypes::Strict::Decimal,\n        TrueClass => DryTypes::Strict::Bool.constrained(eql: true),\n        FalseClass => DryTypes::Strict::Bool.constrained(eql: false)\n      }.freeze\n\n      def initialize\n        super\n        @cache = Hash.new do |h, strict_type|\n          h[strict_type] = MAPPING.fetch(strict_type) do\n            DryTypes.wrapped_dry_types_const_get(DryTypes::Strict, strict_type)\n          end\n        end\n      end\n    end\n\n    class ParamsCache < Grape::Util::Cache\n      MAPPING = {\n        Grape::API::Boolean => DryTypes::Params::Bool,\n        BigDecimal => DryTypes::Params::Decimal,\n        Numeric => DryTypes::Params::Integer | DryTypes::Params::Float | DryTypes::Params::Decimal,\n        TrueClass => DryTypes::Params::Bool.constrained(eql: true),\n        FalseClass => DryTypes::Params::Bool.constrained(eql: false),\n        String => DryTypes::Coercible::String\n      }.freeze\n\n      def initialize\n        super\n        @cache = Hash.new do |h, params_type|\n          h[params_type] = MAPPING.fetch(params_type) do\n            DryTypes.wrapped_dry_types_const_get(DryTypes::Params, params_type)\n          end\n        end\n      end\n    end\n\n    def self.wrapped_dry_types_const_get(dry_type, type)\n      dry_type.const_get(type.name, false)\n    rescue NameError\n      raise ArgumentError, \"type #{type} should support coercion via `[]`\" unless type.respond_to?(:[])\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/dsl/callbacks.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module DSL\n    module Callbacks\n      # before: execute the given block before validation, coercion, or any endpoint\n      # before_validation: execute the given block after `before`, but prior to validation or coercion\n      # after_validation: execute the given block after validations and coercions, but before any endpoint code\n      # after: execute the given block after the endpoint code has run except in unsuccessful\n      # finally: execute the given block after the endpoint code even if unsuccessful\n\n      %w[before before_validation after_validation after finally].each do |callback_method|\n        define_method callback_method.to_sym do |&block|\n          inheritable_setting.namespace_stackable[callback_method.pluralize.to_sym] = block\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/dsl/declared.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module DSL\n    module Declared\n      # Denotes a situation where a DSL method has been invoked in a\n      # filter which it should not yet be available in\n      class MethodNotYetAvailable < StandardError\n        def initialize(msg = '#declared is not available prior to parameter validation')\n          super\n        end\n      end\n\n      # A filtering method that will return a hash\n      # consisting only of keys that have been declared by a\n      # `params` statement against the current/target endpoint or parent\n      # namespaces.\n      # @param params [Hash] The initial hash to filter. Usually this will just be `params`\n      # @param options [Hash] Can pass `:include_missing`, `:stringify` and `:include_parent_namespaces`\n      # options. `:include_parent_namespaces` defaults to true, hence must be set to false if\n      # you want only to return params declared against the current/target endpoint.\n      def declared(passed_params, include_parent_namespaces: true, include_missing: true, evaluate_given: false, stringify: false)\n        raise MethodNotYetAvailable unless before_filter_passed\n\n        contract_key_map = inheritable_setting.namespace_stackable[:contract_key_map]\n        handler = DeclaredParamsHandler.new(include_missing:, evaluate_given:, stringify:, contract_key_map: contract_key_map)\n        declared_params = include_parent_namespaces ? inheritable_setting.route[:declared_params] : (inheritable_setting.namespace_stackable[:declared_params].last || [])\n        renamed_params = inheritable_setting.route[:renamed_params] || {}\n        route_params = options.dig(:route_options, :params) || {} # options = endpoint's option\n\n        handler.call(passed_params, declared_params, route_params, renamed_params)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/dsl/desc.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module DSL\n    module Desc\n      extend Grape::DSL::Settings\n\n      # Add a description to the next namespace or function.\n      # @param description [String] descriptive string for this endpoint\n      #   or namespace\n      # @param options [Hash] other properties you can set to describe the\n      #   endpoint or namespace. Optional.\n      # @option options :detail [String] additional detail about this endpoint\n      # @option options :summary [String] summary for this endpoint\n      # @option options :params [Hash] param types and info. normally, you set\n      #   these via the `params` dsl method.\n      # @option options :entity [Grape::Entity] the entity returned upon a\n      #   successful call to this action\n      # @option options :http_codes [Array[Array]] possible HTTP codes this\n      #   endpoint may return, with their meanings, in a 2d array\n      # @option options :named [String] a specific name to help find this route\n      # @option options :body_name [String] override the autogenerated body name param\n      # @option options :headers [Hash] HTTP headers this method can accept\n      # @option options :hidden [Boolean] hide the endpoint or not\n      # @option options :deprecated [Boolean] deprecate the endpoint or not\n      # @option options :is_array [Boolean] response entity is array or not\n      # @option options :nickname [String] nickname of the endpoint\n      # @option options :produces [Array[String]] a list of MIME types the endpoint produce\n      # @option options :consumes [Array[String]] a list of MIME types the endpoint consume\n      # @option options :security [Array[Hash]] a list of security schemes\n      # @option options :tags [Array[String]] a list of tags\n      # @yield a block yielding an instance context with methods mapping to\n      #   each of the above, except that :entity is also aliased as #success\n      #   and :http_codes is aliased as #failure.\n      #\n      # @example\n      #\n      #     desc 'create a user'\n      #     post '/users' do\n      #       # ...\n      #     end\n      #\n      #     desc 'find a user' do\n      #       detail 'locates the user from the given user ID'\n      #       failure [ [404, 'Couldn\\'t find the given user' ] ]\n      #       success User::Entity\n      #     end\n      #     get '/user/:id' do\n      #       # ...\n      #     end\n      #\n      def desc(description, options = {}, &config_block)\n        settings =\n          if config_block\n            endpoint_config = defined?(configuration) ? configuration : nil\n            Grape::Util::ApiDescription.new(description, endpoint_config, &config_block).settings\n          else\n            options.merge(description: description)\n          end\n        inheritable_setting.namespace[:description] = settings\n        inheritable_setting.route[:description] = settings\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/dsl/headers.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module DSL\n    module Headers\n      # This method has four responsibilities:\n      # 1. Set a specifc header value by key\n      # 2. Retrieve a specifc header value by key\n      # 3. Retrieve all headers that have been set\n      # 4. Delete a specifc header key-value pair\n      def header(key = nil, val = nil)\n        if key\n          val ? header[key] = val : header.delete(key)\n        else\n          @header ||= Grape::Util::Header.new\n        end\n      end\n      alias headers header\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/dsl/helpers.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module DSL\n    module Helpers\n      # Add helper methods that will be accessible from any\n      # endpoint within this namespace (and child namespaces).\n      #\n      # When called without a block, all known helpers within this scope\n      # are included.\n      #\n      # @param [Array] new_modules optional array of modules to include\n      # @param [Block] block optional block of methods to include\n      #\n      # @example Define some helpers.\n      #\n      #     class ExampleAPI < Grape::API\n      #       helpers do\n      #         def current_user\n      #           User.find_by_id(params[:token])\n      #         end\n      #       end\n      #     end\n      #\n      # @example Include many modules\n      #\n      #     class ExampleAPI < Grape::API\n      #       helpers Authentication, Mailer, OtherModule\n      #     end\n      #\n      def helpers(*new_modules, &block)\n        include_new_modules(new_modules)\n        include_block(block)\n        include_all_in_scope if !block && new_modules.empty?\n      end\n\n      private\n\n      def include_new_modules(modules)\n        return if modules.empty?\n\n        modules.each { |mod| make_inclusion(mod) }\n      end\n\n      def include_block(block)\n        return unless block\n\n        Module.new.tap do |mod|\n          make_inclusion(mod) { mod.class_eval(&block) }\n        end\n      end\n\n      def make_inclusion(mod, &)\n        define_boolean_in_mod(mod)\n        inject_api_helpers_to_mod(mod, &)\n        inheritable_setting.namespace_stackable[:helpers] = mod\n      end\n\n      def include_all_in_scope\n        Module.new.tap do |mod|\n          namespace_stackable(:helpers).each { |mod_to_include| mod.include mod_to_include }\n          change!\n        end\n      end\n\n      def define_boolean_in_mod(mod)\n        return if defined? mod::Boolean\n\n        mod.const_set(:Boolean, Grape::API::Boolean)\n      end\n\n      def inject_api_helpers_to_mod(mod, &block)\n        mod.extend(BaseHelper) unless mod.is_a?(BaseHelper)\n        yield if block\n        mod.api_changed(self)\n      end\n\n      # This module extends user defined helpers\n      # to provide some API-specific functionality.\n      module BaseHelper\n        attr_accessor :api\n\n        def params(name, &block)\n          @named_params ||= {}\n          @named_params[name] = block\n        end\n\n        def api_changed(new_api)\n          @api = new_api\n          process_named_params\n        end\n\n        protected\n\n        def process_named_params\n          return if @named_params.blank?\n\n          api.inheritable_setting.namespace_stackable[:named_params] = @named_params\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/dsl/inside_route.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module DSL\n    module InsideRoute\n      include Declared\n\n      # Backward compatibility: alias exception class to previous location\n      MethodNotYetAvailable = Declared::MethodNotYetAvailable\n\n      # The API version as specified in the URL.\n      def version\n        env[Grape::Env::API_VERSION]\n      end\n\n      def configuration\n        options[:for].configuration.evaluate\n      end\n\n      # End the request and display an error to the\n      # end user with the specified message.\n      #\n      # @param message [String] The message to display.\n      # @param status [Integer] The HTTP Status Code. Defaults to default_error_status, 500 if not set.\n      # @param additional_headers [Hash] Addtional headers for the response.\n      # @param backtrace [Array<String>] The backtrace of the exception that caused the error.\n      # @param original_exception [Exception] The original exception that caused the error.\n      def error!(message, status = nil, additional_headers = nil, backtrace = nil, original_exception = nil)\n        status = self.status(status || inheritable_setting.namespace_inheritable[:default_error_status])\n        headers = additional_headers.present? ? header.merge(additional_headers) : header\n        throw :error,\n              message: message,\n              status: status,\n              headers: headers,\n              backtrace: backtrace,\n              original_exception: original_exception\n      end\n\n      # Redirect to a new url.\n      #\n      # @param url [String] The url to be redirect.\n      # @param permanent [Boolean] default false.\n      # @param body default a short message including the URL.\n      def redirect(url, permanent: false, body: nil)\n        body_message = body\n        if permanent\n          status 301\n          body_message ||= \"This resource has been moved permanently to #{url}.\"\n        elsif http_version == 'HTTP/1.1' && !request.get?\n          status 303\n          body_message ||= \"An alternate resource is located at #{url}.\"\n        else\n          status 302\n          body_message ||= \"This resource has been moved temporarily to #{url}.\"\n        end\n        header 'Location', url\n        content_type 'text/plain'\n        body body_message\n      end\n\n      # Set or retrieve the HTTP status code.\n      #\n      # @param status [Integer] The HTTP Status Code to return for this request.\n      def status(status = nil)\n        case status\n        when Symbol\n          raise ArgumentError, \"Status code :#{status} is invalid.\" unless Rack::Utils::SYMBOL_TO_STATUS_CODE.key?(status)\n\n          @status = Rack::Utils.status_code(status)\n        when Integer\n          @status = status\n        when nil\n          return @status if @status\n\n          if request.post?\n            201\n          elsif request.delete?\n            if @body.present?\n              200\n            else\n              204\n            end\n          else\n            200\n          end\n        else\n          raise ArgumentError, 'Status code must be Integer or Symbol.'\n        end\n      end\n\n      # Set response content-type\n      def content_type(val = nil)\n        if val\n          header(Rack::CONTENT_TYPE, val)\n        else\n          header[Rack::CONTENT_TYPE]\n        end\n      end\n\n      # Allows you to define the response body as something other than the\n      # return value.\n      #\n      # @example\n      #   get '/body' do\n      #     body \"Body\"\n      #     \"Not the Body\"\n      #   end\n      #\n      #   GET /body # => \"Body\"\n      def body(value = nil)\n        if value\n          @body = value\n        elsif value == false\n          @body = ''\n          status 204\n        else\n          @body\n        end\n      end\n\n      # Allows you to explicitly return no content.\n      #\n      # @example\n      #   delete :id do\n      #     return_no_content\n      #     \"not returned\"\n      #   end\n      #\n      #   DELETE /12 # => 204 No Content, \"\"\n      def return_no_content\n        status 204\n        body false\n      end\n\n      # Allows you to send a file to the client via sendfile.\n      #\n      # @example\n      #   get '/file' do\n      #     sendfile FileStreamer.new(...)\n      #   end\n      #\n      #   GET /file # => \"contents of file\"\n      def sendfile(value = nil)\n        if value.is_a?(String)\n          file_body = Grape::ServeStream::FileBody.new(value)\n          @stream = Grape::ServeStream::StreamResponse.new(file_body)\n        elsif !value.is_a?(NilClass)\n          raise ArgumentError, 'Argument must be a file path'\n        else\n          stream\n        end\n      end\n\n      # Allows you to define the response as a streamable object.\n      #\n      # If Content-Length and Transfer-Encoding are blank (among other conditions),\n      # Rack assumes this response can be streamed in chunks.\n      #\n      # @example\n      #   get '/stream' do\n      #     stream FileStreamer.new(...)\n      #   end\n      #\n      #   GET /stream # => \"chunked contents of file\"\n      #\n      # See:\n      # * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/chunked.rb\n      # * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/etag.rb\n      def stream(value = nil)\n        return if value.nil? && @stream.nil?\n\n        header Rack::CONTENT_LENGTH, nil\n        header 'Transfer-Encoding', nil\n        header Rack::CACHE_CONTROL, 'no-cache' # Skips ETag generation (reading the response up front)\n        if value.is_a?(String)\n          file_body = Grape::ServeStream::FileBody.new(value)\n          @stream = Grape::ServeStream::StreamResponse.new(file_body)\n        elsif value.respond_to?(:each)\n          @stream = Grape::ServeStream::StreamResponse.new(value)\n        elsif !value.is_a?(NilClass)\n          raise ArgumentError, 'Stream object must respond to :each.'\n        else\n          @stream\n        end\n      end\n\n      # Allows you to make use of Grape Entities by setting\n      # the response body to the serializable hash of the\n      # entity provided in the `:with` option. This has the\n      # added benefit of automatically passing along environment\n      # and version information to the serialization, making it\n      # very easy to do conditional exposures. See Entity docs\n      # for more info.\n      #\n      # @example\n      #\n      #   get '/users/:id' do\n      #     present User.find(params[:id]),\n      #       with: API::Entities::User,\n      #       admin: current_user.admin?\n      #   end\n      def present(*args, **options)\n        key, object = if args.count == 2 && args.first.is_a?(Symbol)\n                        args\n                      else\n                        [nil, args.first]\n                      end\n        entity_class = entity_class_for_obj(object, options)\n\n        root = options.delete(:root)\n\n        representation = if entity_class\n                           entity_representation_for(entity_class, object, options)\n                         else\n                           object\n                         end\n\n        representation = { root => representation } if root\n\n        if key\n          representation = (body || {}).merge(key => representation)\n        elsif entity_class.present? && body\n          raise ArgumentError, \"Representation of type #{representation.class} cannot be merged.\" unless representation.respond_to?(:merge)\n\n          representation = body.merge(representation)\n        end\n\n        body representation\n      end\n\n      # Returns route information for the current request.\n      #\n      # @example\n      #\n      #   desc \"Returns the route description.\"\n      #   get '/' do\n      #     route.description\n      #   end\n      def route\n        env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info]\n      end\n\n      # Attempt to locate the Entity class for a given object, if not given\n      # explicitly. This is done by looking for the presence of Klass::Entity,\n      # where Klass is the class of the `object` parameter, or one of its\n      # ancestors.\n      # @param object [Object] the object to locate the Entity class for\n      # @param options [Hash]\n      # @option options :with [Class] the explicit entity class to use\n      # @return [Class] the located Entity class, or nil if none is found\n      def entity_class_for_obj(object, options)\n        entity_class = options.delete(:with)\n        return entity_class if entity_class\n\n        # entity class not explicitly defined, auto-detect from relation#klass or first object in the collection\n        object_class = if object.respond_to?(:klass)\n                         object.klass\n                       else\n                         object.respond_to?(:first) ? object.first.class : object.class\n                       end\n\n        representations = inheritable_setting.namespace_stackable_with_hash(:representations)\n        if representations\n          potential = object_class.ancestors.detect { |potential| representations.key?(potential) }\n          entity_class = representations[potential] if potential\n        end\n\n        entity_class = object_class.const_get(:Entity) if !entity_class && object_class.const_defined?(:Entity) && object_class.const_get(:Entity).respond_to?(:represent)\n        entity_class\n      end\n\n      # @return the representation of the given object as done through\n      #   the given entity_class.\n      def entity_representation_for(entity_class, object, options)\n        embeds = { env: env }\n        embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)\n        entity_class.represent(object, **embeds, **options)\n      end\n\n      def http_version\n        env.fetch('HTTP_VERSION') { env[Rack::SERVER_PROTOCOL] }\n      end\n\n      def api_format(format)\n        env[Grape::Env::API_FORMAT] = format\n      end\n\n      def context\n        self\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/dsl/logger.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module DSL\n    module Logger\n      # Set or retrive the configured logger. If none was configured, this\n      # method will create a new one, logging to stdout.\n      # @param logger [Object] the new logger to use\n      def logger(logger = nil)\n        global_settings = inheritable_setting.global\n        if logger\n          global_settings[:logger] = logger\n        else\n          global_settings[:logger] || global_settings[:logger] = ::Logger.new($stdout)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/dsl/middleware.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module DSL\n    module Middleware\n      # Apply a custom middleware to the API. Applies\n      # to the current namespace and any children, but\n      # not parents.\n      #\n      # @param middleware_class [Class] The class of the middleware you'd like\n      #   to inject.\n      def use(middleware_class, *args, &block)\n        arr = [:use, middleware_class, *args]\n        arr << block if block\n\n        inheritable_setting.namespace_stackable[:middleware] = arr\n      end\n\n      %i[insert insert_before insert_after].each do |method_name|\n        define_method method_name do |*args, &block|\n          arr = [method_name, *args]\n          arr << block if block\n\n          inheritable_setting.namespace_stackable[:middleware] = arr\n        end\n      end\n\n      # Retrieve an array of the middleware classes\n      # and arguments that are currently applied to the\n      # application.\n      def middleware\n        inheritable_setting.namespace_stackable[:middleware] || []\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/dsl/parameters.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module DSL\n    # Defines DSL methods, meant to be applied to a ParamsScope, which define\n    # and describe the parameters accepted by an endpoint, or all endpoints\n    # within a namespace.\n    module Parameters\n      # Set the module used to build the request.params.\n      #\n      # @param build_with the ParamBuilder module to use when building request.params\n      #   Available builders are:\n      #\n      #     * Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder (default)\n      #     * Grape::Extensions::Hash::ParamBuilder\n      #     * Grape::Extensions::Hashie::Mash::ParamBuilder\n      #\n      # @example\n      #\n      #     require 'grape/extenstions/hashie_mash'\n      #     class API < Grape::API\n      #       desc \"Get collection\"\n      #       params do\n      #         build_with :hashie_mash\n      #         requires :user_id, type: Integer\n      #       end\n      #       get do\n      #         params['user_id']\n      #       end\n      #     end\n      def build_with(build_with)\n        @api.inheritable_setting.namespace_inheritable[:build_params_with] = build_with\n      end\n\n      # Include reusable params rules among current.\n      # You can define reusable params with helpers method.\n      #\n      # @example\n      #\n      #     class API < Grape::API\n      #       helpers do\n      #         params :pagination do\n      #           optional :page, type: Integer\n      #           optional :per_page, type: Integer\n      #         end\n      #       end\n      #\n      #       desc \"Get collection\"\n      #       params do\n      #         use :pagination\n      #       end\n      #       get do\n      #         Collection.page(params[:page]).per(params[:per_page])\n      #       end\n      #     end\n      def use(*names, **options)\n        named_params = @api.inheritable_setting.namespace_stackable_with_hash(:named_params) || {}\n        names.each do |name|\n          params_block = named_params.fetch(name) do\n            raise \"Params :#{name} not found!\"\n          end\n\n          if options.empty?\n            instance_exec(options, &params_block)\n          else\n            instance_exec(**options, &params_block)\n          end\n        end\n      end\n      alias use_scope use\n      alias includes use\n\n      # Require one or more parameters for the current endpoint.\n      #\n      # @param attrs list of parameters names, or, if :using is\n      #   passed as an option, which keys to include (:all or :none) from\n      #   the :using hash. The last key can be a hash, which specifies\n      #   options for the parameters\n      # @option attrs :type [Class] the type to coerce this parameter to before\n      #   passing it to the endpoint. See {Grape::Validations::Types} for a list of\n      #   types that are supported automatically. Custom classes may be used\n      #   where they define a class-level `::parse` method, or in conjunction\n      #   with the `:coerce_with` parameter. `JSON` may be supplied to denote\n      #   `JSON`-formatted objects or arrays of objects. `Array[JSON]` accepts\n      #   the same values as `JSON` but will wrap single objects in an `Array`.\n      # @option attrs :types [Array<Class>] may be supplied in place of +:type+\n      #   to declare an attribute that has multiple allowed types. See\n      #   {Validations::Types::MultipleTypeCoercer} for more details on coercion\n      #   and validation rules for variant-type parameters.\n      # @option attrs :desc [String] description to document this parameter\n      # @option attrs :default [Object] default value, if parameter is optional\n      # @option attrs :values [Array] permissable values for this field. If any\n      #   other value is given, it will be handled as a validation error\n      # @option attrs :using [Hash[Symbol => Hash]] a hash defining keys and\n      #   options, like that returned by {Grape::Entity#documentation}. The value\n      #   of each key is an options hash accepting the same parameters\n      # @option attrs :except [Array[Symbol]] a list of keys to exclude from\n      #   the :using Hash. The meaning of this depends on if :all or :none was\n      #   passed; :all + :except will make the :except fields optional, whereas\n      #   :none + :except will make the :except fields required\n      # @option attrs :coerce_with [#parse, #call] method to be used when coercing\n      #   the parameter to the type named by `attrs[:type]`. Any class or object\n      #   that defines `::parse` or `::call` may be used.\n      #\n      # @example\n      #\n      #     params do\n      #       # Basic usage: require a parameter of a certain type\n      #       requires :user_id, type: Integer\n      #\n      #       # You don't need to specify type; String is default\n      #       requires :foo\n      #\n      #       # Multiple params can be specified at once if they share\n      #       # the same options.\n      #       requires :x, :y, :z, type: Date\n      #\n      #       # Nested parameters can be handled as hashes. You must\n      #       # pass in a block, within which you can use any of the\n      #       # parameters DSL methods.\n      #       requires :user, type: Hash do\n      #         requires :name, type: String\n      #       end\n      #     end\n      def requires(*attrs, **opts, &block)\n        opts[:presence] = { value: true, message: opts[:message] }\n        opts = @group.deep_merge(opts) if @group\n\n        if opts[:using]\n          require_required_and_optional_fields(attrs.first, using: opts[:using], except: opts[:except])\n        else\n          validate_attributes(attrs, **opts, &block)\n          block ? new_scope(attrs.first, type: opts[:type], as: opts[:as], &block) : push_declared_params(attrs, as: opts[:as])\n        end\n      end\n\n      # Allow, but don't require, one or more parameters for the current\n      #   endpoint.\n      # @param (see #requires)\n      # @option (see #requires)\n      def optional(*attrs, **opts, &block)\n        type = opts[:type]\n        opts = @group.deep_merge(opts) if @group\n\n        # check type for optional parameter group\n        if attrs && block\n          raise Grape::Exceptions::MissingGroupType if type.nil?\n          raise Grape::Exceptions::UnsupportedGroupType unless Grape::Validations::Types.group?(type)\n        end\n\n        if opts[:using]\n          require_optional_fields(attrs.first, using: opts[:using], except: opts[:except])\n        else\n          validate_attributes(attrs, **opts, &block)\n\n          block ? new_scope(attrs.first, type: opts[:type], as: opts[:as], optional: true, &block) : push_declared_params(attrs, as: opts[:as])\n        end\n      end\n\n      # Define common settings for one or more parameters\n      # @param (see #requires)\n      # @option (see #requires)\n      def with(**opts, &)\n        new_group_attrs = [@group, opts].compact.reduce(&:deep_merge)\n        new_group_scope(new_group_attrs, &)\n      end\n\n      %i[mutually_exclusive exactly_one_of at_least_one_of all_or_none_of].each do |validator|\n        define_method validator do |*attrs, message: nil|\n          validates(attrs, validator => { value: true, message: message })\n        end\n      end\n\n      # Define a block of validations which should be applied if and only if\n      # the given parameter is present. The parameters are not nested.\n      # @param attr [Symbol] the parameter which, if present, triggers the\n      #   validations\n      # @raise Grape::Exceptions::UnknownParameter if `attr` has not been\n      #   defined in this scope yet\n      # @yield a parameter definition DSL\n      def given(*attrs, &)\n        attrs.each do |attr|\n          proxy_attr = first_hash_key_or_param(attr)\n          raise Grape::Exceptions::UnknownParameter.new(proxy_attr) unless declared_param?(proxy_attr)\n        end\n        new_lateral_scope(dependent_on: attrs, &)\n      end\n\n      # Test for whether a certain parameter has been defined in this params\n      # block yet.\n      # @return [Boolean] whether the parameter has been defined\n      def declared_param?(param)\n        if lateral?\n          # Elements of @declared_params of lateral scope are pushed in @parent. So check them in @parent.\n          @parent.declared_param?(param)\n        else\n          # @declared_params also includes hashes of options and such, but those\n          # won't be flattened out.\n          @declared_params.flatten.any? do |declared_param_attr|\n            first_hash_key_or_param(declared_param_attr.key) == param\n          end\n        end\n      end\n\n      alias group requires\n\n      class EmptyOptionalValue; end # rubocop:disable Lint/EmptyClass\n\n      def map_params(params, element, is_array = false)\n        if params.is_a?(Array)\n          params.map do |el|\n            map_params(el, element, true)\n          end\n        elsif params.is_a?(Hash)\n          params[element] || (@optional && is_array ? EmptyOptionalValue : {})\n        elsif params == EmptyOptionalValue\n          EmptyOptionalValue\n        else\n          {}\n        end\n      end\n\n      # @param params [Hash] initial hash of parameters\n      # @return hash of parameters relevant for the current scope\n      # @api private\n      def params(params)\n        params = @parent.qualifying_params.presence || @parent.params(params) if @parent\n        params = map_params(params, @element) if @element\n        params\n      end\n\n      private\n\n      def first_hash_key_or_param(parameter)\n        parameter.is_a?(Hash) ? parameter.keys.first : parameter\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/dsl/request_response.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module DSL\n    module RequestResponse\n      # Specify the default format for the API's serializers.\n      # May be `:json` or `:txt` (default).\n      def default_format(new_format = nil)\n        return inheritable_setting.namespace_inheritable[:default_format] if new_format.nil?\n\n        inheritable_setting.namespace_inheritable[:default_format] = new_format.to_sym\n      end\n\n      # Specify the format for the API's serializers.\n      # May be `:json`, `:xml`, `:txt`, etc.\n      def format(new_format = nil)\n        return inheritable_setting.namespace_inheritable[:format] if new_format.nil?\n\n        symbolic_new_format = new_format.to_sym\n        inheritable_setting.namespace_inheritable[:format] = symbolic_new_format\n        inheritable_setting.namespace_inheritable[:default_error_formatter] = Grape::ErrorFormatter.formatter_for(symbolic_new_format)\n\n        content_type = content_types[symbolic_new_format]\n        raise Grape::Exceptions::MissingMimeType.new(new_format) unless content_type\n\n        inheritable_setting.namespace_stackable[:content_types] = { symbolic_new_format => content_type }\n      end\n\n      # Specify a custom formatter for a content-type.\n      def formatter(content_type, new_formatter)\n        inheritable_setting.namespace_stackable[:formatters] = { content_type.to_sym => new_formatter }\n      end\n\n      # Specify a custom parser for a content-type.\n      def parser(content_type, new_parser)\n        inheritable_setting.namespace_stackable[:parsers] = { content_type.to_sym => new_parser }\n      end\n\n      # Specify a default error formatter.\n      def default_error_formatter(new_formatter_name = nil)\n        return inheritable_setting.namespace_inheritable[:default_error_formatter] if new_formatter_name.nil?\n\n        new_formatter = Grape::ErrorFormatter.formatter_for(new_formatter_name)\n        inheritable_setting.namespace_inheritable[:default_error_formatter] = new_formatter\n      end\n\n      def error_formatter(format, options)\n        formatter = if options.is_a?(Hash) && options.key?(:with)\n                      options[:with]\n                    else\n                      options\n                    end\n\n        inheritable_setting.namespace_stackable[:error_formatters] = { format.to_sym => formatter }\n      end\n\n      # Specify additional content-types, e.g.:\n      #   content_type :xls, 'application/vnd.ms-excel'\n      def content_type(key, val)\n        inheritable_setting.namespace_stackable[:content_types] = { key.to_sym => val }\n      end\n\n      # All available content types.\n      def content_types\n        c_types = inheritable_setting.namespace_stackable_with_hash(:content_types)\n        Grape::ContentTypes.content_types_for c_types\n      end\n\n      # Specify the default status code for errors.\n      def default_error_status(new_status = nil)\n        return inheritable_setting.namespace_inheritable[:default_error_status] if new_status.nil?\n\n        inheritable_setting.namespace_inheritable[:default_error_status] = new_status\n      end\n\n      # Allows you to rescue certain exceptions that occur to return\n      # a grape error rather than raising all the way to the\n      # server level.\n      #\n      # @example Rescue from custom exceptions\n      #     class ExampleAPI < Grape::API\n      #       class CustomError < StandardError; end\n      #\n      #       rescue_from CustomError\n      #     end\n      #\n      # @overload rescue_from(*exception_classes, **options)\n      #   @param [Array] exception_classes A list of classes that you want to rescue, or\n      #     the symbol :all to rescue from all exceptions.\n      #   @param [Block] block Execution block to handle the given exception.\n      #   @param [Hash] options Options for the rescue usage.\n      #   @option options [Boolean] :backtrace Include a backtrace in the rescue response.\n      #   @option options [Boolean] :rescue_subclasses Also rescue subclasses of exception classes\n      #   @param [Proc] handler Execution proc to handle the given exception as an\n      #     alternative to passing a block.\n      def rescue_from(*args, **options, &block)\n        if args.last.is_a?(Proc)\n          handler = args.pop\n        elsif block\n          handler = block\n        end\n\n        raise ArgumentError, 'both :with option and block cannot be passed' if block && options.key?(:with)\n\n        handler ||= extract_with(options)\n\n        if args.include?(:all)\n          inheritable_setting.namespace_inheritable[:rescue_all] = true\n          inheritable_setting.namespace_inheritable[:all_rescue_handler] = handler\n        elsif args.include?(:grape_exceptions)\n          inheritable_setting.namespace_inheritable[:rescue_all] = true\n          inheritable_setting.namespace_inheritable[:rescue_grape_exceptions] = true\n          inheritable_setting.namespace_inheritable[:grape_exceptions_rescue_handler] = handler\n        else\n          handler_type =\n            case options[:rescue_subclasses]\n            when nil, true\n              :rescue_handlers\n            else\n              :base_only_rescue_handlers\n            end\n\n          inheritable_setting.namespace_reverse_stackable[handler_type] = args.to_h { |arg| [arg, handler] }\n        end\n\n        inheritable_setting.namespace_stackable[:rescue_options] = options\n      end\n\n      # Allows you to specify a default representation entity for a\n      # class. This allows you to map your models to their respective\n      # entities once and then simply call `present` with the model.\n      #\n      # @example\n      #   class ExampleAPI < Grape::API\n      #     represent User, with: Entity::User\n      #\n      #     get '/me' do\n      #       present current_user # with: Entity::User is assumed\n      #     end\n      #   end\n      #\n      # Note that Grape will automatically go up the class ancestry to\n      # try to find a representing entity, so if you, for example, define\n      # an entity to represent `Object` then all presented objects will\n      # bubble up and utilize the entity provided on that `represent` call.\n      #\n      # @param model_class [Class] The model class that will be represented.\n      # @option options [Class] :with The entity class that will represent the model.\n      def represent(model_class, options)\n        raise Grape::Exceptions::InvalidWithOptionForRepresent.new unless options[:with].is_a?(Class)\n\n        inheritable_setting.namespace_stackable[:representations] = { model_class => options[:with] }\n      end\n\n      private\n\n      def extract_with(options)\n        return unless options.key?(:with)\n\n        with_option = options.delete(:with)\n        return with_option if with_option.instance_of?(Proc)\n        return with_option.to_sym if with_option.instance_of?(Symbol) || with_option.instance_of?(String)\n\n        raise ArgumentError, \"with: #{with_option.class}, expected Symbol, String or Proc\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/dsl/routing.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module DSL\n    module Routing\n      attr_reader :endpoints\n\n      def given(conditional_option, &)\n        return unless conditional_option\n\n        mounted(&)\n      end\n\n      def mounted(&block)\n        evaluate_as_instance_with_configuration(block, lazy: true)\n      end\n\n      def cascade(value = nil)\n        return inheritable_setting.namespace_inheritable.key?(:cascade) ? !inheritable_setting.namespace_inheritable[:cascade].nil? : true if value.nil?\n\n        inheritable_setting.namespace_inheritable[:cascade] = value\n      end\n\n      # Specify an API version.\n      #\n      # @example API with legacy support.\n      #   class MyAPI < Grape::API\n      #     version 'v2'\n      #\n      #     get '/main' do\n      #       {some: 'data'}\n      #     end\n      #\n      #     version 'v1' do\n      #       get '/main' do\n      #         {legacy: 'data'}\n      #       end\n      #     end\n      #   end\n      #\n      def version(*args, **options, &block)\n        if args.any?\n          options = options.reverse_merge(using: :path)\n          requested_versions = args.flatten.map(&:to_s)\n\n          raise Grape::Exceptions::MissingVendorOption.new if options[:using] == :header && !options.key?(:vendor)\n\n          @versions = versions | requested_versions\n\n          if block\n            within_namespace do\n              inheritable_setting.namespace_inheritable[:version] = requested_versions\n              inheritable_setting.namespace_inheritable[:version_options] = options\n\n              instance_eval(&block)\n            end\n          else\n            inheritable_setting.namespace_inheritable[:version] = requested_versions\n            inheritable_setting.namespace_inheritable[:version_options] = options\n          end\n        end\n\n        @versions&.last\n      end\n\n      # Define a root URL prefix for your entire API.\n      def prefix(prefix = nil)\n        return inheritable_setting.namespace_inheritable[:root_prefix] if prefix.nil?\n\n        inheritable_setting.namespace_inheritable[:root_prefix] = prefix.to_s\n      end\n\n      # Create a scope without affecting the URL.\n      #\n      # @param _name [Symbol] Purely placebo, just allows to name the scope to\n      # make the code more readable.\n      def scope(_name = nil, &block)\n        within_namespace do\n          nest(block)\n        end\n      end\n\n      def build_with(build_with)\n        inheritable_setting.namespace_inheritable[:build_params_with] = build_with\n      end\n\n      # Do not route HEAD requests to GET requests automatically.\n      def do_not_route_head!\n        inheritable_setting.namespace_inheritable[:do_not_route_head] = true\n      end\n\n      # Do not automatically route OPTIONS.\n      def do_not_route_options!\n        inheritable_setting.namespace_inheritable[:do_not_route_options] = true\n      end\n\n      def lint!\n        inheritable_setting.namespace_inheritable[:lint] = true\n      end\n\n      def do_not_document!\n        inheritable_setting.namespace_inheritable[:do_not_document] = true\n      end\n\n      def mount(mounts, *opts)\n        mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair)\n        mounts.each_pair do |app, path|\n          if app.respond_to?(:mount_instance)\n            opts_with = opts.any? ? opts.first[:with] : {}\n            mount({ app.mount_instance(configuration: opts_with) => path }, *opts)\n            next\n          end\n          in_setting = inheritable_setting\n\n          if app.respond_to?(:inheritable_setting, true)\n            mount_path = Grape::Router.normalize_path(path)\n            app.top_level_setting.namespace_stackable[:mount_path] = mount_path\n\n            app.inherit_settings(inheritable_setting)\n\n            in_setting = app.top_level_setting\n\n            app.change!\n            change!\n          end\n\n          # When trying to mount multiple times the same endpoint, remove the previous ones\n          # from the list of endpoints if refresh_already_mounted parameter is true\n          refresh_already_mounted = opts.any? ? opts.first[:refresh_already_mounted] : false\n          if refresh_already_mounted && !endpoints.empty?\n            endpoints.delete_if do |endpoint|\n              endpoint.options[:app].to_s == app.to_s\n            end\n          end\n\n          endpoints << Grape::Endpoint.new(\n            in_setting,\n            method: :any,\n            path: path,\n            app: app,\n            route_options: { anchor: false },\n            forward_match: !app.respond_to?(:inheritable_setting),\n            for: self\n          )\n        end\n      end\n\n      # Defines a route that will be recognized\n      # by the Grape API.\n      #\n      # @param methods [HTTP Verb] One or more HTTP verbs that are accepted by this route. Set to `:any` if you want any verb to be accepted.\n      # @param paths [String] One or more strings representing the URL segment(s) for this route.\n      #\n      # @example Defining a basic route.\n      #   class MyAPI < Grape::API\n      #     route(:any, '/hello') do\n      #       {hello: 'world'}\n      #     end\n      #   end\n      def route(methods, paths = ['/'], route_options = {}, &)\n        method = methods == :any ? '*' : methods\n        endpoint_params = inheritable_setting.namespace_stackable_with_hash(:params) || {}\n        endpoint_description = inheritable_setting.route[:description]\n        all_route_options = { params: endpoint_params }\n        all_route_options.deep_merge!(endpoint_description) if endpoint_description\n        all_route_options.deep_merge!(route_options) if route_options&.any?\n\n        new_endpoint = Grape::Endpoint.new(\n          inheritable_setting,\n          method: method,\n          path: paths,\n          for: self,\n          route_options: all_route_options,\n          &\n        )\n        endpoints << new_endpoint unless endpoints.any? { |e| e.equals?(new_endpoint) }\n\n        inheritable_setting.route_end\n        reset_validations!\n      end\n\n      Grape::HTTP_SUPPORTED_METHODS.each do |supported_method|\n        define_method supported_method.downcase do |*args, **options, &block|\n          paths = args.first || ['/']\n          route(supported_method, paths, options, &block)\n        end\n      end\n\n      # Declare a \"namespace\", which prefixes all subordinate routes with its\n      # name. Any endpoints within a namespace, group, resource or segment,\n      # etc., will share their parent context as well as any configuration\n      # done in the namespace context.\n      #\n      # @example\n      #\n      #     namespace :foo do\n      #       get 'bar' do\n      #         # defines the endpoint: GET /foo/bar\n      #       end\n      #     end\n      def namespace(space = nil, requirements: nil, **options, &block)\n        return Namespace.joined_space_path(inheritable_setting.namespace_stackable[:namespace]) unless space || block\n\n        within_namespace do\n          nest(block) do\n            inheritable_setting.namespace_stackable[:namespace] = Grape::Namespace.new(space, requirements: requirements, **options) if space\n          end\n        end\n      end\n\n      alias group namespace\n      alias resource namespace\n      alias resources namespace\n      alias segment namespace\n\n      # An array of API routes.\n      def routes\n        @routes ||= endpoints.map(&:routes).flatten\n      end\n\n      # This method allows you to quickly define a parameter route segment\n      # in your API.\n      #\n      # @param param [Symbol] The name of the parameter you wish to declare.\n      # @option options [Regexp] You may supply a regular expression that the declared parameter must meet.\n      def route_param(param, requirements: nil, type: nil, **, &)\n        requirements = { param.to_sym => requirements } if requirements.is_a?(Regexp)\n\n        Grape::Validations::ParamsScope.new(api: self) do\n          requires param, type: type\n        end if type\n\n        namespace(\":#{param}\", requirements: requirements, **, &)\n      end\n\n      # @return array of defined versions\n      def versions\n        @versions ||= []\n      end\n\n      private\n\n      # Remove all defined routes.\n      def reset_routes!\n        endpoints.each(&:reset_routes!)\n        @routes = nil\n      end\n\n      def reset_endpoints!\n        @endpoints = []\n      end\n\n      def refresh_mounted_api(mounts, *opts)\n        opts << { refresh_already_mounted: true }\n        mount(mounts, *opts)\n      end\n\n      # Execute first the provided block, then each of the\n      # block passed in. Allows for simple 'before' setups\n      # of settings stack pushes.\n      def nest(*blocks, &block)\n        blocks.compact!\n        if blocks.any?\n          evaluate_as_instance_with_configuration(block) if block\n          blocks.each { |b| evaluate_as_instance_with_configuration(b) }\n          reset_validations!\n        else\n          instance_eval(&block)\n        end\n      end\n\n      def evaluate_as_instance_with_configuration(block, lazy: false)\n        lazy_block = Grape::Util::Lazy::Block.new do |configuration|\n          value_for_configuration = configuration\n          self.configuration = value_for_configuration.evaluate if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy?\n          response = instance_eval(&block)\n          self.configuration = value_for_configuration\n          response\n        end\n        if @base && base_instance? && lazy\n          lazy_block\n        else\n          lazy_block.evaluate_from(configuration)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/dsl/settings.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module DSL\n    # Keeps track of settings (implemented as key-value pairs, grouped by\n    # types), in two contexts: top-level settings which apply globally no\n    # matter where they're defined, and inheritable settings which apply only\n    # in the current scope and scopes nested under it.\n    module Settings\n      attr_writer :inheritable_setting\n\n      # Fetch our top-level settings, which apply to all endpoints in the API.\n      def top_level_setting\n        @top_level_setting ||= Grape::Util::InheritableSetting.new.tap do |setting|\n          # Doesn't try to inherit settings from +Grape::API::Instance+ which also responds to\n          # +inheritable_setting+, however, it doesn't contain any user-defined settings.\n          # Otherwise, it would lead to an extra instance of +Grape::Util::InheritableSetting+\n          # in the chain for every endpoint.\n          setting.inherit_from superclass.inheritable_setting if defined?(superclass) && superclass.respond_to?(:inheritable_setting) && superclass != Grape::API::Instance\n        end\n      end\n\n      # Fetch our current inheritable settings, which are inherited by\n      # nested scopes but not shared across siblings.\n      def inheritable_setting\n        @inheritable_setting ||= Grape::Util::InheritableSetting.new.tap { |new_settings| new_settings.inherit_from top_level_setting }\n      end\n\n      def global_setting(key, value = nil)\n        get_or_set(inheritable_setting.global, key, value)\n      end\n\n      def route_setting(key, value = nil)\n        get_or_set(inheritable_setting.route, key, value)\n      end\n\n      def namespace_setting(key, value = nil)\n        get_or_set(inheritable_setting.namespace, key, value)\n      end\n\n      private\n\n      # Execute the block within a context where our inheritable settings are forked\n      # to a new copy (see #namespace_start).\n      def within_namespace\n        new_inheritable_settings = Grape::Util::InheritableSetting.new\n        new_inheritable_settings.inherit_from inheritable_setting\n\n        @inheritable_setting = new_inheritable_settings\n\n        result = yield\n\n        inheritable_setting.route_end\n        @inheritable_setting = inheritable_setting.parent\n        reset_validations!\n\n        result\n      end\n\n      def get_or_set(setting, key, value)\n        return setting[key] if value.nil?\n\n        setting[key] = value\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/dsl/validations.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module DSL\n    module Validations\n      # Opens a root-level ParamsScope, defining parameter coercions and\n      # validations for the endpoint.\n      # @yield instance context of the new scope\n      def params(&)\n        Grape::Validations::ParamsScope.new(api: self, type: Hash, &)\n      end\n\n      # Declare the contract to be used for the endpoint's parameters.\n      # @param contract [Class<Dry::Validation::Contract> | Dry::Schema::Processor]\n      #   The contract or schema to be used for validation. Optional.\n      # @yield a block yielding a new instance of Dry::Schema::Params\n      #   subclass, allowing to define the schema inline. When the\n      #   +contract+ parameter is a schema, it will be used as a parent. Optional.\n      def contract(contract = nil, &block)\n        raise ArgumentError, 'Either contract or block must be provided' unless contract || block\n        raise ArgumentError, 'Cannot inherit from contract, only schema' if block && contract.respond_to?(:schema)\n\n        Grape::Validations::ContractScope.new(self, contract, &block)\n      end\n\n      private\n\n      # Clears all defined parameters and validations. The main purpose of it is to clean up\n      # settings, so next endpoint won't interfere with previous one.\n      #\n      #    params do\n      #      # params for the endpoint below this block\n      #    end\n      #    post '/current' do\n      #      # whatever\n      #    end\n      #\n      #    # somewhere between them the reset_validations! method gets called\n      #\n      #    params do\n      #      # params for the endpoint below this block\n      #    end\n      #    post '/next' do\n      #      # whatever\n      #    end\n      def reset_validations!\n        inheritable_setting.namespace_stackable.delete(:declared_params, :params, :validations)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/endpoint.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  # An Endpoint is the proxy scope in which all routing\n  # blocks are executed. In other words, any methods\n  # on the instance level of this class may be called\n  # from inside a `get`, `post`, etc.\n  class Endpoint\n    extend Forwardable\n    include Grape::DSL::Settings\n    include Grape::DSL::Headers\n    include Grape::DSL::InsideRoute\n\n    attr_reader :env, :request, :source, :options\n\n    def_delegators :request, :params, :headers, :cookies\n    def_delegator :cookies, :response_cookies\n\n    class << self\n      def before_each(new_setup = false, &block)\n        @before_each ||= []\n        if new_setup == false\n          return @before_each unless block\n\n          @before_each << block\n        elsif new_setup\n          @before_each = [new_setup]\n        else\n          @before_each.clear\n        end\n      end\n\n      def run_before_each(endpoint)\n        superclass.run_before_each(endpoint) unless self == Endpoint\n        before_each.each { |blk| blk.call(endpoint) }\n      end\n\n      def block_to_unbound_method(block)\n        return unless block\n\n        define_method :temp_unbound_method, block\n        method = instance_method(:temp_unbound_method)\n        remove_method :temp_unbound_method\n        method\n      end\n    end\n\n    # Create a new endpoint.\n    # @param new_settings [InheritableSetting] settings to determine the params,\n    #   validations, and other properties from.\n    # @param options [Hash] attributes of this endpoint\n    # @option options path [String or Array] the path to this endpoint, within\n    #   the current scope.\n    # @option options method [String or Array] which HTTP method(s) can be used\n    #   to reach this endpoint.\n    # @option options route_options [Hash]\n    # @note This happens at the time of API definition, so in this context the\n    # endpoint does not know if it will be mounted under a different endpoint.\n    # @yield a block defining what your API should do when this endpoint is hit\n    def initialize(new_settings, **options, &block)\n      self.inheritable_setting = new_settings.point_in_time_copy\n\n      # now +namespace_stackable(:declared_params)+ contains all params defined for\n      # this endpoint and its parents, but later it will be cleaned up,\n      # see +reset_validations!+ in lib/grape/dsl/validations.rb\n      inheritable_setting.route[:declared_params] = inheritable_setting.namespace_stackable[:declared_params].flatten\n      inheritable_setting.route[:saved_validations] = inheritable_setting.namespace_stackable[:validations]\n\n      inheritable_setting.namespace_stackable[:representations] = [] unless inheritable_setting.namespace_stackable[:representations]\n      inheritable_setting.namespace_inheritable[:default_error_status] = 500 unless inheritable_setting.namespace_inheritable[:default_error_status]\n\n      @options = options\n\n      @options[:path] = Array(options[:path])\n      @options[:path] << '/' if options[:path].empty?\n      @options[:method] = Array(options[:method])\n\n      @status = nil\n      @stream = nil\n      @body = nil\n      @source = self.class.block_to_unbound_method(block)\n      @before_filter_passed = false\n    end\n\n    # Update our settings from a given set of stackable parameters. Used when\n    # the endpoint's API is mounted under another one.\n    def inherit_settings(namespace_stackable)\n      parent_validations = namespace_stackable[:validations]\n      inheritable_setting.route[:saved_validations].concat(parent_validations) if parent_validations.any?\n      parent_declared_params = namespace_stackable[:declared_params]\n      inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) if parent_declared_params.any?\n\n      endpoints&.each { |e| e.inherit_settings(namespace_stackable) }\n    end\n\n    def routes\n      @routes ||= endpoints&.collect(&:routes)&.flatten || to_routes\n    end\n\n    def reset_routes!\n      endpoints&.each(&:reset_routes!)\n      @namespace = nil\n      @routes = nil\n    end\n\n    def mount_in(router)\n      if endpoints\n        compile!\n        return endpoints.each { |e| e.mount_in(router) }\n      end\n\n      reset_routes!\n      compile!\n      routes.each do |route|\n        router.append(route.apply(self))\n        next unless !inheritable_setting.namespace_inheritable[:do_not_route_head] && route.request_method == Rack::GET\n\n        route.dup.then do |head_route|\n          head_route.convert_to_head_request!\n          router.append(head_route.apply(self))\n        end\n      end\n    end\n\n    def namespace\n      @namespace ||= Namespace.joined_space_path(inheritable_setting.namespace_stackable[:namespace])\n    end\n\n    def call(env)\n      dup.call!(env)\n    end\n\n    def call!(env)\n      env[Grape::Env::API_ENDPOINT] = self\n      @env = env\n      # this adds the helpers only to the instance\n      singleton_class.include(@helpers) if @helpers\n      @app.call(env)\n    end\n\n    # Return the collection of endpoints within this endpoint.\n    # This is the case when an Grape::API mounts another Grape::API.\n    def endpoints\n      @endpoints ||= options[:app].respond_to?(:endpoints) ? options[:app].endpoints : nil\n    end\n\n    def equals?(endpoint)\n      (options == endpoint.options) && (inheritable_setting.to_hash == endpoint.inheritable_setting.to_hash)\n    end\n\n    # The purpose of this override is solely for stripping internals when an error occurs while calling\n    # an endpoint through an api. See https://github.com/ruby-grape/grape/issues/2398\n    # Otherwise, it calls super.\n    def inspect\n      return super unless env\n\n      \"#{self.class} in '#{route.origin}' endpoint\"\n    end\n\n    protected\n\n    def run\n      ActiveSupport::Notifications.instrument('endpoint_run.grape', endpoint: self, env: env) do\n        @request = Grape::Request.new(env, build_params_with: inheritable_setting.namespace_inheritable[:build_params_with])\n        begin\n          self.class.run_before_each(self)\n          run_filters befores, :before\n          @before_filter_passed = true\n\n          if env.key?(Grape::Env::GRAPE_ALLOWED_METHODS)\n            header['Allow'] = env[Grape::Env::GRAPE_ALLOWED_METHODS].join(', ')\n            raise Grape::Exceptions::MethodNotAllowed.new(header) unless options?\n\n            header 'Allow', header['Allow']\n            response_object = ''\n            status 204\n          else\n            run_filters before_validations, :before_validation\n            run_validators validations, request\n            run_filters after_validations, :after_validation\n            response_object = execute\n          end\n\n          run_filters afters, :after\n          build_response_cookies\n\n          # status verifies body presence when DELETE\n          @body ||= response_object\n\n          # The body commonly is an Array of Strings, the application instance itself, or a Stream-like object\n          response_object = stream || [body]\n\n          [status, header, response_object]\n        ensure\n          run_filters finallies, :finally\n        end\n      end\n    end\n\n    def execute\n      return unless @source\n\n      ActiveSupport::Notifications.instrument('endpoint_render.grape', endpoint: self) do\n        @source.bind_call(self)\n      end\n    end\n\n    def run_validators(validators, request)\n      validation_errors = []\n\n      Grape::Validations::ParamScopeTracker.track do\n        ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators: validators, request: request) do\n          validators.each do |validator|\n            validator.validate(request)\n          rescue Grape::Exceptions::Validation => e\n            validation_errors << e\n            break if validator.fail_fast?\n          rescue Grape::Exceptions::ValidationArrayErrors => e\n            validation_errors.concat e.errors\n            break if validator.fail_fast?\n          end\n        end\n      end\n\n      validation_errors.any? && raise(Grape::Exceptions::ValidationErrors.new(errors: validation_errors, headers: header))\n    end\n\n    def run_filters(filters, type = :other)\n      return unless filters\n\n      ActiveSupport::Notifications.instrument('endpoint_run_filters.grape', endpoint: self, filters: filters, type: type) do\n        filters.each { |filter| instance_eval(&filter) }\n      end\n    end\n\n    %i[befores before_validations after_validations afters finallies].each do |method|\n      define_method method do\n        inheritable_setting.namespace_stackable[method]\n      end\n    end\n\n    def validations\n      saved_validations = inheritable_setting.route[:saved_validations]\n      return if saved_validations.nil?\n      return enum_for(:validations) unless block_given?\n\n      saved_validations.each do |saved_validation|\n        yield Grape::Validations::ValidatorFactory.create_validator(saved_validation)\n      end\n    end\n\n    def options?\n      options[:options_route_enabled] &&\n        env[Rack::REQUEST_METHOD] == Rack::OPTIONS\n    end\n\n    private\n\n    attr_reader :before_filter_passed\n\n    def compile!\n      @app = options[:app] || build_stack\n      @helpers = build_helpers\n    end\n\n    def to_routes\n      route_options = options[:route_options]\n      default_route_options = prepare_default_route_attributes(route_options)\n      complete_route_options = route_options.merge(default_route_options)\n      path_settings = prepare_default_path_settings\n\n      options[:method].flat_map do |method|\n        options[:path].map do |path|\n          prepared_path = Path.new(path, default_route_options[:namespace], path_settings)\n          pattern = Grape::Router::Pattern.new(\n            origin: prepared_path.origin,\n            suffix: prepared_path.suffix,\n            anchor: default_route_options[:anchor],\n            params: route_options[:params],\n            format: options[:format],\n            version: default_route_options[:version],\n            requirements: default_route_options[:requirements]\n          )\n          Grape::Router::Route.new(self, method, pattern, complete_route_options)\n        end\n      end\n    end\n\n    def prepare_default_route_attributes(route_options)\n      {\n        namespace: namespace,\n        version: prepare_version(inheritable_setting.namespace_inheritable[:version]),\n        requirements: prepare_routes_requirements(route_options[:requirements]),\n        prefix: inheritable_setting.namespace_inheritable[:root_prefix],\n        anchor: route_options.fetch(:anchor, true),\n        settings: inheritable_setting.route.except(:declared_params, :saved_validations),\n        forward_match: options[:forward_match]\n      }\n    end\n\n    def prepare_default_path_settings\n      namespace_stackable_hash = inheritable_setting.namespace_stackable.to_hash\n      namespace_inheritable_hash = inheritable_setting.namespace_inheritable.to_hash\n      namespace_stackable_hash.merge!(namespace_inheritable_hash)\n    end\n\n    def prepare_routes_requirements(route_options_requirements)\n      namespace_requirements = inheritable_setting.namespace_stackable[:namespace].filter_map(&:requirements)\n      namespace_requirements << route_options_requirements if route_options_requirements.present?\n      namespace_requirements.reduce({}, :merge)\n    end\n\n    def prepare_version(namespace_inheritable_version)\n      return if namespace_inheritable_version.blank?\n\n      namespace_inheritable_version.length == 1 ? namespace_inheritable_version.first : namespace_inheritable_version\n    end\n\n    def build_stack\n      stack = Grape::Middleware::Stack.new\n\n      content_types = inheritable_setting.namespace_stackable_with_hash(:content_types)\n      format = inheritable_setting.namespace_inheritable[:format]\n\n      stack.use Rack::Head\n      stack.use Rack::Lint if lint?\n      stack.use Grape::Middleware::Error,\n                format: format,\n                content_types: content_types,\n                default_status: inheritable_setting.namespace_inheritable[:default_error_status],\n                rescue_all: inheritable_setting.namespace_inheritable[:rescue_all],\n                rescue_grape_exceptions: inheritable_setting.namespace_inheritable[:rescue_grape_exceptions],\n                default_error_formatter: inheritable_setting.namespace_inheritable[:default_error_formatter],\n                error_formatters: inheritable_setting.namespace_stackable_with_hash(:error_formatters),\n                rescue_options: inheritable_setting.namespace_stackable_with_hash(:rescue_options),\n                rescue_handlers: rescue_handlers,\n                base_only_rescue_handlers: inheritable_setting.namespace_stackable_with_hash(:base_only_rescue_handlers),\n                all_rescue_handler: inheritable_setting.namespace_inheritable[:all_rescue_handler],\n                grape_exceptions_rescue_handler: inheritable_setting.namespace_inheritable[:grape_exceptions_rescue_handler]\n\n      stack.concat inheritable_setting.namespace_stackable[:middleware]\n\n      if inheritable_setting.namespace_inheritable[:version].present?\n        stack.use Grape::Middleware::Versioner.using(inheritable_setting.namespace_inheritable[:version_options][:using]),\n                  versions: inheritable_setting.namespace_inheritable[:version].flatten,\n                  version_options: inheritable_setting.namespace_inheritable[:version_options],\n                  prefix: inheritable_setting.namespace_inheritable[:root_prefix],\n                  mount_path: inheritable_setting.namespace_stackable[:mount_path].first\n      end\n\n      stack.use Grape::Middleware::Formatter,\n                format: format,\n                default_format: inheritable_setting.namespace_inheritable[:default_format] || :txt,\n                content_types: content_types,\n                formatters: inheritable_setting.namespace_stackable_with_hash(:formatters),\n                parsers: inheritable_setting.namespace_stackable_with_hash(:parsers)\n\n      builder = stack.build\n      builder.run ->(env) { env[Grape::Env::API_ENDPOINT].run }\n      builder.to_app\n    end\n\n    def build_helpers\n      helpers = inheritable_setting.namespace_stackable[:helpers]\n      return if helpers.empty?\n\n      Module.new { helpers.each { |mod_to_include| include mod_to_include } }\n    end\n\n    def build_response_cookies\n      response_cookies do |name, value|\n        cookie_value = value.is_a?(Hash) ? value : { value: value }\n        Rack::Utils.set_cookie_header! header, name, cookie_value\n      end\n    end\n\n    def lint?\n      inheritable_setting.namespace_inheritable[:lint] || Grape.config.lint\n    end\n\n    def rescue_handlers\n      rescue_handlers = inheritable_setting.namespace_reverse_stackable[:rescue_handlers]\n      return if rescue_handlers.blank?\n\n      rescue_handlers.each_with_object({}) do |rescue_handler, result|\n        result.merge!(rescue_handler) { |_k, s1, _s2| s1 }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/env.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Env\n    API_VERSION = 'api.version'\n    API_ENDPOINT = 'api.endpoint'\n    API_REQUEST_INPUT = 'api.request.input'\n    API_REQUEST_BODY = 'api.request.body'\n    API_TYPE = 'api.type'\n    API_SUBTYPE = 'api.subtype'\n    API_VENDOR = 'api.vendor'\n    API_FORMAT = 'api.format'\n\n    GRAPE_REQUEST = 'grape.request'\n    GRAPE_REQUEST_HEADERS = 'grape.request.headers'\n    GRAPE_REQUEST_PARAMS = 'grape.request.params'\n    GRAPE_ROUTING_ARGS = 'grape.routing_args'\n    GRAPE_ALLOWED_METHODS = 'grape.allowed_methods'\n  end\nend\n"
  },
  {
    "path": "lib/grape/error_formatter/base.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module ErrorFormatter\n    class Base\n      class << self\n        def call(message, backtrace, options = {}, env = nil, original_exception = nil)\n          merge_backtrace = backtrace.present? && options.dig(:rescue_options, :backtrace)\n          merge_original_exception = original_exception && options.dig(:rescue_options, :original_exception)\n\n          wrapped_message = wrap_message(present(message, env))\n          if wrapped_message.is_a?(Hash)\n            wrapped_message[:backtrace] = backtrace if merge_backtrace\n            wrapped_message[:original_exception] = original_exception.inspect if merge_original_exception\n          end\n\n          format_structured_message(wrapped_message)\n        end\n\n        def present(message, env)\n          present_options = {}\n          presented_message = message\n          if presented_message.is_a?(Hash)\n            presented_message = presented_message.dup\n            present_options[:with] = presented_message.delete(:with)\n          end\n\n          presenter = env[Grape::Env::API_ENDPOINT].entity_class_for_obj(presented_message, present_options)\n\n          unless presenter || env[Grape::Env::GRAPE_ROUTING_ARGS].nil?\n            # env['api.endpoint'].route does not work when the error occurs within a middleware\n            # the Endpoint does not have a valid env at this moment\n            http_codes = env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info].http_codes || []\n\n            found_code = http_codes.find do |http_code|\n              (http_code[0].to_i == env[Grape::Env::API_ENDPOINT].status) && http_code[2].respond_to?(:represent)\n            end if env[Grape::Env::API_ENDPOINT].request\n\n            presenter = found_code[2] if found_code\n          end\n\n          if presenter\n            embeds = { env: env }\n            embeds[:version] = env[Grape::Env::API_VERSION] if env.key?(Grape::Env::API_VERSION)\n            presented_message = presenter.represent(presented_message, embeds).serializable_hash\n          end\n\n          presented_message\n        end\n\n        def wrap_message(message)\n          return message if message.is_a?(Hash)\n\n          { message: message }\n        end\n\n        def format_structured_message(_structured_message)\n          raise NotImplementedError\n        end\n\n        private\n\n        def inherited(klass)\n          super\n          ErrorFormatter.register(klass)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/error_formatter/json.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module ErrorFormatter\n    class Json < Base\n      class << self\n        def format_structured_message(structured_message)\n          ::Grape::Json.dump(structured_message)\n        end\n\n        private\n\n        def wrap_message(message)\n          return message if message.is_a?(Hash)\n          return message.as_json if message.is_a?(Exceptions::ValidationErrors)\n\n          { error: ensure_utf8(message) }\n        end\n\n        def ensure_utf8(message)\n          return message unless message.respond_to? :encode\n\n          message.encode('UTF-8', invalid: :replace, undef: :replace)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/error_formatter/serializable_hash.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module ErrorFormatter\n    class SerializableHash < Json; end\n  end\nend\n"
  },
  {
    "path": "lib/grape/error_formatter/txt.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module ErrorFormatter\n    class Txt < Base\n      def self.format_structured_message(structured_message)\n        message = structured_message[:message] || Grape::Json.dump(structured_message)\n        Array.wrap(message).tap do |final_message|\n          if structured_message.key?(:backtrace)\n            final_message << 'backtrace:'\n            final_message.concat(structured_message[:backtrace])\n          end\n          if structured_message.key?(:original_exception)\n            final_message << 'original exception:'\n            final_message << structured_message[:original_exception]\n          end\n        end.join(\"\\r\\n \")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/error_formatter/xml.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module ErrorFormatter\n    class Xml < Base\n      def self.format_structured_message(structured_message)\n        structured_message.respond_to?(:to_xml) ? structured_message.to_xml(root: :error) : structured_message.to_s\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/error_formatter.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module ErrorFormatter\n    extend Grape::Util::Registry\n\n    module_function\n\n    def formatter_for(format, error_formatters = nil, default_error_formatter = nil)\n      return error_formatters[format] if error_formatters&.key?(format)\n\n      registry[format] || default_error_formatter || Grape::ErrorFormatter::Txt\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/base.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class Base < StandardError\n      include Grape::Util::Translation\n\n      MESSAGE_STEPS = %w[problem summary resolution].to_h { |s| [s, s.capitalize] }.freeze\n\n      attr_reader :status, :headers\n\n      def initialize(status: nil, message: nil, headers: nil)\n        super(message)\n\n        @status  = status\n        @headers = headers\n      end\n\n      def [](index)\n        __send__ index\n      end\n\n      private\n\n      def compose_message(key, **)\n        short_message = translate_message(key, **)\n        return short_message unless short_message.is_a?(Hash)\n\n        MESSAGE_STEPS.filter_map do |step, label|\n          detail = translate_message(:\"#{key}.#{step}\", **)\n          \"\\n#{label}:\\n  #{detail}\" if detail.present?\n        end.join\n      end\n\n      def translate_message(translation_key, **)\n        case translation_key\n        when Symbol\n          translate(translation_key, **)\n        when Hash\n          translation_key => { key:, **opts }\n          translate(key, **opts)\n        when Proc\n          translation_key.call\n        else\n          translation_key\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/conflicting_types.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class ConflictingTypes < Base\n      def initialize\n        super(message: compose_message(:conflicting_types), status: 400)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/empty_message_body.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class EmptyMessageBody < Base\n      def initialize(body_format)\n        super(message: compose_message(:empty_message_body, body_format: body_format), status: 400)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/incompatible_option_values.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class IncompatibleOptionValues < Base\n      def initialize(option1, value1, option2, value2)\n        super(message: compose_message(:incompatible_option_values, option1: option1, value1: value1, option2: option2, value2: value2))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/invalid_accept_header.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class InvalidAcceptHeader < Base\n      def initialize(message, headers)\n        super(message: compose_message(:invalid_accept_header, message: message), status: 406, headers: headers)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/invalid_formatter.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class InvalidFormatter < Base\n      def initialize(klass, to_format)\n        super(message: compose_message(:invalid_formatter, klass: klass, to_format: to_format))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/invalid_message_body.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class InvalidMessageBody < Base\n      def initialize(body_format)\n        super(message: compose_message(:invalid_message_body, body_format: body_format), status: 400)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/invalid_parameters.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class InvalidParameters < Base\n      def initialize\n        super(message: compose_message(:invalid_parameters), status: 400)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/invalid_response.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class InvalidResponse < Base\n      def initialize\n        super(message: compose_message(:invalid_response))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/invalid_version_header.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class InvalidVersionHeader < Base\n      def initialize(message, headers)\n        super(message: compose_message(:invalid_version_header, message: message), status: 406, headers: headers)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/invalid_versioner_option.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class InvalidVersionerOption < Base\n      def initialize(strategy)\n        super(message: compose_message(:invalid_versioner_option, strategy: strategy))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/invalid_with_option_for_represent.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class InvalidWithOptionForRepresent < Base\n      def initialize\n        super(message: compose_message(:invalid_with_option_for_represent))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/method_not_allowed.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class MethodNotAllowed < Base\n      def initialize(headers)\n        super(message: '405 Not Allowed', status: 405, headers: headers)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/missing_group_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class MissingGroupType < Base\n      def initialize\n        super(message: compose_message(:missing_group_type))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/missing_mime_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class MissingMimeType < Base\n      def initialize(new_format)\n        super(message: compose_message(:missing_mime_type, new_format: new_format))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/missing_vendor_option.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class MissingVendorOption < Base\n      def initialize\n        super(message: compose_message(:missing_vendor_option))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/too_deep_parameters.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class TooDeepParameters < Base\n      def initialize(limit)\n        super(message: compose_message(:too_deep_parameters, limit: limit), status: 400)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/too_many_multipart_files.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class TooManyMultipartFiles < Base\n      def initialize(limit)\n        super(message: compose_message(:too_many_multipart_files, limit: limit), status: 413)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/unknown_auth_strategy.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class UnknownAuthStrategy < Base\n      def initialize(strategy:)\n        super(message: compose_message(:unknown_auth_strategy, strategy: strategy))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/unknown_parameter.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class UnknownParameter < Base\n      def initialize(param)\n        super(message: compose_message(:unknown_parameter, param: param))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/unknown_params_builder.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class UnknownParamsBuilder < Base\n      def initialize(params_builder_type)\n        super(message: compose_message(:unknown_params_builder, params_builder_type: params_builder_type))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/unknown_validator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class UnknownValidator < Base\n      def initialize(validator_type)\n        super(message: compose_message(:unknown_validator, validator_type: validator_type))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/unsupported_group_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class UnsupportedGroupType < Base\n      def initialize\n        super(message: compose_message(:unsupported_group_type))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/validation.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class Validation < Base\n      attr_reader :params, :message_key\n\n      def initialize(params:, message: nil, status: nil, headers: nil)\n        @params = Array(params)\n        if message\n          @message_key = case message\n                         when Symbol then message\n                         when Hash then message[:key]\n                         end\n          message = translate_message(message)\n        end\n\n        super(status: status, message: message, headers: headers)\n      end\n\n      # Remove all the unnecessary stuff from Grape::Exceptions::Base like status\n      # and headers when converting a validation error to json or string\n      def as_json(*_args)\n        to_s\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/validation_array_errors.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class ValidationArrayErrors < Base\n      attr_reader :errors\n\n      def initialize(errors)\n        super()\n        @errors = errors\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/exceptions/validation_errors.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Exceptions\n    class ValidationErrors < Base\n      include Enumerable\n\n      attr_reader :errors\n\n      def initialize(errors: [], headers: {})\n        @errors = errors.group_by(&:params)\n        super(message: full_messages.join(', '), status: 400, headers: headers)\n      end\n\n      def each\n        errors.each_pair do |attribute, errors|\n          errors.each do |error|\n            yield attribute, error\n          end\n        end\n      end\n\n      def as_json(**_opts)\n        errors.map do |k, v|\n          {\n            params: k,\n            messages: v.map(&:to_s)\n          }\n        end\n      end\n\n      def to_json(*_opts)\n        as_json.to_json\n      end\n\n      def full_messages\n        messages = map do |attributes, error|\n          translate(\n            :format,\n            scope: 'grape.errors',\n            default: '%<attributes>s %<message>s',\n            attributes: translate_attributes(attributes),\n            message: error.message\n          )\n        end\n        messages.uniq!\n        messages\n      end\n\n      private\n\n      def translate_attributes(keys)\n        keys.map do |key|\n          translate(key, scope: 'grape.errors.attributes', default: key.to_s)\n        end.join(', ')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/formatter/base.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Formatter\n    class Base\n      def self.call(_object, _env)\n        raise NotImplementedError\n      end\n\n      def self.inherited(klass)\n        super\n        Formatter.register(klass)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/formatter/json.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Formatter\n    class Json < Base\n      def self.call(object, _env)\n        return object.to_json if object.respond_to?(:to_json)\n\n        ::Grape::Json.dump(object)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/formatter/serializable_hash.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Formatter\n    class SerializableHash < Base\n      class << self\n        def call(object, _env)\n          return object if object.is_a?(String)\n          return ::Grape::Json.dump(serialize(object)) if serializable?(object)\n          return object.to_json if object.respond_to?(:to_json)\n\n          ::Grape::Json.dump(object)\n        end\n\n        private\n\n        def serializable?(object)\n          object.respond_to?(:serializable_hash) || array_serializable?(object) || object.is_a?(Hash)\n        end\n\n        def serialize(object)\n          if object.respond_to? :serializable_hash\n            object.serializable_hash\n          elsif array_serializable?(object)\n            object.map(&:serializable_hash)\n          elsif object.is_a?(Hash)\n            object.transform_values { |v| serialize(v) }\n          else\n            object\n          end\n        end\n\n        def array_serializable?(object)\n          object.is_a?(Array) && object.all? { |o| o.respond_to? :serializable_hash }\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/formatter/txt.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Formatter\n    class Txt < Base\n      def self.call(object, _env)\n        object.respond_to?(:to_txt) ? object.to_txt : object.to_s\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/formatter/xml.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Formatter\n    class Xml < Base\n      def self.call(object, _env)\n        return object.to_xml if object.respond_to?(:to_xml)\n\n        raise Grape::Exceptions::InvalidFormatter.new(object.class, 'xml')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/formatter.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Formatter\n    extend Grape::Util::Registry\n\n    module_function\n\n    DEFAULT_LAMBDA_FORMATTER = ->(obj, _env) { obj }\n\n    def formatter_for(api_format, formatters)\n      return formatters[api_format] if formatters&.key?(api_format)\n\n      registry[api_format] || DEFAULT_LAMBDA_FORMATTER\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/json.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  if defined?(::MultiJson)\n    Json = ::MultiJson\n  else\n    Json = ::JSON\n    Json::ParseError = Json::ParserError\n  end\nend\n"
  },
  {
    "path": "lib/grape/locale/en.yml",
    "content": "---\nen:\n  grape:\n    errors:\n      format: '%{attributes} %{message}'\n      messages:\n        all_or_none: 'provide all or none of parameters'\n        at_least_one: 'are missing, at least one parameter must be provided'\n        blank: 'is empty'\n        coerce: 'is invalid'\n        conflicting_types: 'query params contains conflicting types'\n        empty_message_body: 'empty message body supplied with %{body_format} content-type'\n        exactly_one: 'are missing, exactly one parameter must be provided'\n        except_values: 'has a value not allowed'\n        incompatible_option_values: '%{option1}: %{value1} is incompatible with %{option2}: %{value2}'\n        invalid_accept_header:\n          problem: 'invalid accept header'\n          resolution: '%{message}'\n        invalid_formatter: 'cannot convert %{klass} to %{to_format}'\n        invalid_message_body:\n          problem: 'message body does not match declared format'\n          resolution: 'when specifying %{body_format} as content-type, you must pass valid %{body_format} in the request''s ''body'' '\n        invalid_parameters: 'query params contains invalid format or byte sequence'\n        invalid_response: 'Invalid response'\n        invalid_version_header:\n          problem: 'invalid version header'\n          resolution: '%{message}'\n        invalid_versioner_option:\n          problem: 'unknown :using for versioner: %{strategy}'\n          resolution: 'available strategy for :using is :path, :header, :accept_version_header, :param'\n        invalid_with_option_for_represent:\n          problem: 'you must specify an entity class in the :with option'\n          resolution: 'eg: represent User, :with => Entity::User'\n        length: 'is expected to have length within %{min} and %{max}'\n        length_is: 'is expected to have length exactly equal to %{is}'\n        length_max: 'is expected to have length less than or equal to %{max}'\n        length_min: 'is expected to have length greater than or equal to %{min}'\n        missing_group_type: 'group type is required'\n        missing_mime_type:\n          problem: 'missing mime type for %{new_format}'\n          resolution: 'you can choose existing mime type from Grape::ContentTypes::CONTENT_TYPES or add your own with content_type :%{new_format}, ''application/%{new_format}'' '\n        missing_option: 'you must specify :%{option} options'\n        missing_vendor_option:\n          problem: 'missing :vendor option'\n          resolution: 'eg: version ''v1'', using: :header, vendor: ''twitter'''\n          summary: 'when version using header, you must specify :vendor option'\n        mutual_exclusion: 'are mutually exclusive'\n        presence: 'is missing'\n        regexp: 'is invalid'\n        same_as: 'is not the same as %{parameter}'\n        too_deep_parameters: 'query params are recursively nested over the specified limit (%{limit})'\n        too_many_multipart_files: 'the number of uploaded files exceeded the system''s configured limit (%{limit})'\n        unknown_auth_strategy: 'unknown auth strategy: %{strategy}'\n        unknown_options: 'unknown options: %{options}'\n        unknown_parameter: 'unknown parameter: %{param}'\n        unknown_params_builder: 'unknown params_builder: %{params_builder_type}'\n        unknown_validator: 'unknown validator: %{validator_type}'\n        unsupported_group_type: 'group type must be Array, Hash, JSON or Array[JSON]'\n        values: 'does not have a valid value'\n"
  },
  {
    "path": "lib/grape/middleware/auth/base.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Middleware\n    module Auth\n      class Base < Grape::Middleware::Base\n        def initialize(app, **options)\n          super\n          @auth_strategy = Grape::Middleware::Auth::Strategies[options[:type]].tap do |auth_strategy|\n            raise Grape::Exceptions::UnknownAuthStrategy.new(strategy: options[:type]) unless auth_strategy\n          end\n        end\n\n        def call!(env)\n          @env = env\n          @auth_strategy.create(app, options) do |*args|\n            context.instance_exec(*args, &options[:proc])\n          end.call(env)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/middleware/auth/dsl.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Middleware\n    module Auth\n      module DSL\n        def auth(type = nil, options = {}, &block)\n          namespace_inheritable = inheritable_setting.namespace_inheritable\n          return namespace_inheritable[:auth] unless type\n\n          namespace_inheritable[:auth] = options.reverse_merge(type: type.to_sym, proc: block)\n          use Grape::Middleware::Auth::Base, namespace_inheritable[:auth]\n        end\n\n        # Add HTTP Basic authorization to the API.\n        #\n        # @param [Hash] options A hash of options.\n        # @option options [String] :realm \"API Authorization\" The HTTP Basic realm.\n        def http_basic(options = {}, &)\n          options[:realm] ||= 'API Authorization'\n          auth(:http_basic, options, &)\n        end\n\n        def http_digest(options = {}, &)\n          options[:realm] ||= 'API Authorization'\n\n          if options[:realm].respond_to?(:values_at)\n            options[:realm][:opaque] ||= 'secret'\n          else\n            options[:opaque] ||= 'secret'\n          end\n\n          auth(:http_digest, options, &)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/middleware/auth/strategies.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Middleware\n    module Auth\n      module Strategies\n        module_function\n\n        def add(label, strategy, option_fetcher = ->(_) { [] })\n          auth_strategies[label] = StrategyInfo.new(strategy, option_fetcher)\n        end\n\n        def auth_strategies\n          @auth_strategies ||= {\n            http_basic: StrategyInfo.new(Rack::Auth::Basic, ->(settings) { [settings[:realm]] })\n          }\n        end\n\n        def [](label)\n          auth_strategies[label]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/middleware/auth/strategy_info.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Middleware\n    module Auth\n      StrategyInfo = Struct.new(:auth_class, :settings_fetcher) do\n        def create(app, options, &block)\n          strategy_args = settings_fetcher.call(options)\n\n          auth_class.new(app, *strategy_args, &block)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/middleware/base.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Middleware\n    class Base\n      include Grape::DSL::Headers\n\n      attr_reader :app, :env, :options\n\n      # @param [Rack Application] app The standard argument for a Rack middleware.\n      # @param [Hash] options A hash of options, simply stored for use by subclasses.\n      def initialize(app, **options)\n        @app = app\n        @options = merge_default_options(options)\n        @app_response = nil\n      end\n\n      def call(env)\n        dup.call!(env).to_a\n      end\n\n      def call!(env)\n        @env = env\n        before\n        begin\n          @app_response = @app.call(@env)\n        ensure\n          begin\n            after_response = after\n          rescue StandardError => e\n            warn \"caught error of type #{e.class} in after callback inside #{self.class.name} : #{e.message}\"\n            raise e\n          end\n        end\n\n        response = after_response || @app_response\n        merge_headers response\n        response\n      end\n\n      # @abstract\n      # Called before the application is called in the middleware lifecycle.\n      def before; end\n\n      # @abstract\n      # Called after the application is called in the middleware lifecycle.\n      # @return [Response, nil] a Rack SPEC response or nil to call the application afterwards.\n      def after; end\n\n      def rack_request\n        @rack_request ||= Rack::Request.new(env)\n      end\n\n      def context\n        env[Grape::Env::API_ENDPOINT]\n      end\n\n      def response\n        return @app_response if @app_response.is_a?(Rack::Response)\n\n        @app_response = Rack::Response[*@app_response]\n      end\n\n      def content_types\n        @content_types ||= Grape::ContentTypes.content_types_for(options[:content_types])\n      end\n\n      def mime_types\n        @mime_types ||= Grape::ContentTypes.mime_types_for(content_types)\n      end\n\n      def content_type_for(format)\n        content_types_indifferent_access[format]\n      end\n\n      def content_type\n        content_type_for(env[Grape::Env::API_FORMAT] || options[:format]) || 'text/html'\n      end\n\n      def query_params\n        rack_request.GET\n      rescue Rack::QueryParser::ParamsTooDeepError\n        raise Grape::Exceptions::TooDeepParameters.new(Rack::Utils.param_depth_limit)\n      rescue Rack::Utils::ParameterTypeError\n        raise Grape::Exceptions::ConflictingTypes\n      end\n\n      private\n\n      def merge_headers(response)\n        return unless headers.is_a?(Hash)\n\n        case response\n        when Rack::Response then response.headers.merge!(headers)\n        when Array          then response[1].merge!(headers)\n        end\n      end\n\n      def content_types_indifferent_access\n        @content_types_indifferent_access ||= content_types.with_indifferent_access\n      end\n\n      def merge_default_options(options)\n        if respond_to?(:default_options)\n          default_options.deep_merge(options)\n        elsif self.class.const_defined?(:DEFAULT_OPTIONS)\n          self.class::DEFAULT_OPTIONS.deep_merge(options)\n        else\n          options\n        end\n      end\n\n      def try_scrub(obj)\n        obj.respond_to?(:valid_encoding?) && !obj.valid_encoding? ? obj.scrub : obj\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/middleware/error.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Middleware\n    class Error < Base\n      DEFAULT_OPTIONS = {\n        default_status: 500,\n        default_message: '',\n        format: :txt,\n        rescue_all: false,\n        rescue_grape_exceptions: false,\n        rescue_subclasses: true,\n        rescue_options: {\n          backtrace: false,\n          original_exception: false\n        }.freeze\n      }.freeze\n\n      def call!(env)\n        @env = env\n        error_response(catch(:error) { return @app.call(@env) })\n      rescue Exception => e # rubocop:disable Lint/RescueException\n        run_rescue_handler(find_handler(e.class), e, @env[Grape::Env::API_ENDPOINT])\n      end\n\n      private\n\n      def rack_response(status, headers, message)\n        message = Rack::Utils.escape_html(message) if headers[Rack::CONTENT_TYPE] == 'text/html'\n        Rack::Response.new(Array.wrap(message), Rack::Utils.status_code(status), Grape::Util::Header.new.merge(headers))\n      end\n\n      def format_message(message, backtrace, original_exception = nil)\n        format = env[Grape::Env::API_FORMAT] || options[:format]\n        formatter = Grape::ErrorFormatter.formatter_for(format, options[:error_formatters], options[:default_error_formatter])\n        return formatter.call(message, backtrace, options, env, original_exception) if formatter\n\n        throw :error,\n              status: 406,\n              message: \"The requested format '#{format}' is not supported.\",\n              backtrace: backtrace,\n              original_exception: original_exception\n      end\n\n      def find_handler(klass)\n        rescue_handler_for_base_only_class(klass) ||\n          rescue_handler_for_class_or_its_ancestor(klass) ||\n          rescue_handler_for_grape_exception(klass) ||\n          rescue_handler_for_any_class(klass) ||\n          raise\n      end\n\n      def error_response(error = {})\n        status = error[:status] || options[:default_status]\n        env[Grape::Env::API_ENDPOINT].status(status) # error! may not have been called\n        message = error[:message] || options[:default_message]\n        headers = { Rack::CONTENT_TYPE => content_type }.tap do |h|\n          h.merge!(error[:headers]) if error[:headers].is_a?(Hash)\n        end\n        backtrace = error[:backtrace] || error[:original_exception]&.backtrace || []\n        original_exception = error.is_a?(Exception) ? error : error[:original_exception]\n        rack_response(status, headers, format_message(message, backtrace, original_exception))\n      end\n\n      def default_rescue_handler(exception)\n        error_response(message: exception.message, backtrace: exception.backtrace, original_exception: exception)\n      end\n\n      def rescue_handler_for_base_only_class(klass)\n        error, handler = options[:base_only_rescue_handlers]&.find { |err, _handler| klass == err }\n\n        return unless error\n\n        handler || method(:default_rescue_handler)\n      end\n\n      def rescue_handler_for_class_or_its_ancestor(klass)\n        error, handler = options[:rescue_handlers]&.find { |err, _handler| klass <= err }\n\n        return unless error\n\n        handler || method(:default_rescue_handler)\n      end\n\n      def rescue_handler_for_grape_exception(klass)\n        return unless klass <= Grape::Exceptions::Base\n        return method(:error_response) if klass == Grape::Exceptions::InvalidVersionHeader\n        return unless options[:rescue_grape_exceptions] || !options[:rescue_all]\n\n        options[:grape_exceptions_rescue_handler] || method(:error_response)\n      end\n\n      def rescue_handler_for_any_class(klass)\n        return unless klass <= StandardError\n        return unless options[:rescue_all] || options[:rescue_grape_exceptions]\n\n        options[:all_rescue_handler] || method(:default_rescue_handler)\n      end\n\n      def run_rescue_handler(handler, error, endpoint)\n        handler = endpoint.public_method(handler) if handler.instance_of?(Symbol)\n        response = catch(:error) do\n          handler.arity.zero? ? endpoint.instance_exec(&handler) : endpoint.instance_exec(error, &handler)\n        end\n\n        if error?(response)\n          error_response(response)\n        elsif response.is_a?(Rack::Response)\n          response\n        else\n          run_rescue_handler(method(:default_rescue_handler), Grape::Exceptions::InvalidResponse.new, endpoint)\n        end\n      end\n\n      def error!(message, status = options[:default_status], headers = {}, backtrace = [], original_exception = nil)\n        env[Grape::Env::API_ENDPOINT].status(status) # not error! inside route\n        rack_response(\n          status, headers.reverse_merge(Rack::CONTENT_TYPE => content_type),\n          format_message(message, backtrace, original_exception)\n        )\n      end\n\n      def error?(response)\n        return false unless response.is_a?(Hash)\n\n        response.key?(:message) && response.key?(:status) && response.key?(:headers)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/middleware/filter.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Middleware\n    # This is a simple middleware for adding before and after filters\n    # to Grape APIs. It is used like so:\n    #\n    #     use Grape::Middleware::Filter, before: -> { do_something }, after: -> { do_something }\n    class Filter < Base\n      def before\n        app.instance_eval(&options[:before]) if options[:before]\n      end\n\n      def after\n        app.instance_eval(&options[:after]) if options[:after]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/middleware/formatter.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Middleware\n    class Formatter < Base\n      DEFAULT_OPTIONS = {\n        default_format: :txt\n      }.freeze\n\n      ALL_MEDIA_TYPES = '*/*'\n\n      def before\n        negotiate_content_type\n        read_body_input\n      end\n\n      def after\n        return unless @app_response\n\n        status, headers, bodies = *@app_response\n\n        if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status)\n          [status, headers, []]\n        else\n          build_formatted_response(status, headers, bodies)\n        end\n      end\n\n      private\n\n      def build_formatted_response(status, headers, bodies)\n        headers = ensure_content_type(headers)\n\n        if bodies.is_a?(Grape::ServeStream::StreamResponse)\n          Grape::ServeStream::SendfileResponse.new([], status, headers) do |resp|\n            resp.body = bodies.stream\n          end\n        else\n          # Allow content-type to be explicitly overwritten\n          formatter = fetch_formatter(headers, options)\n          bodymap = ActiveSupport::Notifications.instrument('format_response.grape', formatter: formatter, env: env) do\n            bodies.collect { |body| formatter.call(body, env) }\n          end\n          Rack::Response.new(bodymap, status, headers)\n        end\n      rescue Grape::Exceptions::InvalidFormatter => e\n        throw :error, status: 500, message: e.message, backtrace: e.backtrace, original_exception: e\n      end\n\n      def fetch_formatter(headers, options)\n        api_format = env.fetch(Grape::Env::API_FORMAT) { mime_types[headers[Rack::CONTENT_TYPE]] }\n        Grape::Formatter.formatter_for(api_format, options[:formatters])\n      end\n\n      # Set the content type header for the API format if it is not already present.\n      #\n      # @param headers [Hash]\n      # @return [Hash]\n      def ensure_content_type(headers)\n        if headers[Rack::CONTENT_TYPE]\n          headers\n        else\n          headers.merge(Rack::CONTENT_TYPE => content_type_for(env[Grape::Env::API_FORMAT]))\n        end\n      end\n\n      def read_body_input\n        input = rack_request.body # reads RACK_INPUT\n        return if input.nil?\n        return unless read_body_input?\n\n        rewind = input.respond_to?(:rewind)\n\n        input.rewind if rewind\n        body = env[Grape::Env::API_REQUEST_INPUT] = input.read\n        begin\n          read_rack_input(body)\n        ensure\n          input.rewind if rewind\n        end\n      end\n\n      def read_rack_input(body)\n        return if body.empty?\n\n        media_type = rack_request.media_type\n        fmt = media_type ? mime_types[media_type] : options[:default_format]\n\n        throw :error, status: 415, message: \"The provided content-type '#{media_type}' is not supported.\" unless content_type_for(fmt)\n        parser = Grape::Parser.parser_for fmt, options[:parsers]\n        if parser\n          begin\n            body = (env[Grape::Env::API_REQUEST_BODY] = parser.call(body, env))\n            if body.is_a?(Hash)\n              env[Rack::RACK_REQUEST_FORM_HASH] = if env.key?(Rack::RACK_REQUEST_FORM_HASH)\n                                                    env[Rack::RACK_REQUEST_FORM_HASH].merge(body)\n                                                  else\n                                                    body\n                                                  end\n              env[Rack::RACK_REQUEST_FORM_INPUT] = env[Rack::RACK_INPUT]\n            end\n          rescue Grape::Exceptions::Base => e\n            raise e\n          rescue StandardError => e\n            throw :error, status: 400, message: e.message, backtrace: e.backtrace, original_exception: e\n          end\n        else\n          env[Grape::Env::API_REQUEST_BODY] = body\n        end\n      end\n\n      # this middleware will not try to format the following content-types since Rack already handles them\n      # when calling Rack's `params` function\n      # - application/x-www-form-urlencoded\n      # - multipart/form-data\n      # - multipart/related\n      # - multipart/mixed\n      def read_body_input?\n        (rack_request.post? || rack_request.put? || rack_request.patch? || rack_request.delete?) &&\n          !(rack_request.form_data? && rack_request.content_type) &&\n          !rack_request.parseable_data? &&\n          (rack_request.content_length.to_i.positive? || rack_request.env['HTTP_TRANSFER_ENCODING'] == 'chunked')\n      end\n\n      def negotiate_content_type\n        fmt = format_from_extension || query_params['format'] || options[:format] || format_from_header || options[:default_format]\n        if content_type_for(fmt)\n          env[Grape::Env::API_FORMAT] = fmt.to_sym\n        else\n          throw :error, status: 406, message: \"The requested format '#{fmt}' is not supported.\"\n        end\n      end\n\n      def format_from_extension\n        request_path = try_scrub(rack_request.path)\n        dot_pos = request_path.rindex('.')\n        return unless dot_pos\n\n        extension = request_path[(dot_pos + 1)..]\n        extension if content_type_for(extension)\n      end\n\n      def format_from_header\n        accept_header = try_scrub(env['HTTP_ACCEPT'])\n        return if accept_header.blank? || accept_header == ALL_MEDIA_TYPES\n\n        media_type = Rack::Utils.best_q_match(accept_header, mime_types.keys)\n        mime_types[media_type] if media_type\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/middleware/globals.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Middleware\n    class Globals < Base\n      def before\n        request = Grape::Request.new(@env, build_params_with: @options[:build_params_with])\n        @env[Grape::Env::GRAPE_REQUEST] = request\n        @env[Grape::Env::GRAPE_REQUEST_HEADERS] = request.headers\n        @env[Grape::Env::GRAPE_REQUEST_PARAMS] = request.params if @env[Rack::RACK_INPUT]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/middleware/stack.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Middleware\n    # Class to handle the stack of middlewares based on ActionDispatch::MiddlewareStack\n    # It allows to insert and insert after\n    class Stack\n      extend Forwardable\n\n      class Middleware\n        attr_reader :args, :block, :klass\n\n        def initialize(klass, args, block)\n          @klass = klass\n          @args = args\n          @block = block\n        end\n\n        def name\n          klass.name\n        end\n\n        def ==(other)\n          case other\n          when Middleware\n            klass == other.klass\n          when Class\n            klass == other || (name.nil? && klass.superclass == other)\n          end\n        end\n\n        def inspect\n          klass.to_s\n        end\n\n        def build(builder)\n          # we need to force the ruby2_keywords_hash for middlewares that initialize contains keywords\n          # like ActionDispatch::RequestId since middleware arguments are serialized\n          # https://rubyapi.org/3.4/o/hash#method-c-ruby2_keywords_hash\n          args[-1] = Hash.ruby2_keywords_hash(args[-1]) if args.last.is_a?(Hash) && Hash.respond_to?(:ruby2_keywords_hash)\n          builder.use(klass, *args, &block)\n        end\n      end\n\n      include Enumerable\n\n      attr_accessor :middlewares, :others\n\n      def_delegators :middlewares, :each, :size, :last, :[]\n\n      def initialize\n        @middlewares = []\n        @others = []\n      end\n\n      def insert(index, klass, *args, &block)\n        index = assert_index(index, :before)\n        middlewares.insert(index, self.class::Middleware.new(klass, args, block))\n      end\n\n      alias insert_before insert\n\n      def insert_after(index, ...)\n        index = assert_index(index, :after)\n        insert(index + 1, ...)\n      end\n\n      def use(klass, *args, &block)\n        middleware = self.class::Middleware.new(klass, args, block)\n        middlewares.push(middleware)\n      end\n\n      def merge_with(middleware_specs)\n        middleware_specs.each do |operation, klass, *args|\n          if args.last.is_a?(Proc)\n            last_proc = args.pop\n            public_send(operation, klass, *args, &last_proc)\n          else\n            public_send(operation, klass, *args)\n          end\n        end\n      end\n\n      # @return [Rack::Builder] the builder object with our middlewares applied\n      def build\n        Rack::Builder.new.tap do |builder|\n          others.shift(others.size).each { |m| merge_with(m) }\n          middlewares.each do |m|\n            m.build(builder)\n          end\n        end\n      end\n\n      # @description Add middlewares with :use operation to the stack. Store others with :insert_* operation for later\n      # @param [Array] other_specs An array of middleware specifications (e.g. [[:use, klass], [:insert_before, *args]])\n      def concat(other_specs)\n        use, not_use = other_specs.partition { |o| o.first == :use }\n        others << not_use\n        merge_with(use)\n      end\n\n      protected\n\n      def assert_index(index, where)\n        i = index.is_a?(Integer) ? index : middlewares.index(index)\n        i || raise(\"No such middleware to insert #{where}: #{index.inspect}\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/middleware/versioner/accept_version_header.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Middleware\n    module Versioner\n      # This middleware sets various version related rack environment variables\n      # based on the HTTP Accept-Version header\n      #\n      # Example: For request header\n      #    Accept-Version: v1\n      #\n      # The following rack env variables are set:\n      #\n      #    env['api.version']  => 'v1'\n      #\n      # If version does not match this route, then a 406 is raised with\n      # X-Cascade header to alert Grape::Router to attempt the next matched\n      # route.\n      class AcceptVersionHeader < Base\n        def before\n          potential_version = try_scrub(env['HTTP_ACCEPT_VERSION'])\n          not_acceptable!('Accept-Version header must be set.') if strict && potential_version.blank?\n\n          return if potential_version.blank?\n\n          not_acceptable!('The requested version is not supported.') unless potential_version_match?(potential_version)\n          env[Grape::Env::API_VERSION] = potential_version\n        end\n\n        private\n\n        def not_acceptable!(message)\n          throw :error, status: 406, headers: error_headers, message: message\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/middleware/versioner/base.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Middleware\n    module Versioner\n      class Base < Grape::Middleware::Base\n        DEFAULT_OPTIONS = {\n          pattern: /.*/i,\n          prefix: nil,\n          mount_path: nil,\n          version_options: {\n            strict: false,\n            cascade: true,\n            parameter: 'apiver',\n            vendor: nil\n          }.freeze\n        }.freeze\n\n        CASCADE_PASS_HEADER = { 'X-Cascade' => 'pass' }.freeze\n\n        DEFAULT_OPTIONS.each_key do |key|\n          define_method key do\n            options[key]\n          end\n        end\n\n        DEFAULT_OPTIONS[:version_options].each_key do |key|\n          define_method key do\n            options[:version_options][key]\n          end\n        end\n\n        def self.inherited(klass)\n          super\n          Versioner.register(klass)\n        end\n\n        attr_reader :error_headers, :versions\n\n        def initialize(app, **options)\n          super\n          @error_headers = cascade ? CASCADE_PASS_HEADER : {}\n          @versions = options[:versions]&.map(&:to_s) # making sure versions are strings to ease potential match\n        end\n\n        def potential_version_match?(potential_version)\n          versions.blank? || versions.include?(potential_version)\n        end\n\n        def version_not_found!\n          throw :error, status: 404, message: '404 API Version Not Found', headers: CASCADE_PASS_HEADER\n        end\n\n        private\n\n        def available_media_types\n          @available_media_types ||= begin\n            media_types = []\n            base_media_type = \"application/vnd.#{vendor}\"\n            content_types.each_key do |extension|\n              versions&.reverse_each do |version|\n                media_types << \"#{base_media_type}-#{version}+#{extension}\"\n                media_types << \"#{base_media_type}-#{version}\"\n              end\n              media_types << \"#{base_media_type}+#{extension}\"\n            end\n\n            media_types << base_media_type\n            media_types.concat(content_types.values.flatten)\n            media_types\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/middleware/versioner/header.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Middleware\n    module Versioner\n      # This middleware sets various version related rack environment variables\n      # based on the HTTP Accept header with the pattern:\n      # application/vnd.:vendor-:version+:format\n      #\n      # Example: For request header\n      #    Accept: application/vnd.mycompany.a-cool-resource-v1+json\n      #\n      # The following rack env variables are set:\n      #\n      #    env['api.type']    => 'application'\n      #    env['api.subtype'] => 'vnd.mycompany.a-cool-resource-v1+json'\n      #    env['api.vendor]   => 'mycompany.a-cool-resource'\n      #    env['api.version]  => 'v1'\n      #    env['api.format]   => 'json'\n      #\n      # If version does not match this route, then a 406 is raised with\n      # X-Cascade header to alert Grape::Router to attempt the next matched\n      # route.\n      class Header < Base\n        def before\n          match_best_quality_media_type! do |media_type|\n            env.update(\n              Grape::Env::API_TYPE => media_type.type,\n              Grape::Env::API_SUBTYPE => media_type.subtype,\n              Grape::Env::API_VENDOR => media_type.vendor,\n              Grape::Env::API_VERSION => media_type.version,\n              Grape::Env::API_FORMAT => media_type.format\n            )\n          end\n        end\n\n        private\n\n        def match_best_quality_media_type!\n          return unless vendor\n\n          strict_header_checks!\n          media_type = Grape::Util::MediaType.best_quality(accept_header, available_media_types)\n          if media_type\n            yield media_type\n          else\n            fail!\n          end\n        end\n\n        def accept_header\n          env['HTTP_ACCEPT']\n        end\n\n        def strict_header_checks!\n          return unless strict\n\n          accept_header_check!\n          version_and_vendor_check!\n        end\n\n        def accept_header_check!\n          return if accept_header.present?\n\n          invalid_accept_header!('Accept header must be set.')\n        end\n\n        def version_and_vendor_check!\n          return if versions.blank? || version_and_vendor?\n\n          invalid_accept_header!('API vendor or version not found.')\n        end\n\n        def q_values_mime_types\n          @q_values_mime_types ||= Rack::Utils.q_values(accept_header).map(&:first)\n        end\n\n        def version_and_vendor?\n          q_values_mime_types.any? { |mime_type| Grape::Util::MediaType.match?(mime_type) }\n        end\n\n        def invalid_accept_header!(message)\n          raise Grape::Exceptions::InvalidAcceptHeader.new(message, error_headers)\n        end\n\n        def invalid_version_header!(message)\n          raise Grape::Exceptions::InvalidVersionHeader.new(message, error_headers)\n        end\n\n        def fail!\n          return if env[Grape::Env::GRAPE_ALLOWED_METHODS].present?\n\n          media_types = q_values_mime_types.map { |mime_type| Grape::Util::MediaType.parse(mime_type) }\n          vendor_not_found!(media_types) || version_not_found!(media_types)\n        end\n\n        def vendor_not_found!(media_types)\n          return unless media_types.all? { |media_type| media_type&.vendor && media_type.vendor != vendor }\n\n          invalid_accept_header!('API vendor not found.')\n        end\n\n        def version_not_found!(media_types)\n          return unless media_types.all? { |media_type| media_type&.version && versions && !versions.include?(media_type.version) }\n\n          invalid_version_header!('API version not found.')\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/middleware/versioner/param.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Middleware\n    module Versioner\n      # This middleware sets various version related rack environment variables\n      # based on the request parameters and removes that parameter from the\n      # request parameters for subsequent middleware and API.\n      # If the version substring does not match any potential initialized\n      # versions, a 404 error is thrown.\n      # If the version substring is not passed the version (highest mounted)\n      # version will be used.\n      #\n      # Example: For a uri path\n      #   /resource?apiver=v1\n      #\n      # The following rack env variables are set and path is rewritten to\n      # '/resource':\n      #\n      #   env['api.version'] => 'v1'\n      class Param < Base\n        def before\n          potential_version = query_params[parameter]\n          return if potential_version.blank?\n\n          version_not_found! unless potential_version_match?(potential_version)\n          env[Grape::Env::API_VERSION] = env[Rack::RACK_REQUEST_QUERY_HASH].delete(parameter)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/middleware/versioner/path.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Middleware\n    module Versioner\n      # This middleware sets various version related rack environment variables\n      # based on the uri path and removes the version substring from the uri\n      # path. If the version substring does not match any potential initialized\n      # versions, a 404 error is thrown.\n      #\n      # Example: For a uri path\n      #   /v1/resource\n      #\n      # The following rack env variables are set and path is rewritten to\n      # '/resource':\n      #\n      #   env['api.version'] => 'v1'\n      #\n      class Path < Base\n        def before\n          path_info = Grape::Router.normalize_path(env[Rack::PATH_INFO])\n          return if path_info == '/'\n\n          [mount_path, Grape::Router.normalize_path(prefix)].each do |path|\n            path_info = path_info.delete_prefix(path) if path.present? && path != '/' && path_info.start_with?(path)\n          end\n\n          slash_position = path_info.index('/', 1) # omit the first one\n          return unless slash_position\n\n          potential_version = path_info[1..(slash_position - 1)]\n          return unless potential_version.match?(pattern)\n\n          version_not_found! unless potential_version_match?(potential_version)\n          env[Grape::Env::API_VERSION] = potential_version\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/middleware/versioner.rb",
    "content": "# frozen_string_literal: true\n\n# Versioners set env['api.version'] when a version is defined on an API and\n# on the requests. The current methods for determining version are:\n#\n#   :header - version from HTTP Accept header.\n#   :accept_version_header - version from HTTP Accept-Version header\n#   :path   - version from uri. e.g. /v1/resource\n#   :param  - version from uri query string, e.g. /v1/resource?apiver=v1\n# See individual classes for details.\nmodule Grape\n  module Middleware\n    module Versioner\n      extend Grape::Util::Registry\n\n      module_function\n\n      # @param strategy [Symbol] :path, :header, :accept_version_header or :param\n      # @return a middleware class based on strategy\n      def using(strategy)\n        raise Grape::Exceptions::InvalidVersionerOption, strategy unless registry.key?(strategy)\n\n        registry[strategy]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/namespace.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  # A container for endpoints or other namespaces, which allows for both\n  # logical grouping of endpoints as well as sharing common configuration.\n  # May also be referred to as group, segment, or resource.\n  class Namespace\n    attr_reader :space, :requirements, :options\n\n    # @param space [String] the name of this namespace\n    # @param options [Hash] options hash\n    # @option options :requirements [Hash] param-regex pairs, all of which must\n    #   be met by a request's params for all endpoints in this namespace, or\n    #   validation will fail and return a 422.\n    def initialize(space, requirements: nil, **options)\n      @space = space.to_s\n      @requirements = requirements\n      @options = options\n    end\n\n    # (see ::joined_space_path)\n    def self.joined_space(settings)\n      settings&.map(&:space)\n    end\n\n    def eql?(other)\n      other.class == self.class &&\n        other.space == space &&\n        other.requirements == requirements &&\n        other.options == options\n    end\n    alias == eql?\n\n    def hash\n      [self.class, space, requirements, options].hash\n    end\n\n    # Join the namespaces from a list of settings to create a path prefix.\n    # @param settings [Array] list of Grape::Util::InheritableSettings.\n    def self.joined_space_path(settings)\n      JoinedSpaceCache[joined_space(settings)]\n    end\n\n    class JoinedSpaceCache < Grape::Util::Cache\n      def initialize\n        super\n        @cache = Hash.new do |h, joined_space|\n          h[joined_space] = Grape::Router.normalize_path(joined_space.join('/'))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/params_builder/base.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module ParamsBuilder\n    class Base\n      class << self\n        def call(_params)\n          raise NotImplementedError\n        end\n\n        private\n\n        def inherited(klass)\n          super\n          ParamsBuilder.register(klass)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/params_builder/hash.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module ParamsBuilder\n    class Hash < Base\n      def self.call(params)\n        params.deep_symbolize_keys\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/params_builder/hash_with_indifferent_access.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module ParamsBuilder\n    class HashWithIndifferentAccess < Base\n      def self.call(params)\n        params.with_indifferent_access\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/params_builder/hashie_mash.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module ParamsBuilder\n    class HashieMash < Base\n      def self.call(params)\n        ::Hashie::Mash.new(params)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/params_builder.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module ParamsBuilder\n    extend Grape::Util::Registry\n\n    module_function\n\n    def params_builder_for(short_name)\n      raise Grape::Exceptions::UnknownParamsBuilder, short_name unless registry.key?(short_name)\n\n      registry[short_name]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/parser/base.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Parser\n    class Base\n      def self.call(_object, _env)\n        raise NotImplementedError\n      end\n\n      def self.inherited(klass)\n        super\n        Parser.register(klass)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/parser/json.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Parser\n    class Json < Base\n      def self.call(object, _env)\n        ::Grape::Json.load(object)\n      rescue ::Grape::Json::ParseError\n        # handle JSON parsing errors via the rescue handlers or provide error message\n        raise Grape::Exceptions::InvalidMessageBody.new('application/json')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/parser/xml.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Parser\n    class Xml < Base\n      def self.call(object, _env)\n        ::Grape::Xml.parse(object)\n      rescue ::Grape::Xml::ParseError\n        # handle XML parsing errors via the rescue handlers or provide error message\n        raise Grape::Exceptions::InvalidMessageBody.new('application/xml')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/parser.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Parser\n    extend Grape::Util::Registry\n\n    module_function\n\n    def parser_for(format, parsers = nil)\n      return parsers[format] if parsers&.key?(format)\n\n      registry[format]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/path.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  # Represents a path to an endpoint.\n  class Path\n    DEFAULT_FORMAT_SEGMENT = '(/.:format)'\n    NO_VERSIONING_WITH_VALID_PATH_FORMAT_SEGMENT = '(.:format)'\n    VERSION_SEGMENT = ':version'\n\n    attr_reader :origin, :suffix\n\n    def initialize(raw_path, raw_namespace, settings)\n      @origin = PartsCache[build_parts(raw_path, raw_namespace, settings)]\n      @suffix = build_suffix(raw_path, raw_namespace, settings)\n    end\n\n    def to_s\n      \"#{origin}#{suffix}\"\n    end\n\n    private\n\n    def build_suffix(raw_path, raw_namespace, settings)\n      if uses_specific_format?(settings)\n        \"(.#{settings[:format]})\"\n      elsif !uses_path_versioning?(settings) || (valid_part?(raw_namespace) || valid_part?(raw_path))\n        NO_VERSIONING_WITH_VALID_PATH_FORMAT_SEGMENT\n      else\n        DEFAULT_FORMAT_SEGMENT\n      end\n    end\n\n    def build_parts(raw_path, raw_namespace, settings)\n      [].tap do |parts|\n        add_part(parts, settings[:mount_path])\n        add_part(parts, settings[:root_prefix])\n        parts << VERSION_SEGMENT if uses_path_versioning?(settings)\n        add_part(parts, raw_namespace)\n        add_part(parts, raw_path)\n      end\n    end\n\n    def add_part(parts, value)\n      parts << value if value && not_slash?(value)\n    end\n\n    def not_slash?(value)\n      value != '/'\n    end\n\n    def uses_specific_format?(settings)\n      return false unless settings.key?(:format) && settings.key?(:content_types)\n\n      settings[:format] && Array(settings[:content_types]).size == 1\n    end\n\n    def uses_path_versioning?(settings)\n      return false unless settings.key?(:version) && settings[:version_options]&.key?(:using)\n\n      settings[:version] && settings[:version_options][:using] == :path\n    end\n\n    def valid_part?(part)\n      part&.match?(/^\\S/) && not_slash?(part)\n    end\n\n    class PartsCache < Grape::Util::Cache\n      def initialize\n        super\n        @cache = Hash.new do |h, parts|\n          h[parts] = Grape::Router.normalize_path(parts.join('/'))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/presenters/presenter.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Presenters\n    class Presenter\n      def self.represent(object, **_options)\n        object\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/railtie.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  class Railtie < ::Rails::Railtie\n    initializer 'grape.deprecator' do |app|\n      app.deprecators[:grape] = Grape.deprecator\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/request.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  class Request < Rack::Request\n    # Based on rack 3 KNOWN_HEADERS\n    # https://github.com/rack/rack/blob/4f15e7b814922af79605be4b02c5b7c3044ba206/lib/rack/headers.rb#L10\n\n    KNOWN_HEADERS = %w[\n      Accept\n      Accept-CH\n      Accept-Encoding\n      Accept-Language\n      Accept-Patch\n      Accept-Ranges\n      Accept-Version\n      Access-Control-Allow-Credentials\n      Access-Control-Allow-Headers\n      Access-Control-Allow-Methods\n      Access-Control-Allow-Origin\n      Access-Control-Expose-Headers\n      Access-Control-Max-Age\n      Age\n      Allow\n      Alt-Svc\n      Authorization\n      Cache-Control\n      Client-Ip\n      Connection\n      Content-Disposition\n      Content-Encoding\n      Content-Language\n      Content-Length\n      Content-Location\n      Content-MD5\n      Content-Range\n      Content-Security-Policy\n      Content-Security-Policy-Report-Only\n      Content-Type\n      Cookie\n      Date\n      Delta-Base\n      Dnt\n      ETag\n      Expect-CT\n      Expires\n      Feature-Policy\n      Forwarded\n      Host\n      If-Modified-Since\n      If-None-Match\n      IM\n      Last-Modified\n      Link\n      Location\n      NEL\n      P3P\n      Permissions-Policy\n      Pragma\n      Preference-Applied\n      Proxy-Authenticate\n      Public-Key-Pins\n      Range\n      Referer\n      Referrer-Policy\n      Refresh\n      Report-To\n      Retry-After\n      Sec-Fetch-Dest\n      Sec-Fetch-Mode\n      Sec-Fetch-Site\n      Sec-Fetch-User\n      Server\n      Set-Cookie\n      Status\n      Strict-Transport-Security\n      Timing-Allow-Origin\n      Tk\n      Trailer\n      Transfer-Encoding\n      Upgrade\n      Upgrade-Insecure-Requests\n      User-Agent\n      Vary\n      Version\n      Via\n      Warning\n      WWW-Authenticate\n      X-Accel-Buffering\n      X-Accel-Charset\n      X-Accel-Expires\n      X-Accel-Limit-Rate\n      X-Accel-Mapping\n      X-Accel-Redirect\n      X-Access-Token\n      X-Auth-Request-Access-Token\n      X-Auth-Request-Email\n      X-Auth-Request-Groups\n      X-Auth-Request-Preferred-Username\n      X-Auth-Request-Redirect\n      X-Auth-Request-Token\n      X-Auth-Request-User\n      X-Cascade\n      X-Client-Ip\n      X-Content-Duration\n      X-Content-Security-Policy\n      X-Content-Type-Options\n      X-Correlation-Id\n      X-Download-Options\n      X-Forwarded-Access-Token\n      X-Forwarded-Email\n      X-Forwarded-For\n      X-Forwarded-Groups\n      X-Forwarded-Host\n      X-Forwarded-Port\n      X-Forwarded-Preferred-Username\n      X-Forwarded-Proto\n      X-Forwarded-Scheme\n      X-Forwarded-Ssl\n      X-Forwarded-Uri\n      X-Forwarded-User\n      X-Frame-Options\n      X-HTTP-Method-Override\n      X-Permitted-Cross-Domain-Policies\n      X-Powered-By\n      X-Real-IP\n      X-Redirect-By\n      X-Request-Id\n      X-Requested-With\n      X-Runtime\n      X-Sendfile\n      X-Sendfile-Type\n      X-UA-Compatible\n      X-WebKit-CS\n      X-XSS-Protection\n    ].each_with_object({}) do |header, response|\n      response[\"HTTP_#{header.upcase.tr('-', '_')}\"] = header\n    end.freeze\n\n    alias rack_params params\n    alias rack_cookies cookies\n\n    def initialize(env, build_params_with: nil)\n      super(env)\n      @params_builder = Grape::ParamsBuilder.params_builder_for(build_params_with || Grape.config.param_builder)\n    end\n\n    def params\n      @params ||= make_params\n    end\n\n    def headers\n      @headers ||= build_headers\n    end\n\n    def cookies\n      @cookies ||= Grape::Cookies.new(-> { rack_cookies })\n    end\n\n    # needs to be public until extensions param_builder are removed\n    def grape_routing_args\n      # preserve version from query string parameters\n      env[Grape::Env::GRAPE_ROUTING_ARGS]&.except(:version, :route_info) || {}\n    end\n\n    private\n\n    def make_params\n      @params_builder.call(rack_params).deep_merge!(grape_routing_args)\n    rescue EOFError\n      raise Grape::Exceptions::EmptyMessageBody.new(content_type)\n    rescue Rack::Multipart::MultipartPartLimitError, Rack::Multipart::MultipartTotalPartLimitError\n      raise Grape::Exceptions::TooManyMultipartFiles.new(Rack::Utils.multipart_part_limit)\n    rescue Rack::QueryParser::ParamsTooDeepError\n      raise Grape::Exceptions::TooDeepParameters.new(Rack::Utils.param_depth_limit)\n    rescue Rack::Utils::ParameterTypeError\n      raise Grape::Exceptions::ConflictingTypes\n    rescue Rack::Utils::InvalidParameterError\n      raise Grape::Exceptions::InvalidParameters\n    end\n\n    def build_headers\n      each_header.with_object(Grape::Util::Header.new) do |(k, v), headers|\n        next unless k.start_with? 'HTTP_'\n\n        transformed_header = KNOWN_HEADERS.fetch(k) { -k[5..].tr('_', '-').downcase }\n        headers[transformed_header] = v\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/router/base_route.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  class Router\n    class BaseRoute\n      extend Forwardable\n\n      delegate_missing_to :@options\n\n      attr_reader :options, :pattern\n\n      def_delegators :@pattern, :path, :origin\n      def_delegators :@options, :description, :version, :requirements, :prefix, :anchor, :settings, :forward_match, *Grape::Util::ApiDescription::DSL_METHODS\n\n      def initialize(pattern, options = {})\n        @pattern = pattern\n        @options = options.is_a?(ActiveSupport::OrderedOptions) ? options : ActiveSupport::OrderedOptions.new.update(options)\n      end\n\n      # see https://github.com/ruby-grape/grape/issues/1348\n      def namespace\n        @namespace ||= @options[:namespace]\n      end\n\n      def regexp_capture_index\n        @regexp_capture_index ||= CaptureIndexCache[@index]\n      end\n\n      def pattern_regexp\n        @pattern.to_regexp\n      end\n\n      def to_regexp(index)\n        @index = index\n        Regexp.new(\"(?<#{regexp_capture_index}>#{pattern_regexp})\")\n      end\n\n      class CaptureIndexCache < Grape::Util::Cache\n        def initialize\n          super\n          @cache = Hash.new do |h, index|\n            h[index] = \"_#{index}\"\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/router/greedy_route.rb",
    "content": "# frozen_string_literal: true\n\n# Act like a Grape::Router::Route but for greedy_match\n# see @neutral_map\n\nmodule Grape\n  class Router\n    class GreedyRoute < BaseRoute\n      extend Forwardable\n\n      def_delegators :@endpoint, :call\n\n      attr_reader :endpoint, :allow_header\n\n      def initialize(pattern, endpoint:, allow_header:)\n        super(pattern)\n        @endpoint = endpoint\n        @allow_header = allow_header\n      end\n\n      def params(_input = nil)\n        nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/router/pattern.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  class Router\n    class Pattern\n      extend Forwardable\n\n      DEFAULT_CAPTURES = %w[format version].freeze\n\n      attr_reader :origin, :path, :pattern, :to_regexp\n\n      def_delegators :pattern, :params\n      def_delegators :to_regexp, :===\n      alias match? ===\n\n      def initialize(origin:, suffix:, anchor:, params:, format:, version:, requirements:)\n        @origin = origin\n        @path = PatternCache[[build_path_from_pattern(@origin, anchor), suffix]]\n        @pattern = Mustermann::Grape.new(@path, uri_decode: true, params: params, capture: extract_capture(format, version, requirements))\n        @to_regexp = @pattern.to_regexp\n      end\n\n      def captures_default\n        to_regexp.names\n                 .delete_if { |n| DEFAULT_CAPTURES.include?(n) }\n                 .to_h { |k| [k, ''] }\n      end\n\n      private\n\n      def extract_capture(format, version, requirements)\n        capture = {}\n        capture[:format] = map_str(format) if format.present?\n        capture[:version] = map_str(version) if version.present?\n\n        return capture if requirements.blank?\n\n        requirements.merge(capture)\n      end\n\n      def build_path_from_pattern(pattern, anchor)\n        if pattern.end_with?('*path')\n          pattern.dup.insert(pattern.rindex('/') + 1, '?')\n        elsif anchor\n          pattern\n        elsif pattern.end_with?('/')\n          \"#{pattern}?*path\"\n        else\n          \"#{pattern}/?*path\"\n        end\n      end\n\n      def map_str(value)\n        Array.wrap(value).map(&:to_s)\n      end\n\n      class PatternCache < Grape::Util::Cache\n        def initialize\n          super\n          @cache = Hash.new do |h, (pattern, suffix)|\n            h[[pattern, suffix]] = -\"#{pattern}#{suffix}\"\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/router/route.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  class Router\n    class Route < BaseRoute\n      extend Forwardable\n\n      FORWARD_MATCH_METHOD = ->(input, pattern) { input.start_with?(pattern.origin) }\n      NON_FORWARD_MATCH_METHOD = ->(input, pattern) { pattern.match?(input) }\n\n      attr_reader :app, :request_method, :index\n\n      def_delegators :@app, :call\n\n      def initialize(endpoint, method, pattern, options)\n        super(pattern, options)\n        @app = endpoint\n        @request_method = upcase_method(method)\n        @match_function = options[:forward_match] ? FORWARD_MATCH_METHOD : NON_FORWARD_MATCH_METHOD\n      end\n\n      def convert_to_head_request!\n        @request_method = Rack::HEAD\n      end\n\n      def apply(app)\n        @app = app\n        self\n      end\n\n      def match?(input)\n        return false if input.blank?\n\n        @match_function.call(input, pattern)\n      end\n\n      def params(input = nil)\n        return params_without_input if input.blank?\n\n        parsed = pattern.params(input)\n        return unless parsed\n\n        parsed.compact.symbolize_keys\n      end\n\n      private\n\n      def params_without_input\n        @params_without_input ||= pattern.captures_default.merge(options[:params])\n      end\n\n      def upcase_method(method)\n        method_s = method.to_s\n        Grape::HTTP_SUPPORTED_METHODS.detect { |m| m.casecmp(method_s).zero? } || method_s.upcase\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/router.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  class Router\n    # Taken from Rails\n    #     normalize_path(\"/foo\")  # => \"/foo\"\n    #     normalize_path(\"/foo/\") # => \"/foo\"\n    #     normalize_path(\"foo\")   # => \"/foo\"\n    #     normalize_path(\"\")      # => \"/\"\n    #     normalize_path(\"/%ab\")  # => \"/%AB\"\n    # https://github.com/rails/rails/blob/00cc4ff0259c0185fe08baadaa40e63ea2534f6e/actionpack/lib/action_dispatch/journey/router/utils.rb#L19\n    def self.normalize_path(path)\n      return '/' unless path\n      return path if path == '/'\n\n      # Fast path for the overwhelming majority of paths that don't need to be normalized\n      return path if path.start_with?('/') && !(path.end_with?('/') || path.match?(%r{%|//}))\n\n      # Slow path\n      encoding = path.encoding\n      path = \"/#{path}\"\n      path.squeeze!('/')\n\n      unless path == '/'\n        path.delete_suffix!('/')\n        path.gsub!(/(%[a-f0-9]{2})/) { ::Regexp.last_match(1).upcase }\n      end\n\n      path.force_encoding(encoding)\n    end\n\n    def initialize\n      @neutral_map = []\n      @neutral_regexes = []\n      @map = Hash.new { |hash, key| hash[key] = [] }\n      @optimized_map = Hash.new { |hash, key| hash[key] = // }\n    end\n\n    def compile!\n      return if @compiled\n\n      @union = Regexp.union(@neutral_regexes)\n      @neutral_regexes = nil\n      (Grape::HTTP_SUPPORTED_METHODS + ['*']).each do |method|\n        next unless @map.key?(method)\n\n        routes = @map[method]\n        optimized_map = routes.map.with_index { |route, index| route.to_regexp(index) }\n        @optimized_map[method] = Regexp.union(optimized_map)\n      end\n      @compiled = true\n    end\n\n    def append(route)\n      @map[route.request_method] << route\n    end\n\n    def associate_routes(greedy_route)\n      @neutral_regexes << greedy_route.to_regexp(@neutral_map.length)\n      @neutral_map << greedy_route\n    end\n\n    def call(env)\n      with_optimization do\n        input = Router.normalize_path(env[Rack::PATH_INFO])\n        method = env[Rack::REQUEST_METHOD]\n        response, route = identity(input, method, env)\n        response || rotation(input, method, env, route)\n      end\n    end\n\n    def recognize_path(input)\n      any = with_optimization { greedy_match?(input) }\n      return if any == default_response\n\n      any.endpoint\n    end\n\n    private\n\n    def identity(input, method, env)\n      route = nil\n      response = transaction(input, method, env) do\n        route = match?(input, method)\n        process_route(route, input, env) if route\n      end\n      [response, route]\n    end\n\n    def rotation(input, method, env, exact_route)\n      response = nil\n      @map[method].each do |route|\n        next if exact_route == route\n        next unless route.match?(input)\n\n        response = process_route(route, input, env)\n        break unless cascade?(response)\n      end\n      response\n    end\n\n    def transaction(input, method, env)\n      # using a Proc is important since `return` will exit the enclosing function\n      cascade_or_return_response = proc do |response|\n        if response\n          cascade?(response).tap do |cascade|\n            return response unless cascade\n\n            # we need to close the body if possible before dismissing\n            response[2].close if response[2].respond_to?(:close)\n          end\n        end\n      end\n\n      response = yield\n      last_response_cascade = cascade_or_return_response.call(response)\n      last_neighbor_route = greedy_match?(input)\n\n      # If last_neighbor_route exists and request method is OPTIONS,\n      # return response by using #include_allow_header.\n      return process_route(last_neighbor_route, input, env, include_allow_header: true) if !last_response_cascade && method == Rack::OPTIONS && last_neighbor_route\n\n      route = match?(input, '*')\n\n      return last_neighbor_route.call(env) if last_neighbor_route && last_response_cascade && route\n\n      last_response_cascade = cascade_or_return_response.call(process_route(route, input, env)) if route\n\n      return process_route(last_neighbor_route, input, env, include_allow_header: true) if !last_response_cascade && last_neighbor_route\n\n      nil\n    end\n\n    def process_route(route, input, env, include_allow_header: false)\n      args = env[Grape::Env::GRAPE_ROUTING_ARGS] || { route_info: route }\n      route_params = route.params(input)\n      routing_args = args.merge(route_params || {})\n      env[Grape::Env::GRAPE_ROUTING_ARGS] = routing_args\n      env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header if include_allow_header\n      route.call(env)\n    end\n\n    def with_optimization\n      compile!\n      yield || default_response\n    end\n\n    def default_response\n      headers = Grape::Util::Header.new.merge('X-Cascade' => 'pass')\n      [404, headers, ['404 Not Found']]\n    end\n\n    def match?(input, method)\n      @optimized_map[method].match(input) { |m| @map[method].detect { |route| m[route.regexp_capture_index] } }\n    end\n\n    def greedy_match?(input)\n      @union.match(input) { |m| @neutral_map.detect { |route| m[route.regexp_capture_index] } }\n    end\n\n    def cascade?(response)\n      response && response[1]['X-Cascade'] == 'pass'\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/serve_stream/file_body.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module ServeStream\n    CHUNK_SIZE = 16_384\n\n    # Class helps send file through API\n    class FileBody\n      attr_reader :path\n\n      # @param path [String]\n      def initialize(path)\n        @path = path\n      end\n\n      # Need for Rack::Sendfile middleware\n      #\n      # @return [String]\n      def to_path\n        path\n      end\n\n      def each\n        File.open(path, 'rb') do |file|\n          while (chunk = file.read(CHUNK_SIZE))\n            yield chunk\n          end\n        end\n      end\n\n      def ==(other)\n        path == other.path\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/serve_stream/sendfile_response.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module ServeStream\n    # Response should respond to to_path method\n    # for using Rack::SendFile middleware\n    class SendfileResponse < Rack::Response\n      def respond_to?(method_name, include_all = false)\n        if method_name == :to_path\n          @body.respond_to?(:to_path, include_all)\n        else\n          super\n        end\n      end\n\n      def to_path\n        @body.to_path\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/serve_stream/stream_response.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module ServeStream\n    # A simple class used to identify responses which represent streams (or files) and do not\n    # need to be formatted or pre-read by Rack::Response\n    class StreamResponse\n      attr_reader :stream\n\n      # @param stream [Object]\n      def initialize(stream)\n        @stream = stream\n      end\n\n      # Equality provided mostly for tests.\n      #\n      # @return [Boolean]\n      def ==(other)\n        stream == other.stream\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/util/api_description.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Util\n    class ApiDescription\n      DSL_METHODS = %i[\n        body_name\n        consumes\n        default\n        deprecated\n        detail\n        entity\n        headers\n        hidden\n        http_codes\n        is_array\n        named\n        nickname\n        params\n        produces\n        security\n        summary\n        tags\n      ].freeze\n\n      def initialize(description, endpoint_configuration, &)\n        @endpoint_configuration = endpoint_configuration\n        @attributes = { description: description }\n        instance_eval(&)\n      end\n\n      DSL_METHODS.each do |attribute|\n        define_method attribute do |value|\n          @attributes[attribute] = value\n        end\n      end\n\n      alias success entity\n      alias failure http_codes\n\n      def configuration\n        @configuration ||= eval_endpoint_config(@endpoint_configuration)\n      end\n\n      def settings\n        @attributes\n      end\n\n      private\n\n      def eval_endpoint_config(configuration)\n        return configuration if configuration.is_a?(Hash)\n\n        configuration.evaluate\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/util/base_inheritable.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Util\n    # Base for classes which need to operate with own values kept\n    # in the hash and inherited values kept in a Hash-like object.\n    class BaseInheritable\n      attr_accessor :inherited_values, :new_values\n\n      # @param inherited_values [Object] An object implementing an interface\n      #   of the Hash class.\n      def initialize(inherited_values = nil)\n        @inherited_values = inherited_values || {}\n        @new_values = {}\n      end\n\n      def delete(*keys)\n        keys.map do |key|\n          # since delete returns the deleted value, seems natural to `map` the result\n          new_values.delete key\n        end\n      end\n\n      def initialize_copy(other)\n        super\n        self.inherited_values = other.inherited_values\n        self.new_values = other.new_values.dup\n      end\n\n      def keys\n        if new_values.any?\n          inherited_values.keys.tap do |combined|\n            combined.concat(new_values.keys)\n            combined.uniq!\n          end\n        else\n          inherited_values.keys\n        end\n      end\n\n      def key?(name)\n        inherited_values.key?(name) || new_values.key?(name)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/util/cache.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Util\n    class Cache\n      include Singleton\n\n      attr_reader :cache\n\n      class << self\n        extend Forwardable\n\n        def_delegators :cache, :[]\n        def_delegators :instance, :cache\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/util/endpoint_configuration.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Util\n    class EndpointConfiguration < Lazy::ValueHash\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/util/header.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Util\n    if Gem::Version.new(Rack.release) >= Gem::Version.new('3')\n      require 'rack/headers'\n      Header = Rack::Headers\n    else\n      require 'rack/utils'\n      Header = Rack::Utils::HeaderHash\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/util/inheritable_setting.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Util\n    # A branchable, inheritable settings object which can store both stackable\n    # and inheritable values (see InheritableValues and StackableValues).\n    class InheritableSetting\n      attr_accessor :route, :api_class, :namespace, :namespace_inheritable, :namespace_stackable, :namespace_reverse_stackable, :parent, :point_in_time_copies\n\n      # Retrieve global settings.\n      def self.global\n        @global ||= {}\n      end\n\n      # Clear all global settings.\n      # @api private\n      # @note only for testing\n      def self.reset_global!\n        @global = {}\n      end\n\n      # Instantiate a new settings instance, with blank values. The fresh\n      # instance can then be set to inherit from an existing instance (see\n      # #inherit_from).\n      def initialize\n        self.route = {}\n        self.api_class = {}\n        self.namespace = InheritableValues.new # only inheritable from a parent when\n        # used with a mount, or should every API::Class be a separate namespace by default?\n        self.namespace_inheritable = InheritableValues.new\n        self.namespace_stackable = StackableValues.new\n        self.namespace_reverse_stackable = ReverseStackableValues.new\n\n        self.point_in_time_copies = []\n\n        self.parent = nil\n      end\n\n      # Return the class-level global properties.\n      def global\n        self.class.global\n      end\n\n      # Set our inherited values to the given parent's current values. Also,\n      # update the inherited values on any settings instances which were forked\n      # from us.\n      # @param parent [InheritableSetting]\n      def inherit_from(parent)\n        return if parent.nil?\n\n        self.parent = parent\n\n        namespace_inheritable.inherited_values = parent.namespace_inheritable\n        namespace_stackable.inherited_values = parent.namespace_stackable\n        namespace_reverse_stackable.inherited_values = parent.namespace_reverse_stackable\n        self.route = parent.route.merge(route)\n\n        point_in_time_copies.map { |cloned_one| cloned_one.inherit_from parent }\n      end\n\n      # Create a point-in-time copy of this settings instance, with clones of\n      # all our values. Note that, should this instance's parent be set or\n      # changed via #inherit_from, it will copy that inheritence to any copies\n      # which were made.\n      def point_in_time_copy\n        self.class.new.tap do |new_setting|\n          point_in_time_copies << new_setting\n          new_setting.point_in_time_copies = []\n\n          new_setting.namespace = namespace.clone\n          new_setting.namespace_inheritable = namespace_inheritable.clone\n          new_setting.namespace_stackable = namespace_stackable.clone\n          new_setting.namespace_reverse_stackable = namespace_reverse_stackable.clone\n          new_setting.route = route.clone\n          new_setting.api_class = api_class\n\n          new_setting.inherit_from(parent)\n        end\n      end\n\n      # Resets the instance store of per-route settings.\n      # @api private\n      def route_end\n        @route = {}\n      end\n\n      # Return a serializable hash of our values.\n      def to_hash\n        {\n          global: global.clone,\n          route: route.clone,\n          namespace: namespace.to_hash,\n          namespace_inheritable: namespace_inheritable.to_hash,\n          namespace_stackable: namespace_stackable.to_hash,\n          namespace_reverse_stackable: namespace_reverse_stackable.to_hash\n        }\n      end\n\n      def namespace_stackable_with_hash(key)\n        data = namespace_stackable[key]\n        return if data.blank?\n\n        data.each_with_object({}) { |value, result| result.deep_merge!(value) }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/util/inheritable_values.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Util\n    class InheritableValues < BaseInheritable\n      def [](name)\n        values[name]\n      end\n\n      def []=(name, value)\n        new_values[name] = value\n      end\n\n      def merge(new_hash)\n        values.merge!(new_hash)\n      end\n\n      def to_hash\n        values\n      end\n\n      protected\n\n      def values\n        @inherited_values.merge(@new_values)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/util/lazy/block.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Util\n    module Lazy\n      class Block\n        def initialize(&new_block)\n          @block = new_block\n        end\n\n        def evaluate_from(configuration)\n          @block.call(configuration)\n        end\n\n        def evaluate\n          @block.call({})\n        end\n\n        def lazy?\n          true\n        end\n\n        def to_s\n          evaluate.to_s\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/util/lazy/value.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Util\n    module Lazy\n      class Value\n        attr_reader :access_keys\n\n        def initialize(value, access_keys = [])\n          @value = value\n          @access_keys = access_keys\n        end\n\n        def evaluate_from(configuration)\n          matching_lazy_value = configuration.fetch(@access_keys)\n          matching_lazy_value.evaluate\n        end\n\n        def evaluate\n          @value\n        end\n\n        def lazy?\n          true\n        end\n\n        def reached_by(parent_access_keys, access_key)\n          @access_keys = parent_access_keys + [access_key]\n          self\n        end\n\n        def to_s\n          evaluate.to_s\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/util/lazy/value_array.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Util\n    module Lazy\n      class ValueArray < ValueEnumerable\n        def initialize(array)\n          super\n          @value_hash = []\n          array.each_with_index do |value, index|\n            self[index] = value\n          end\n        end\n\n        def evaluate\n          @value_hash.map(&:evaluate)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/util/lazy/value_enumerable.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Util\n    module Lazy\n      class ValueEnumerable < Value\n        def [](key)\n          if @value_hash[key].nil?\n            Value.new(nil).reached_by(access_keys, key)\n          else\n            @value_hash[key].reached_by(access_keys, key)\n          end\n        end\n\n        def fetch(access_keys)\n          fetched_keys = access_keys.dup\n          value = self[fetched_keys.shift]\n          fetched_keys.any? ? value.fetch(fetched_keys) : value\n        end\n\n        def []=(key, value)\n          @value_hash[key] = case value\n                             when Hash\n                               ValueHash.new(value)\n                             when Array\n                               ValueArray.new(value)\n                             else\n                               Value.new(value)\n                             end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/util/lazy/value_hash.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Util\n    module Lazy\n      class ValueHash < ValueEnumerable\n        def initialize(hash)\n          super\n          @value_hash = ActiveSupport::HashWithIndifferentAccess.new\n          hash.each do |key, value|\n            self[key] = value\n          end\n        end\n\n        def evaluate\n          @value_hash.transform_values(&:evaluate)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/util/media_type.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Util\n    class MediaType\n      attr_reader :type, :subtype, :vendor, :version, :format\n\n      # based on the HTTP Accept header with the pattern:\n      # application/vnd.:vendor-:version+:format\n      VENDOR_VERSION_HEADER_REGEX = /\\Avnd\\.(?<vendor>[a-z0-9.\\-_!^]+?)(?:-(?<version>[a-z0-9*.]+))?(?:\\+(?<format>[a-z0-9*\\-.]+))?\\z/\n\n      def initialize(type:, subtype:)\n        @type = type\n        @subtype = subtype\n        VENDOR_VERSION_HEADER_REGEX.match(subtype) do |m|\n          @vendor = m[:vendor]\n          @version = m[:version]\n          @format = m[:format]\n        end\n      end\n\n      def ==(other)\n        eql?(other)\n      end\n\n      def eql?(other)\n        self.class == other.class &&\n          other.type == type &&\n          other.subtype == subtype &&\n          other.vendor == vendor &&\n          other.version == version &&\n          other.format == format\n      end\n\n      def hash\n        [self.class, type, subtype, vendor, version, format].hash\n      end\n\n      class << self\n        def best_quality(header, available_media_types)\n          parse(best_quality_media_type(header, available_media_types))\n        end\n\n        def parse(media_type)\n          return if media_type.blank?\n\n          type, subtype = media_type.split('/', 2)\n          return if type.blank? || subtype.blank?\n\n          new(type: type, subtype: subtype)\n        end\n\n        def match?(media_type)\n          return false if media_type.blank?\n\n          subtype = media_type.split('/', 2).last\n          return false if subtype.blank?\n\n          VENDOR_VERSION_HEADER_REGEX.match?(subtype)\n        end\n\n        def best_quality_media_type(header, available_media_types)\n          header.blank? ? available_media_types.first : Rack::Utils.best_q_match(header, available_media_types)\n        end\n      end\n\n      private_class_method :best_quality_media_type\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/util/registry.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Util\n    module Registry\n      def register(klass)\n        short_name = build_short_name(klass)\n        return if short_name.nil?\n\n        warn \"#{short_name} is already registered with class #{registry[short_name]}. It will be overridden globally with the following: #{klass.name}\" if registry.key?(short_name)\n        registry[short_name] = klass\n      end\n\n      private\n\n      def build_short_name(klass)\n        return if klass.name.blank?\n\n        klass.name.demodulize.underscore\n      end\n\n      def registry\n        @registry ||= {}.with_indifferent_access\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/util/reverse_stackable_values.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Util\n    class ReverseStackableValues < StackableValues\n      protected\n\n      def concat_values(inherited_value, new_value)\n        return inherited_value unless new_value\n\n        new_value + inherited_value\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/util/stackable_values.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Util\n    class StackableValues < BaseInheritable\n      # Even if there is no value, an empty array will be returned.\n      def [](name)\n        inherited_value = inherited_values[name]\n        new_value = new_values[name]\n\n        return new_value || [] unless inherited_value\n\n        concat_values(inherited_value, new_value)\n      end\n\n      def []=(name, value)\n        new_values[name] ||= []\n        new_values[name].push value\n      end\n\n      def to_hash\n        keys.each_with_object({}) do |key, result|\n          result[key] = self[key]\n        end\n      end\n\n      protected\n\n      def concat_values(inherited_value, new_value)\n        return inherited_value unless new_value\n\n        inherited_value + new_value\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/util/translation.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Util\n    module Translation\n      FALLBACK_LOCALE = :en\n      private_constant :FALLBACK_LOCALE\n      # Sentinel returned by I18n when a key is missing (passed as the default:\n      # value). Using a named class rather than plain Object.new makes it\n      # identifiable in debug output and immune to backends that call .to_s on\n      # the default before returning it.\n      MISSING = Class.new { def inspect = 'Grape::Util::Translation::MISSING' }.new.freeze\n      private_constant :MISSING\n\n      private\n\n      # Extra keyword args (**) are forwarded verbatim to I18n as interpolation\n      # variables (e.g. +min:+, +max:+ from LengthValidator's Hash message).\n      # Callers must not pass unintended keyword arguments — any extra keyword\n      # will silently become an I18n interpolation variable.\n      def translate(key, default: MISSING, scope: 'grape.errors.messages', locale: nil, **)\n        i18n_opts = { default:, scope:, ** }\n        i18n_opts[:locale] = locale if locale\n        message = ::I18n.translate(key, **i18n_opts)\n        return message unless message.equal?(MISSING)\n\n        effective_default = default.equal?(MISSING) ? [*Array(scope), key].join('.') : default\n        return effective_default if fallback_locale?(locale) || fallback_locale_unavailable?\n\n        ::I18n.translate(key, default: effective_default, scope:, locale: FALLBACK_LOCALE, **)\n      end\n\n      def fallback_locale?(locale)\n        (locale || ::I18n.locale) == FALLBACK_LOCALE\n      end\n\n      def fallback_locale_unavailable?\n        ::I18n.enforce_available_locales && !::I18n.available_locales.include?(FALLBACK_LOCALE)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/attributes_iterator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    class AttributesIterator\n      include Enumerable\n\n      attr_reader :scope\n\n      def initialize(attrs, scope, params)\n        @attrs = attrs\n        @scope = scope\n        @original_params = scope.params(params)\n        @params = Array.wrap(@original_params)\n      end\n\n      def each(&)\n        do_each(@params, &) # because we need recursion for nested arrays\n      end\n\n      private\n\n      def do_each(params_to_process, parent_indices = [], &block)\n        params_to_process.each_with_index do |resource_params, index|\n          # when we get arrays of arrays it means that target element located inside array\n          # we need this because we want to know parent arrays indices\n          if resource_params.is_a?(Array)\n            do_each(resource_params, [index] + parent_indices, &block)\n            next\n          end\n\n          if @scope.type == Array\n            next unless @original_params.is_a?(Array) # do not validate content of array if it isn't array\n\n            store_indices(@scope, index, parent_indices)\n          elsif @original_params.is_a?(Array)\n            # Lateral scope (no @element) whose params resolved to an array —\n            # delegate index tracking to the nearest array-typed ancestor so\n            # that full_name produces the correct bracketed index.\n            target = @scope.nearest_array_ancestor\n            store_indices(target, index, parent_indices) if target\n          end\n\n          yield_attributes(resource_params, &block)\n        end\n      end\n\n      def store_indices(target_scope, index, parent_indices)\n        # No tracker means we're outside a ParamScopeTracker.track block (e.g.\n        # a unit test that invokes a validator directly). Index tracking is\n        # skipped — full_name will produce bracket-less names — but validation\n        # continues rather than crashing.\n        tracker = ParamScopeTracker.current or return\n        parent_scope = target_scope.parent\n        parent_indices.each do |parent_index|\n          break unless parent_scope\n\n          tracker.store_index(parent_scope, parent_index)\n          parent_scope = parent_scope.parent\n        end\n        tracker.store_index(target_scope, index)\n      end\n\n      def yield_attributes(_resource_params)\n        raise NotImplementedError\n      end\n\n      # This is a special case so that we can ignore trees where option\n      # values are missing lower down. Unfortunately we can't remove this\n      # at the parameter parsing stage as they are required to ensure\n      # the correct indexing is maintained\n      def skip?(val)\n        val == Grape::DSL::Parameters::EmptyOptionalValue\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/contract_scope.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    class ContractScope\n      # Declare the contract to be used for the endpoint's parameters.\n      # @param api [API] the API endpoint to modify.\n      # @param contract the contract or schema to be used for validation. Optional.\n      # @yield a block yielding a new schema class. Optional.\n      def initialize(api, contract = nil, &block)\n        # When block is passed, the first arg is either schema or nil.\n        contract = Dry::Schema.Params(parent: contract, &block) if block\n\n        if contract.respond_to?(:schema)\n          # It's a Dry::Validation::Contract, then.\n          contract = contract.new\n          key_map = contract.schema.key_map\n        else\n          # Dry::Schema::Processor, hopefully.\n          key_map = contract.key_map\n        end\n\n        api.inheritable_setting.namespace_stackable[:contract_key_map] = key_map\n\n        validator_options = {\n          validator_class: Grape::Validations.require_validator(:contract_scope),\n          opts: { schema: contract, fail_fast: false }\n        }\n\n        api.inheritable_setting.namespace_stackable[:validations] = validator_options\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/multiple_attributes_iterator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    class MultipleAttributesIterator < AttributesIterator\n      private\n\n      def yield_attributes(resource_params)\n        yield resource_params unless skip?(resource_params)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/param_scope_tracker.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    # Holds per-request mutable state that must not live on shared ParamsScope\n    # instances. Both trackers are identity-keyed hashes so that ParamsScope\n    # objects can serve as keys without relying on value equality.\n    #\n    # Lifecycle is managed by Endpoint#run_validators via +.track+.\n    # Use +.current+ to access the instance for the running request.\n    class ParamScopeTracker\n      # Fiber-local key used to store the current tracker.\n      # Fiber[] (Ruby 3.0+) is used instead of Thread.current[] so that\n      # fiber-based servers (e.g. Falcon with async) isolate each request's\n      # tracker within its own fiber rather than sharing state across all\n      # fibers running on the same thread.\n      FIBER_KEY = :grape_param_scope_tracker\n      EMPTY_PARAMS = [].freeze\n\n      def self.track\n        previous = Fiber[FIBER_KEY]\n        Fiber[FIBER_KEY] = new\n        yield\n      ensure\n        Fiber[FIBER_KEY] = previous\n      end\n\n      def self.current\n        Fiber[FIBER_KEY]\n      end\n\n      def initialize\n        @index_tracker = {}.compare_by_identity\n        @qualifying_params_tracker = {}.compare_by_identity\n      end\n\n      def store_index(scope, index)\n        @index_tracker.store(scope, index)\n      end\n\n      def index_for(scope)\n        @index_tracker[scope]\n      end\n\n      # Returns qualifying params for +scope+, or EMPTY_PARAMS if none were stored.\n      # Note: an explicitly stored empty array and \"never stored\" are treated identically\n      # by callers (both yield a blank result that falls through to the parent params).\n      def qualifying_params(scope)\n        @qualifying_params_tracker.fetch(scope, EMPTY_PARAMS)\n      end\n\n      def store_qualifying_params(scope, params)\n        @qualifying_params_tracker.store(scope, params)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/params_documentation.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    # Documents parameters of an endpoint. If documentation isn't needed (for instance, it is an\n    # internal API), the class only cleans up attributes to avoid junk in RAM.\n\n    module ParamsDocumentation\n      def document_params(attrs, validations, type = nil, values = nil, except_values = nil)\n        return validations.except!(:desc, :description, :documentation) if @api.inheritable_setting.namespace_inheritable[:do_not_document]\n\n        documented_attrs = attrs.each_with_object({}) do |name, memo|\n          memo[full_name(name)] = extract_details(validations, type, values, except_values)\n        end\n        @api.inheritable_setting.namespace_stackable[:params] = documented_attrs\n      end\n\n      private\n\n      def extract_details(validations, type, values, except_values)\n        {}.tap do |details|\n          details[:required] = validations.key?(:presence)\n          details[:type] = TypeCache[type] if type\n          details[:values] = values if values\n          details[:except_values] = except_values if except_values\n          details[:default] = validations[:default] if validations.key?(:default)\n          if validations.key?(:length)\n            details[:min_length] = validations[:length][:min] if validations[:length].key?(:min)\n            details[:max_length] = validations[:length][:max] if validations[:length].key?(:max)\n          end\n\n          desc = validations.delete(:desc) || validations.delete(:description)\n          details[:desc] = desc if desc\n\n          documentation = validations.delete(:documentation)\n          details[:documentation] = documentation if documentation\n        end\n      end\n\n      class TypeCache < Grape::Util::Cache\n        def initialize\n          super\n          @cache = Hash.new do |h, type|\n            h[type] = type.to_s\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/params_scope.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    class ParamsScope\n      attr_accessor :element, :parent\n      attr_reader :type, :nearest_array_ancestor\n\n      def qualifying_params\n        ParamScopeTracker.current&.qualifying_params(self)\n      end\n\n      include Grape::DSL::Parameters\n      include Grape::Validations::ParamsDocumentation\n\n      # There are a number of documentation options on entities that don't have\n      # corresponding validators. Since there is nowhere that enumerates them all,\n      # we maintain a list of them here and skip looking up validators for them.\n      RESERVED_DOCUMENTATION_KEYWORDS = %i[as required param_type is_array format example].freeze\n\n      SPECIAL_JSON = [JSON, Array[JSON]].freeze\n\n      class Attr\n        attr_accessor :key, :scope\n\n        # Open up a new ParamsScope::Attr\n        # @param key [Hash, Symbol] key of attr\n        # @param scope [Grape::Validations::ParamsScope] scope of attr\n        def initialize(key, scope)\n          @key = key\n          @scope = scope\n        end\n\n        # @return Array[Symbol, Hash[Symbol => Array]] declared_params with symbol instead of Attr\n        def self.attrs_keys(declared_params)\n          declared_params.map do |declared_param_attr|\n            attr_key(declared_param_attr)\n          end\n        end\n\n        def self.attr_key(declared_param_attr)\n          return attr_key(declared_param_attr.key) if declared_param_attr.is_a?(self)\n\n          if declared_param_attr.is_a?(Hash)\n            declared_param_attr.transform_values { |value| attrs_keys(value) }\n          else\n            declared_param_attr\n          end\n        end\n      end\n\n      # Open up a new ParamsScope, allowing parameter definitions per\n      #   Grape::DSL::Params.\n      # @param api [API] the API endpoint to modify\n      # @param element [Symbol] the element that contains this scope; for\n      #   this to be relevant, parent must be set\n      # @param element_renamed [Symbol, nil] whenever this scope should\n      #   be renamed and to what, given +nil+ no renaming is done\n      # @param parent [ParamsScope] the scope containing this scope\n      # @param optional [Boolean] whether or not this scope needs to have\n      #   any parameters set or not\n      # @param type [Class] a type meant to govern this scope (deprecated)\n      # @param type [Hash] group options for this scope\n      # @param dependent_on [Symbol] if present, this scope should only\n      #   validate if this param is present in the parent scope\n      # @yield the instance context, open for parameter definitions\n      def initialize(api:, element: nil, element_renamed: nil, parent: nil, optional: false, type: nil, group: nil, dependent_on: nil, &block)\n        @element          = element\n        @element_renamed  = element_renamed\n        @parent           = parent\n        @api              = api\n        @optional         = optional\n        @type             = type\n        @group            = group\n        @dependent_on     = dependent_on\n        # Must be an ivar: push_declared_params is dispatched on self during\n        # instance_eval, so local variables from initialize are unreachable.\n        # configure_declared_params consumes it and clears @declared_params to nil.\n        @declared_params = []\n\n        instance_eval(&block) if block\n\n        configure_declared_params\n        @nearest_array_ancestor = find_nearest_array_ancestor\n      end\n\n      def configuration\n        (@api.configuration.respond_to?(:evaluate) && @api.configuration.evaluate) || @api.configuration\n      end\n\n      # @return [Boolean] whether or not this entire scope needs to be\n      #   validated\n      def should_validate?(parameters)\n        scoped_params = params(parameters)\n\n        return false if @optional && (scoped_params.blank? || all_element_blank?(scoped_params))\n        return false unless meets_dependency?(scoped_params, parameters)\n        return true if parent.nil?\n\n        parent.should_validate?(parameters)\n      end\n\n      def meets_dependency?(params, request_params)\n        return true unless @dependent_on\n        return false if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)\n\n        if params.is_a?(Array)\n          filtered = params.flatten.filter { |param| meets_dependency?(param, request_params) }\n          ParamScopeTracker.current&.store_qualifying_params(self, filtered)\n          return filtered.present?\n        end\n\n        meets_hash_dependency?(params)\n      end\n\n      def attr_meets_dependency?(params)\n        return true unless @dependent_on\n        return false if @parent.present? && !@parent.attr_meets_dependency?(params)\n\n        meets_hash_dependency?(params)\n      end\n\n      def meets_hash_dependency?(params)\n        # params might be anything what looks like a hash, so it must implement a `key?` method\n        return false unless params.respond_to?(:key?)\n\n        @dependent_on.each do |dependency|\n          if dependency.is_a?(Hash)\n            dependency_key = dependency.keys[0]\n            proc = dependency.values[0]\n            return false unless proc.call(params[dependency_key])\n          elsif params[dependency].blank?\n            return false\n          end\n        end\n\n        true\n      end\n\n      # @return [String] the proper attribute name, with nesting considered.\n      def full_name(name, index: nil)\n        tracker = ParamScopeTracker.current\n        if nested?\n          # Find our containing element's name, and append ours.\n          resolved_index = index || tracker&.index_for(self)\n          \"#{@parent.full_name(@element)}#{brackets(resolved_index)}#{brackets(name)}\"\n        elsif lateral?\n          # Find the name of the element as if it was at the same nesting level\n          # as our parent. We need to forward our index upward to achieve this.\n          @parent.full_name(name, index: tracker&.index_for(self))\n        else\n          # We must be the root scope, so no prefix needed.\n          name.to_s\n        end\n      end\n\n      def brackets(val)\n        \"[#{val}]\" if val\n      end\n\n      # @return [Boolean] whether or not this scope is the root-level scope\n      def root?\n        !@parent\n      end\n\n      # A nested scope is contained in one of its parent's elements.\n      # @return [Boolean] whether or not this scope is nested\n      def nested?\n        @parent && @element\n      end\n\n      # A lateral scope is subordinate to its parent, but its keys are at the\n      # same level as its parent and thus is not contained within an element.\n      # @return [Boolean] whether or not this scope is lateral\n      def lateral?\n        @parent && !@element\n      end\n\n      # @return [Boolean] whether or not this scope needs to be present, or can\n      #   be blank\n      def required?\n        !@optional\n      end\n\n      protected\n\n      # Adds a parameter declaration to our list of validations.\n      # @param attrs [Array] (see Grape::DSL::Parameters#requires)\n      def push_declared_params(attrs, **opts)\n        opts[:declared_params_scope] = self unless opts.key?(:declared_params_scope)\n        return @parent.push_declared_params(attrs, **opts) if lateral?\n\n        push_renamed_param(full_path + [attrs.first], opts[:as]) if opts[:as]\n        @declared_params.concat(attrs.map { |attr| ::Grape::Validations::ParamsScope::Attr.new(attr, opts[:declared_params_scope]) })\n      end\n\n      # Get the full path of the parameter scope in the hierarchy.\n      #\n      # @return [Array<Symbol>] the nesting/path of the current parameter scope\n      def full_path\n        if nested?\n          (@parent.full_path + [@element])\n        elsif lateral?\n          @parent.full_path\n        else\n          []\n        end\n      end\n\n      private\n\n      # Add a new parameter which should be renamed when using the +#declared+\n      # method.\n      #\n      # @param path [Array<String, Symbol>] the full path of the parameter\n      #   (including the parameter name as last array element)\n      # @param new_name [String, Symbol] the new name of the parameter (the\n      #   renamed name, with the +as: ...+ semantic)\n      def push_renamed_param(path, new_name)\n        api_route_setting = @api.inheritable_setting.route\n        base = api_route_setting[:renamed_params] || {}\n        base[Array(path).map(&:to_s)] = new_name.to_s\n        api_route_setting[:renamed_params] = base\n      end\n\n      def require_required_and_optional_fields(context, using:, except: nil)\n        except_fields = Array.wrap(except)\n        using_fields = using.keys.delete_if { |f| except_fields.include?(f) }\n\n        if context == :all\n          optional_fields = except_fields\n          required_fields = using_fields\n        else # context == :none\n          required_fields = except_fields\n          optional_fields = using_fields\n        end\n        required_fields.each do |field|\n          field_opts = using[field]\n          raise ArgumentError, \"required field not exist: #{field}\" unless field_opts\n\n          requires(field, **field_opts)\n        end\n        optional_fields.each do |field|\n          field_opts = using[field]\n          optional(field, **field_opts) if field_opts\n        end\n      end\n\n      def require_optional_fields(context, using:, except: nil)\n        optional_fields = using.keys\n        unless context == :all\n          except_fields = Array.wrap(except)\n          optional_fields.delete_if { |f| except_fields.include?(f) }\n        end\n        optional_fields.each do |field|\n          field_opts = using[field]\n          optional(field, **field_opts) if field_opts\n        end\n      end\n\n      def validate_attributes(attrs, **opts, &block)\n        opts[:type] ||= Array if block\n        validates(attrs, opts)\n      end\n\n      # Returns a new parameter scope, subordinate to the current one and nested\n      # under the given element.\n      # @param element [Symbol] the parameter name under which this scope is nested\n      # @param type [Class] the type governing this scope\n      # @param as [Symbol, nil] optional renamed name for the element\n      # @param optional [Boolean] whether the parameter this scope is nested under\n      #   is optional or not (and hence, whether this block's params will be).\n      # @yield parameter scope\n      def new_scope(element, type:, as:, optional: false, &)\n        # if required params are grouped and no type or unsupported type is provided, raise an error\n        if element && !optional\n          raise Grape::Exceptions::MissingGroupType if type.nil?\n          raise Grape::Exceptions::UnsupportedGroupType unless Grape::Validations::Types.group?(type)\n        end\n\n        self.class.new(\n          api: @api,\n          element: element,\n          element_renamed: as,\n          parent: self,\n          optional: optional,\n          type: type || Array,\n          group: @group,\n          &\n        )\n      end\n\n      # Returns a new parameter scope, not nested under any current-level param\n      # but instead at the same level as the current scope.\n      # @param dependent_on [Symbol] if given, specifies that this scope should\n      #   only validate if this parameter from the above scope is present\n      # @yield parameter scope\n      def new_lateral_scope(dependent_on:, &)\n        self.class.new(\n          api: @api,\n          parent: self,\n          optional: @optional,\n          type: type == Array ? Array : Hash,\n          dependent_on:,\n          &\n        )\n      end\n\n      # Returns a new parameter scope, subordinate to the current one, sharing\n      # the given group options with all parameters defined within.\n      # @param group [Hash] common options to merge into each parameter in the scope\n      # @yield parameter scope\n      def new_group_scope(group, &)\n        self.class.new(api: @api, parent: self, group: group, &)\n      end\n\n      # Pushes declared params to parent or settings\n      def configure_declared_params\n        push_renamed_param(full_path, @element_renamed) if @element_renamed\n\n        if nested?\n          @parent.push_declared_params [element => @declared_params]\n        else\n          @api.inheritable_setting.namespace_stackable[:declared_params] = @declared_params\n        end\n\n        # params were stored in settings, it can be cleaned from the params scope\n        @declared_params = nil\n      end\n\n      def find_nearest_array_ancestor\n        scope = @parent\n        scope = scope.parent while scope && scope.type != Array\n        scope\n      end\n\n      def validates(attrs, validations)\n        coerce_type = infer_coercion(validations)\n        required = validations.key?(:presence)\n        default = validations[:default]\n        values = validations[:values].is_a?(Hash) ? validations.dig(:values, :value) : validations[:values]\n        except_values = validations[:except_values].is_a?(Hash) ? validations.dig(:except_values, :value) : validations[:except_values]\n\n        # NB. values and excepts should be nil, Proc, Array, or Range.\n        # Specifically, values should NOT be a Hash\n        # use values or excepts to guess coerce type when stated type is Array\n        coerce_type = guess_coerce_type(coerce_type, values, except_values)\n\n        # default value should be present in values array, if both exist and are not procs\n        check_incompatible_option_values(default, values, except_values)\n\n        # type should be compatible with values array, if both exist\n        validate_value_coercion(coerce_type, values, except_values)\n\n        document_params attrs, validations, coerce_type, values, except_values\n\n        opts = derive_validator_options(validations)\n\n        # Validate for presence before any other validators\n        validates_presence(validations, attrs, opts)\n\n        # Before we run the rest of the validators, let's handle\n        # whatever coercion so that we are working with correctly\n        # type casted values\n        coerce_type validations, attrs, required, opts\n\n        validations.each do |type, options|\n          # Don't try to look up validators for documentation params that don't have one.\n          next if RESERVED_DOCUMENTATION_KEYWORDS.include?(type)\n\n          validate(type, options, attrs, required, opts)\n        end\n      end\n\n      # Validate and comprehend the +:type+, +:types+, and +:coerce_with+\n      # options that have been supplied to the parameter declaration.\n      # The +:type+ and +:types+ options will be removed from the\n      # validations list, replaced appropriately with +:coerce+ and\n      # +:coerce_with+ options that will later be passed to\n      # {Validators::CoerceValidator}. The type that is returned may be\n      # used for documentation and further validation of parameter\n      # options.\n      #\n      # @param validations [Hash] list of validations supplied to the\n      #   parameter declaration\n      # @return [class-like] type to which the parameter will be coerced\n      # @raise [ArgumentError] if the given type options are invalid\n      def infer_coercion(validations)\n        raise ArgumentError, ':type may not be supplied with :types' if validations.key?(:type) && validations.key?(:types)\n\n        validations[:coerce] = (options_key?(:type, :value, validations) ? validations[:type][:value] : validations[:type]) if validations.key?(:type)\n        validations[:coerce_message] = (options_key?(:type, :message, validations) ? validations[:type][:message] : nil) if validations.key?(:type)\n        validations[:coerce] = (options_key?(:types, :value, validations) ? validations[:types][:value] : validations[:types]) if validations.key?(:types)\n        validations[:coerce_message] = (options_key?(:types, :message, validations) ? validations[:types][:message] : nil) if validations.key?(:types)\n\n        validations.delete(:types) if validations.key?(:types)\n\n        coerce_type = validations[:coerce]\n\n        # Special case - when the argument is a single type that is a\n        # variant-type collection.\n        if Types.multiple?(coerce_type) && validations.key?(:type)\n          validations[:coerce] = Types::VariantCollectionCoercer.new(\n            coerce_type,\n            validations.delete(:coerce_with)\n          )\n        end\n        validations.delete(:type)\n\n        coerce_type\n      end\n\n      # Enforce correct usage of :coerce_with parameter.\n      # We do not allow coercion without a type, nor with\n      # +JSON+ as a type since this defines its own coercion\n      # method.\n      def check_coerce_with(validations)\n        return unless validations.key?(:coerce_with)\n        # type must be supplied for coerce_with..\n        raise ArgumentError, 'must supply type for coerce_with' unless validations.key?(:coerce)\n\n        # but not special JSON types, which\n        # already imply coercion method\n        return unless SPECIAL_JSON.include?(validations[:coerce])\n\n        raise ArgumentError, 'coerce_with disallowed for type: JSON'\n      end\n\n      # Add type coercion validation to this scope,\n      # if any has been specified.\n      # This validation has special handling since it is\n      # composited from more than one +requires+/+optional+\n      # parameter, and needs to be run before most other\n      # validations.\n      def coerce_type(validations, attrs, required, opts)\n        check_coerce_with(validations)\n\n        return unless validations.key?(:coerce)\n\n        coerce_options = {\n          type: validations[:coerce],\n          method: validations[:coerce_with],\n          message: validations[:coerce_message]\n        }\n        validate('coerce', coerce_options, attrs, required, opts)\n        validations.delete(:coerce_with)\n        validations.delete(:coerce)\n        validations.delete(:coerce_message)\n      end\n\n      def guess_coerce_type(coerce_type, *values_list)\n        return coerce_type unless coerce_type == Array\n\n        values_list.each do |values|\n          next if !values || values.is_a?(Proc)\n          return values.first.class if values.is_a?(Range) || !values.empty?\n        end\n        coerce_type\n      end\n\n      def check_incompatible_option_values(default, values, except_values)\n        return unless default && !default.is_a?(Proc)\n\n        raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :values, values) if values && !values.is_a?(Proc) && !Array(default).all? { |def_val| values.include?(def_val) }\n\n        return unless except_values && !except_values.is_a?(Proc) && Array(default).any? { |def_val| except_values.include?(def_val) }\n\n        raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values)\n      end\n\n      def validate(type, options, attrs, required, opts)\n        validator_options = {\n          attributes: attrs,\n          options: options,\n          required: required,\n          params_scope: self,\n          opts: opts,\n          validator_class: Validations.require_validator(type)\n        }\n        @api.inheritable_setting.namespace_stackable[:validations] = validator_options\n      end\n\n      def validate_value_coercion(coerce_type, *values_list)\n        return unless coerce_type\n\n        coerce_type = coerce_type.first if coerce_type.is_a?(Enumerable)\n        values_list.each do |values|\n          next if !values || values.is_a?(Proc)\n\n          value_types = values.is_a?(Range) ? [values.begin, values.end].compact : values\n          value_types = value_types.map { |type| Grape::API::Boolean.build(type) } if coerce_type == Grape::API::Boolean\n          raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values) unless value_types.all?(coerce_type)\n        end\n      end\n\n      def extract_message_option(attrs)\n        return nil unless attrs.is_a?(Array)\n\n        opts = attrs.last.is_a?(Hash) ? attrs.pop : {}\n        opts.key?(:message) && !opts[:message].nil? ? opts.delete(:message) : nil\n      end\n\n      def options_key?(type, key, validations)\n        validations[type].respond_to?(:key?) && validations[type].key?(key) && !validations[type][key].nil?\n      end\n\n      def all_element_blank?(scoped_params)\n        scoped_params.respond_to?(:all?) && scoped_params.all?(&:blank?)\n      end\n\n      # Validators don't have access to each other and they don't need, however,\n      # some validators might influence others, so their options should be shared\n      def derive_validator_options(validations)\n        allow_blank = validations[:allow_blank]\n\n        {\n          allow_blank: allow_blank.is_a?(Hash) ? allow_blank[:value] : allow_blank,\n          fail_fast: validations.delete(:fail_fast) || false\n        }\n      end\n\n      def validates_presence(validations, attrs, opts)\n        return unless validations.key?(:presence) && validations[:presence]\n\n        validate('presence', validations.delete(:presence), attrs, true, opts)\n        validations.delete(:message) if validations.key?(:message)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/single_attribute_iterator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    class SingleAttributeIterator < AttributesIterator\n      private\n\n      def yield_attributes(val)\n        return if skip?(val)\n\n        @attrs.each do |attr_name|\n          yield val, attr_name, empty?(val)\n        end\n      end\n\n      # Primitives like Integers and Booleans don't respond to +empty?+.\n      # It could be possible to use +blank?+ instead, but\n      #\n      #     false.blank?\n      #     => true\n      def empty?(val)\n        val.respond_to?(:empty?) ? val.empty? : val.nil?\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/types/array_coercer.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Types\n      # Coerces elements in an array. It might be an array of strings or integers or\n      # an array of arrays of integers.\n      #\n      # It could've been possible to use an +of+\n      # method (https://dry-rb.org/gems/dry-types/main/array-with-member/)\n      # provided by dry-types. Unfortunately, it doesn't work for Grape because of\n      # behavior of Virtus which was used earlier, a `Grape::Validations::Types::PrimitiveCoercer`\n      # maintains Virtus behavior in coercing.\n      class ArrayCoercer < DryTypeCoercer\n        def initialize(type, strict = false)\n          super\n          @coercer = strict ? DryTypes::Strict::Array : DryTypes::Params::Array\n          @subtype = type.first\n        end\n\n        def call(_val)\n          collection = super\n          return collection if collection.is_a?(InvalidValue)\n\n          coerce_elements collection\n        end\n\n        protected\n\n        attr_reader :subtype\n\n        def coerce_elements(collection)\n          return if collection.nil?\n\n          collection.each_with_index do |elem, index|\n            return InvalidValue.new if reject?(elem)\n\n            coerced_elem = elem_coercer.call(elem)\n\n            return coerced_elem if coerced_elem.is_a?(InvalidValue)\n\n            collection[index] = coerced_elem\n          end\n\n          collection\n        end\n\n        # This method maintains logic which was defined by Virtus for arrays.\n        # Virtus doesn't allow nil in arrays.\n        def reject?(val)\n          val.nil?\n        end\n\n        def elem_coercer\n          @elem_coercer ||= DryTypeCoercer.coercer_instance_for(subtype, strict)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/types/custom_type_coercer.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Types\n      # This class will detect type classes that implement\n      # a class-level +parse+ method. The method should accept one\n      # +String+ argument and should return the value coerced to\n      # the appropriate type. The method may raise an exception if\n      # there are any problems parsing the string.\n      #\n      # Alternately an optional +method+ may be supplied (see the\n      # +coerce_with+ option of {Grape::Dsl::Parameters#requires}).\n      # This may be any class or object implementing +parse+ or +call+,\n      # with the same contract as described above.\n      #\n      # Type Checking\n      # -------------\n      #\n      # Calls to +coerced?+ will consult this class to check\n      # that the coerced value produced above is in fact of the\n      # expected type. By default this class performs a basic check\n      # against the type supplied, but this behaviour will be\n      # overridden if the class implements a class-level\n      # +coerced?+ or +parsed?+ method. This method\n      # will receive a single parameter that is the coerced value\n      # and should return +true+ if the value meets type expectations.\n      # Arbitrary assertions may be made here but the grape validation\n      # system should be preferred.\n      #\n      # Alternately a proc or other object responding to +call+ may be\n      # supplied in place of a type. This should implement the same\n      # contract as +coerced?+, and must be supplied with a coercion\n      # +method+.\n      class CustomTypeCoercer\n        # A new coercer for the given type specification\n        # and coercion method.\n        #\n        # @param type [Class,#coerced?,#parsed?,#call?]\n        #   specifier for the target type. See class docs.\n        # @param method [#parse,#call]\n        #   optional coercion method. See class docs.\n        def initialize(type, method = nil)\n          coercion_method = infer_coercion_method type, method\n          @method = enforce_symbolized_keys type, coercion_method\n          @type_check = infer_type_check(type)\n        end\n\n        # Coerces the given value.\n        #\n        # @param value [String] value to be coerced, in grape\n        #   this should always be a string.\n        # @return [Object] the coerced result\n        def call(val)\n          coerced_val = @method.call(val)\n\n          return coerced_val if coerced_val.is_a?(InvalidValue)\n          return InvalidValue.new unless coerced?(coerced_val)\n\n          coerced_val\n        end\n\n        def coerced?(val)\n          val.nil? || @type_check.call(val)\n        end\n\n        private\n\n        # Determine the coercion method we're expected to use\n        # based on the parameters given.\n        #\n        # @param type see #new\n        # @param method see #new\n        # @return [#call] coercion method\n        def infer_coercion_method(type, method)\n          if method\n            if method.respond_to? :parse\n              method.method :parse\n            else\n              method\n            end\n          else\n            # Try to use parse() declared on the target type.\n            # This may raise an exception, but we are out of ideas anyway.\n            type.method :parse\n          end\n        end\n\n        # Determine how the type validity of a coerced\n        # value should be decided.\n        #\n        # @param type see #new\n        # @return [#call] a procedure which accepts a single parameter\n        #   and returns +true+ if the passed object is of the correct type.\n        def infer_type_check(type)\n          # First check for special class methods\n          if type.respond_to? :coerced?\n            type.method :coerced?\n          elsif type.respond_to? :parsed?\n            type.method :parsed?\n          elsif type.respond_to? :call\n            # Arbitrary proc passed for type validation.\n            # Note that this will fail unless a method is also\n            # passed, or if the type also implements a parse() method.\n            type\n          elsif type.is_a?(Enumerable)\n            lambda do |value|\n              value.is_a?(Enumerable) && value.all? do |val|\n                recursive_type_check(type.first, val)\n              end\n            end\n          else\n            # By default, do a simple type check\n            ->(value) { value.is_a? type }\n          end\n        end\n\n        def recursive_type_check(type, value)\n          if type.is_a?(Enumerable) && value.is_a?(Enumerable)\n            value.all? { |val| recursive_type_check(type.first, val) }\n          else\n            !type.is_a?(Enumerable) && value.is_a?(type)\n          end\n        end\n\n        # Enforce symbolized keys for complex types\n        # by wrapping the coercion method such that\n        # any Hash objects in the immediate heirarchy\n        # have their keys recursively symbolized.\n        # This helps common libs such as JSON to work easily.\n        #\n        # @param type see #new\n        # @param method see #infer_coercion_method\n        # @return [#call] +method+ wrapped in an additional\n        #   key-conversion step, or just returns +method+\n        #   itself if no conversion is deemed to be\n        #   necessary.\n        def enforce_symbolized_keys(type, method)\n          # Collections have all values processed individually\n          if [Array, Set].include?(type)\n            lambda do |val|\n              method.call(val).tap do |new_val|\n                new_val.map do |item|\n                  item.is_a?(Hash) ? item.deep_symbolize_keys : item\n                end\n              end\n            end\n\n          # Hash objects are processed directly\n          elsif type == Hash\n            lambda do |val|\n              method.call(val).deep_symbolize_keys\n            end\n\n          # Simple types are not processed.\n          # This includes Array<primitive> types.\n          else\n            method\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/types/custom_type_collection_coercer.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Types\n      # See {CustomTypeCoercer} for details on types\n      # that will be supported by this by this coercer.\n      # This coercer works in the same way as +CustomTypeCoercer+\n      # except that it expects to receive an array of strings to\n      # coerce and will return an array (or optionally, a set)\n      # of coerced values.\n      #\n      # +CustomTypeCoercer+ is already capable of providing type\n      # checking for arrays where an independent coercion method\n      # is supplied. As such, +CustomTypeCollectionCoercer+ does\n      # not allow for such a method to be supplied independently\n      # of the type.\n      class CustomTypeCollectionCoercer < CustomTypeCoercer\n        # A new coercer for collections of the given type.\n        #\n        # @param type [Class,#parse]\n        #   type to which items in the array should be coerced.\n        #   Must implement a +parse+ method which accepts a string,\n        #   and for the purposes of type-checking it may either be\n        #   a class, or it may implement a +coerced?+, +parsed?+ or\n        #   +call+ method (in that order of precedence) which\n        #   accepts a single argument and returns true if the given\n        #   array item has been coerced correctly.\n        # @param set [Boolean]\n        #   when true, a +Set+ will be returned by {#call} instead\n        #   of an +Array+ and duplicate items will be discarded.\n        def initialize(type, set = false)\n          super(type)\n          @set = set\n        end\n\n        # Coerces the given value.\n        #\n        # @param value [Array<String>] an array of values to be coerced\n        # @return [Array,Set] the coerced result. May be an +Array+ or a\n        #   +Set+ depending on the setting given to the constructor\n        def call(value)\n          coerced = value.map do |item|\n            coerced_item = super(item)\n\n            return coerced_item if coerced_item.is_a?(InvalidValue)\n\n            coerced_item\n          end\n\n          @set ? Set.new(coerced) : coerced\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/types/dry_type_coercer.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Types\n      # A base class for classes which must identify a coercer to be used.\n      # If the +strict+ argument is true, it won't coerce the given value\n      # but check its type. More information there\n      # https://dry-rb.org/gems/dry-types/main/built-in-types/\n      class DryTypeCoercer\n        class << self\n          # Returns a collection coercer which corresponds to a given type.\n          # Example:\n          #\n          #    collection_coercer_for(Array)\n          #    #=> Grape::Validations::Types::ArrayCoercer\n          def collection_coercer_for(type)\n            case type\n            when Array\n              ArrayCoercer\n            when Set\n              SetCoercer\n            else\n              raise ArgumentError, \"Unknown type: #{type}\"\n            end\n          end\n\n          # Returns an instance of a coercer for a given type\n          def coercer_instance_for(type, strict = false)\n            klass = type.instance_of?(Class) ? PrimitiveCoercer : collection_coercer_for(type)\n            klass.new(type, strict)\n          end\n        end\n\n        def initialize(type, strict = false)\n          @type = type\n          @strict = strict\n          @cache_coercer = strict ? DryTypes::StrictCache : DryTypes::ParamsCache\n        end\n\n        # Coerces the given value to a type which was specified during\n        # initialization as a type argument.\n        #\n        # @param val [Object]\n        def call(val)\n          return if val.nil?\n\n          @coercer[val]\n        rescue Dry::Types::CoercionError\n          InvalidValue.new\n        end\n\n        protected\n\n        attr_reader :type, :strict, :cache_coercer\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/types/file.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Types\n      # Implementation for parameters that are multipart file objects.\n      # Actual handling of these objects is provided by +Rack::Request+;\n      # this class is here only to assert that rack's handling has succeeded.\n      class File\n        class << self\n          def parse(input)\n            return if input.nil?\n            return InvalidValue.new unless parsed?(input)\n\n            # Processing of multipart file objects\n            # is already taken care of by Rack::Request.\n            # Nothing to do here.\n            input\n          end\n\n          def parsed?(value)\n            # Rack::Request creates a Hash with filename,\n            # content type and an IO object. Do a bit of basic\n            # duck-typing.\n            value.is_a?(::Hash) && value.key?(:tempfile) && value[:tempfile].is_a?(Tempfile)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/types/invalid_value.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Types\n      # Instances of this class may be used as tokens to denote that a parameter value could not be\n      # coerced. The given message will be used as a validation error.\n      class InvalidValue\n        attr_reader :message\n\n        def initialize(message = nil)\n          @message = message\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/types/json.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Types\n      # Handles coercion and type checking for parameters that are complex\n      # types given as JSON-encoded strings. It accepts both JSON objects\n      # and arrays of objects, and will coerce the input to a +Hash+\n      # or +Array+ object respectively. In either case the Grape\n      # validation system will apply nested validation rules to\n      # all returned objects.\n      class Json\n        class << self\n          # Coerce the input into a JSON-like data structure.\n          #\n          # @param input [String] a JSON-encoded parameter value\n          # @return [Hash,Array<Hash>,nil]\n          def parse(input)\n            return input if parsed?(input)\n\n            # Allow nulls and blank strings\n            return if input.nil? || input.match?(/^\\s*$/)\n\n            JSON.parse(input, symbolize_names: true)\n          end\n\n          # Checks that the input was parsed successfully\n          # and isn't something odd such as an array of primitives.\n          #\n          # @param value [Object] result of {#parse}\n          # @return [true,false]\n          def parsed?(value)\n            value.is_a?(::Hash) || coerced_collection?(value)\n          end\n\n          protected\n\n          # Is the value an array of JSON-like objects?\n          #\n          # @param value [Object] result of {#parse}\n          # @return [true,false]\n          def coerced_collection?(value)\n            value.is_a?(::Array) && value.all?(::Hash)\n          end\n        end\n      end\n\n      # Specialization of the {Json} attribute that is guaranteed\n      # to return an array of objects. Accepts both JSON-encoded\n      # objects and arrays of objects, but wraps single objects\n      # in an Array.\n      class JsonArray < Json\n        class << self\n          # See {Json#parse}. Wraps single objects in an array.\n          #\n          # @param input [String] JSON-encoded parameter value\n          # @return [Array<Hash>]\n          def parse(input)\n            json = super\n            Array.wrap(json) unless json.nil?\n          end\n\n          # See {Json#coerced_collection?}\n          def parsed?(value)\n            coerced_collection? value\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/types/multiple_type_coercer.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Types\n      # This class is intended for use with Grape endpoint parameters that\n      # have been declared to be of variant-type using the +:types+ option.\n      # +MultipleTypeCoercer+ will build a coercer for each type declared\n      # in the array passed to +:types+ using {Types.build_coercer}. It will\n      # apply these coercers to parameter values in the order given to\n      # +:types+, and will return the value returned by the first coercer\n      # to successfully coerce the parameter value. Therefore if +String+ is\n      # an allowed type it should be declared last, since it will always\n      # successfully \"coerce\" the value.\n      class MultipleTypeCoercer\n        # Construct a new coercer that will attempt to coerce\n        # values to the given list of types in the given order.\n        #\n        # @param types [Array<Class>] list of allowed types\n        # @param method [#call,#parse] method by which values should be\n        #   coerced. See class docs for default behaviour.\n        def initialize(types, method = nil)\n          @method = method.respond_to?(:parse) ? method.method(:parse) : method\n\n          @type_coercers = types.map do |type|\n            if Types.multiple? type\n              VariantCollectionCoercer.new type, @method\n            else\n              Types.build_coercer type, strict: !@method.nil?\n            end\n          end\n        end\n\n        # Coerces the given value.\n        #\n        # @param val [String] value to be coerced, in grape\n        #   this should always be a string.\n        # @return [Object,InvalidValue] the coerced result, or an instance\n        #   of {InvalidValue} if the value could not be coerced.\n        def call(val)\n          # once the value is coerced by the custom method, its type should be checked\n          val = @method.call(val) if @method\n\n          coerced_val = InvalidValue.new\n\n          @type_coercers.each do |coercer|\n            coerced_val = coercer.call(val)\n\n            return coerced_val unless coerced_val.is_a?(InvalidValue)\n          end\n\n          coerced_val\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/types/primitive_coercer.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Types\n      # Coerces the given value to a type defined via a +type+ argument during\n      # initialization. When +strict+ is true, it doesn't coerce a value but check\n      # that it has the proper type.\n      class PrimitiveCoercer < DryTypeCoercer\n        def initialize(type, strict = false)\n          super\n\n          @coercer = cache_coercer[type]\n        end\n\n        def call(val)\n          return InvalidValue.new if reject?(val)\n          return nil if val.nil? || treat_as_nil?(val)\n\n          super\n        end\n\n        protected\n\n        attr_reader :type\n\n        # This method maintains logic which was defined by Virtus. For example,\n        # dry-types is ok to convert an array or a hash to a string, it is supported,\n        # but Virtus wouldn't accept it. So, this method only exists to not introduce\n        # breaking changes.\n        def reject?(val)\n          (val.is_a?(Array) && type == String) ||\n            (val.is_a?(String) && type == Hash) ||\n            (val.is_a?(Hash) && type == String)\n        end\n\n        # Dry-Types treats an empty string as invalid. However, Grape considers an empty string as\n        # absence of a value and coerces it into nil. See a discussion there\n        # https://github.com/ruby-grape/grape/pull/2045\n        def treat_as_nil?(val)\n          val == '' && type != String\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/types/set_coercer.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Types\n      # Takes the given array and converts it to a set. Every element of the set\n      # is also coerced.\n      class SetCoercer < ArrayCoercer\n        def initialize(type, strict = false)\n          super\n\n          @coercer = nil\n        end\n\n        def call(value)\n          return InvalidValue.new unless value.is_a?(Array)\n\n          coerce_elements(value)\n        end\n\n        protected\n\n        def coerce_elements(collection)\n          collection.each_with_object(Set.new) do |elem, memo|\n            coerced_elem = elem_coercer.call(elem)\n\n            return coerced_elem if coerced_elem.is_a?(InvalidValue)\n\n            memo.add(coerced_elem)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/types/variant_collection_coercer.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Types\n      # This class wraps {MultipleTypeCoercer}, for use with collections\n      # that allow members of more than one type.\n      class VariantCollectionCoercer\n        # Construct a new coercer that will attempt to coerce\n        # a list of values such that all members are of one of\n        # the given types. The container may also optionally be\n        # coerced to a +Set+. An arbitrary coercion +method+ may\n        # be supplied, which will be passed the entire collection\n        # as a parameter and should return a new collection, or\n        # may return the same one if no coercion was required.\n        #\n        # @param types [Array<Class>,Set<Class>] list of allowed types,\n        #   also specifying the container type\n        # @param method [#call,#parse] method by which values should be coerced\n        def initialize(types, method = nil)\n          @types = types\n          @method = method.respond_to?(:parse) ? method.method(:parse) : method\n\n          # If we have a coercion method, pass it in here to save\n          # building another one, even though we call it directly.\n          @member_coercer = MultipleTypeCoercer.new types, method\n        end\n\n        # Coerce the given value.\n        #\n        # @param value [Array<String>] collection of values to be coerced\n        # @return [Array<Object>,Set<Object>,InvalidValue]\n        #   the coerced result, or an instance\n        #   of {InvalidValue} if the value could not be coerced.\n        def call(value)\n          return unless value.is_a? Array\n\n          value =\n            if @method\n              @method.call(value)\n            else\n              value.map { |v| @member_coercer.call(v) }\n            end\n          return Set.new value if @types.is_a? Set\n\n          value\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/types.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    # Module for code related to grape's system for\n    # coercion and type validation of incoming request\n    # parameters.\n    #\n    # Grape uses a number of tests and assertions to\n    # work out exactly how a parameter should be handled,\n    # based on the +type+ and +coerce_with+ options that\n    # may be supplied to {Grape::Dsl::Parameters#requires}\n    # and {Grape::Dsl::Parameters#optional}. The main\n    # entry point for this process is {Types.build_coercer}.\n    module Types\n      module_function\n\n      PRIMITIVES = [\n        # Numerical\n        Integer,\n        Float,\n        BigDecimal,\n        Numeric,\n\n        # Date/time\n        Date,\n        DateTime,\n        Time,\n\n        # Misc\n        Grape::API::Boolean,\n        String,\n        Symbol,\n        TrueClass,\n        FalseClass\n      ].freeze\n\n      # Types representing data structures.\n      STRUCTURES = [Hash, Array, Set].freeze\n\n      SPECIAL = {\n        ::JSON => Json,\n        Array[JSON] => JsonArray,\n        ::File => File,\n        Rack::Multipart::UploadedFile => File\n      }.freeze\n\n      GROUPS = [Array, Hash, JSON, Array[JSON]].freeze\n\n      # Is the given class a primitive type as recognized by Grape?\n      #\n      # @param type [Class] type to check\n      # @return [Boolean] whether or not the type is known by Grape as a valid\n      #   type for a single value\n      def primitive?(type)\n        PRIMITIVES.include?(type)\n      end\n\n      # Is the given class a standard data structure (collection or map)\n      # as recognized by Grape?\n      #\n      # @param type [Class] type to check\n      # @return [Boolean] whether or not the type is known by Grape as a valid\n      #   data structure type\n      def structure?(type)\n        STRUCTURES.include?(type)\n      end\n\n      # Is the declared type in fact an array of multiple allowed types?\n      # For example the declaration +types: [Integer,String]+ will attempt\n      # first to coerce given values to integer, but will also accept any\n      # other string.\n      #\n      # @param type [Array<Class>,Set<Class>] type (or type list!) to check\n      # @return [Boolean] +true+ if the given value will be treated as\n      #   a list of types.\n      def multiple?(type)\n        (type.is_a?(Array) || type.is_a?(Set)) && type.size > 1\n      end\n\n      # Does Grape provide special coercion and validation\n      # routines for the given class? This does not include\n      # automatic handling for primitives, structures and\n      # otherwise recognized types. See {Types::SPECIAL}.\n      #\n      # @param type [Class] type to check\n      # @return [Boolean] +true+ if special routines are available\n      def special?(type)\n        SPECIAL.key? type\n      end\n\n      # Is the declared type a supported group type?\n      # Currently supported group types are Array, Hash, JSON, and Array[JSON]\n      #\n      # @param type [Array<Class>,Class] type to check\n      # @return [Boolean] +true+ if the type is a supported group type\n      def group?(type)\n        GROUPS.include? type\n      end\n\n      # A valid custom type must implement a class-level `parse` method, taking\n      # one String argument and returning the parsed value in its correct type.\n      #\n      # @param type [Class] type to check\n      # @return [Boolean] whether or not the type can be used as a custom type\n      def custom?(type)\n        !primitive?(type) &&\n          !structure?(type) &&\n          !multiple?(type) &&\n          type.respond_to?(:parse) &&\n          type.method(:parse).arity == 1\n      end\n\n      # Is the declared type an +Array+ or +Set+ of a {#custom?} type?\n      #\n      # @param type [Array<Class>,Class] type to check\n      # @return [Boolean] true if +type+ is a collection of a type that implements\n      #   its own +#parse+ method.\n      def collection_of_custom?(type)\n        (type.is_a?(Array) || type.is_a?(Set)) &&\n          type.length == 1 &&\n          (custom?(type.first) || special?(type.first))\n      end\n\n      def map_special(type)\n        SPECIAL.fetch(type, type)\n      end\n\n      # Chooses the best coercer for the given type. For example, if the type\n      # is Integer, it will return a coercer which will be able to coerce a value\n      # to the integer.\n      #\n      # There are a few very special coercers which might be returned.\n      #\n      # +Grape::Types::MultipleTypeCoercer+ is a coercer which is returned when\n      # the given type implies values in an array with different types.\n      # For example, +[Integer, String]+ allows integer and string values in\n      # an array.\n      #\n      # +Grape::Types::CustomTypeCoercer+ is a coercer which is returned when\n      # a method is specified by a user with +coerce_with+ option or the user\n      # specifies a custom type which implements requirments of\n      # +Grape::Types::CustomTypeCoercer+.\n      #\n      # +Grape::Types::CustomTypeCollectionCoercer+ is a very similar to the\n      # previous one, but it expects an array or set of values having a custom\n      # type implemented by the user.\n      #\n      # There is also a group of custom types implemented by Grape, check\n      # +Grape::Validations::Types::SPECIAL+ to get the full list.\n      #\n      # @param type [Class] the type to which input strings\n      #   should be coerced\n      # @param method [Class,#call] the coercion method to use\n      # @return [Object] object to be used\n      #   for coercion and type validation\n      def build_coercer(type, method: nil, strict: false)\n        # no cache since unique\n        return create_coercer_instance(type, method, strict) if method.respond_to?(:call)\n\n        CoercerCache[[type, method, strict]]\n      end\n\n      def create_coercer_instance(type, method, strict)\n        # Maps a custom type provided by Grape, it doesn't map types wrapped by collections!!!\n        type = Types.map_special(type)\n\n        # Use a special coercer for multiply-typed parameters.\n        if Types.multiple?(type)\n          MultipleTypeCoercer.new(type, method)\n\n          # Use a special coercer for custom types and coercion methods.\n        elsif method || Types.custom?(type)\n          CustomTypeCoercer.new(type, method)\n\n          # Special coercer for collections of types that implement a parse method.\n          # CustomTypeCoercer (above) already handles such types when an explicit coercion\n          # method is supplied.\n        elsif Types.collection_of_custom?(type)\n          Types::CustomTypeCollectionCoercer.new(\n            Types.map_special(type.first), type.is_a?(Set)\n          )\n        else\n          DryTypeCoercer.coercer_instance_for(type, strict)\n        end\n      end\n\n      class CoercerCache < Grape::Util::Cache\n        def initialize\n          super\n          @cache = Hash.new do |h, (type, method, strict)|\n            h[[type, method, strict]] = Grape::Validations::Types.create_coercer_instance(type, method, strict)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/validator_factory.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    class ValidatorFactory\n      def self.create_validator(options)\n        options[:validator_class].new(options[:attributes],\n                                      options[:options],\n                                      options[:required],\n                                      options[:params_scope],\n                                      options[:opts])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/validators/all_or_none_of_validator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Validators\n      class AllOrNoneOfValidator < MultipleParamsBase\n        def validate_params!(params)\n          keys = keys_in_common(params)\n          return if keys.empty? || keys.length == all_keys.length\n\n          raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:all_or_none))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/validators/allow_blank_validator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Validators\n      class AllowBlankValidator < Base\n        def validate_param!(attr_name, params)\n          return if (options_key?(:value) ? @option[:value] : @option) || !params.is_a?(Hash)\n\n          value = params[attr_name]\n          value = value.scrub if value.respond_to?(:valid_encoding?) && !value.valid_encoding?\n\n          return if value == false || value.present?\n\n          raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:blank))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/validators/as_validator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Validators\n      class AsValidator < Base\n        # We use a validator for renaming parameters. This is just a marker for\n        # the parameter scope to handle the renaming. No actual validation\n        # happens here.\n        def validate_param!(*); end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/validators/at_least_one_of_validator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Validators\n      class AtLeastOneOfValidator < MultipleParamsBase\n        def validate_params!(params)\n          return unless keys_in_common(params).empty?\n\n          raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:at_least_one))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/validators/base.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Validators\n      class Base\n        include Grape::Util::Translation\n\n        attr_reader :attrs\n\n        # Creates a new Validator from options specified\n        # by a +requires+ or +optional+ directive during\n        # parameter definition.\n        # @param attrs [Array] names of attributes to which the Validator applies\n        # @param options [Object] implementation-dependent Validator options\n        # @param required [Boolean] attribute(s) are required or optional\n        # @param scope [ParamsScope] parent scope for this Validator\n        # @param opts [Hash] additional validation options\n        def initialize(attrs, options, required, scope, opts)\n          @attrs = Array(attrs)\n          @option = options\n          @required = required\n          @scope = scope\n          @fail_fast = opts[:fail_fast]\n          @allow_blank = opts[:allow_blank]\n        end\n\n        # Validates a given request.\n        # @note Override #validate! unless you need to access the entire request.\n        # @param request [Grape::Request] the request currently being handled\n        # @raise [Grape::Exceptions::Validation] if validation failed\n        # @return [void]\n        def validate(request)\n          return unless @scope.should_validate?(request.params)\n\n          validate!(request.params)\n        end\n\n        # Validates a given parameter hash.\n        # @note Override #validate if you need to access the entire request.\n        # @param params [Hash] parameters to validate\n        # @raise [Grape::Exceptions::Validation] if validation failed\n        # @return [void]\n        def validate!(params)\n          attributes = SingleAttributeIterator.new(@attrs, @scope, params)\n          # we collect errors inside array because\n          # there may be more than one error per field\n          array_errors = []\n\n          attributes.each do |val, attr_name, empty_val|\n            next if !@scope.required? && empty_val\n            next unless @scope.meets_dependency?(val, params)\n\n            validate_param!(attr_name, val) if @required || (val.respond_to?(:key?) && val.key?(attr_name))\n          rescue Grape::Exceptions::Validation => e\n            array_errors << e\n          end\n\n          raise Grape::Exceptions::ValidationArrayErrors.new(array_errors) if array_errors.any?\n        end\n\n        def self.inherited(klass)\n          super\n          Validations.register(klass)\n        end\n\n        def message(default_key = nil)\n          options = instance_variable_get(:@option)\n          options_key?(:message) ? options[:message] : default_key\n        end\n\n        def options_key?(key, options = nil)\n          options = instance_variable_get(:@option) if options.nil?\n          options.respond_to?(:key?) && options.key?(key) && !options[key].nil?\n        end\n\n        def fail_fast?\n          @fail_fast\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/validators/coerce_validator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Validators\n      class CoerceValidator < Base\n        def initialize(attrs, options, required, scope, opts)\n          super\n\n          @converter = if type.is_a?(Grape::Validations::Types::VariantCollectionCoercer)\n                         type\n                       else\n                         Types.build_coercer(type, method: @option[:method])\n                       end\n        end\n\n        def validate_param!(attr_name, params)\n          raise validation_exception(attr_name) unless params.is_a? Hash\n\n          new_value = coerce_value(params[attr_name])\n\n          raise validation_exception(attr_name, new_value.message) unless valid_type?(new_value)\n\n          # Don't assign a value if it is identical. It fixes a problem with Hashie::Mash\n          # which looses wrappers for hashes and arrays after reassigning values\n          #\n          #     h = Hashie::Mash.new(list: [1, 2, 3, 4])\n          #     => #<Hashie::Mash list=#<Hashie::Array [1, 2, 3, 4]>>\n          #     list = h.list\n          #     h[:list] = list\n          #     h\n          #     => #<Hashie::Mash list=[1, 2, 3, 4]>\n          return if params[attr_name].instance_of?(new_value.class) && params[attr_name] == new_value\n\n          params[attr_name] = new_value\n        end\n\n        private\n\n        # @!attribute [r] converter\n        # Object that will be used for parameter coercion and type checking.\n        #\n        # See {Types.build_coercer}\n        #\n        # @return [Object]\n        attr_reader :converter\n\n        def valid_type?(val)\n          !val.is_a?(Types::InvalidValue)\n        end\n\n        def coerce_value(val)\n          converter.call(val)\n          # Some custom types might fail, so it should be treated as an invalid value\n        rescue StandardError\n          Types::InvalidValue.new\n        end\n\n        # Type to which the parameter will be coerced.\n        #\n        # @return [Class]\n        def type\n          @option[:type].is_a?(Hash) ? @option[:type][:value] : @option[:type]\n        end\n\n        def validation_exception(attr_name, custom_msg = nil)\n          Grape::Exceptions::Validation.new(\n            params: [@scope.full_name(attr_name)],\n            message: custom_msg || message(:coerce)\n          )\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/validators/contract_scope_validator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Validators\n      class ContractScopeValidator < Base\n        attr_reader :schema\n\n        def initialize(_attrs, _options, _required, _scope, opts)\n          super\n          @schema = opts.fetch(:schema)\n        end\n\n        # Validates a given request.\n        # @param request [Grape::Request] the request currently being handled\n        # @raise [Grape::Exceptions::ValidationArrayErrors] if validation failed\n        # @return [void]\n        def validate(request)\n          res = schema.call(request.params)\n\n          if res.success?\n            request.params.deep_merge!(res.to_h)\n            return\n          end\n\n          raise Grape::Exceptions::ValidationArrayErrors.new(build_errors_from_messages(res.errors.messages))\n        end\n\n        private\n\n        def build_errors_from_messages(messages)\n          messages.map do |message|\n            full_name = message.path.first.to_s\n            full_name << \"[#{message.path[1..].join('][')}]\" if message.path.size > 1\n            Grape::Exceptions::Validation.new(params: [full_name], message: message.text)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/validators/default_validator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Validators\n      class DefaultValidator < Base\n        def initialize(attrs, options, required, scope, opts = {})\n          @default = options\n          super\n        end\n\n        def validate_param!(attr_name, params)\n          params[attr_name] = if @default.is_a? Proc\n                                if @default.parameters.empty?\n                                  @default.call\n                                else\n                                  @default.call(params)\n                                end\n                              elsif @default.frozen? || !@default.duplicable?\n                                @default\n                              else\n                                @default.dup\n                              end\n        end\n\n        def validate!(params)\n          attrs = SingleAttributeIterator.new(@attrs, @scope, params)\n          attrs.each do |resource_params, attr_name|\n            next unless @scope.meets_dependency?(resource_params, params)\n\n            validate_param!(attr_name, resource_params) if resource_params.is_a?(Hash) && resource_params[attr_name].nil?\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/validators/exactly_one_of_validator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Validators\n      class ExactlyOneOfValidator < MultipleParamsBase\n        def validate_params!(params)\n          keys = keys_in_common(params)\n          return if keys.length == 1\n          raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) if keys.empty?\n\n          raise Grape::Exceptions::Validation.new(params: keys, message: message(:mutual_exclusion))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/validators/except_values_validator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Validators\n      class ExceptValuesValidator < Base\n        def initialize(attrs, options, required, scope, opts)\n          @except = options.is_a?(Hash) ? options[:value] : options\n          super\n        end\n\n        def validate_param!(attr_name, params)\n          return unless params.respond_to?(:key?) && params.key?(attr_name)\n\n          excepts = @except.is_a?(Proc) ? @except.call : @except\n          return if excepts.nil?\n\n          param_array = params[attr_name].nil? ? [nil] : Array.wrap(params[attr_name])\n          raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:except_values)) if param_array.any? { |param| excepts.include?(param) }\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/validators/length_validator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Validators\n      class LengthValidator < Base\n        def initialize(attrs, options, required, scope, opts)\n          @min = options[:min]\n          @max = options[:max]\n          @is = options[:is]\n\n          super\n\n          raise ArgumentError, 'min must be an integer greater than or equal to zero' if !@min.nil? && (!@min.is_a?(Integer) || @min.negative?)\n          raise ArgumentError, 'max must be an integer greater than or equal to zero' if !@max.nil? && (!@max.is_a?(Integer) || @max.negative?)\n          raise ArgumentError, \"min #{@min} cannot be greater than max #{@max}\" if !@min.nil? && !@max.nil? && @min > @max\n\n          return if @is.nil?\n          raise ArgumentError, 'is must be an integer greater than zero' if !@is.is_a?(Integer) || !@is.positive?\n          raise ArgumentError, 'is cannot be combined with min or max' if !@min.nil? || !@max.nil?\n        end\n\n        def validate_param!(attr_name, params)\n          param = params[attr_name]\n\n          return unless param.respond_to?(:length)\n\n          return unless (!@min.nil? && param.length < @min) || (!@max.nil? && param.length > @max) || (!@is.nil? && param.length != @is)\n\n          raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: build_message)\n        end\n\n        def build_message\n          if options_key?(:message)\n            @option[:message]\n          elsif @min && @max\n            translate(:length, min: @min, max: @max)\n          elsif @min\n            translate(:length_min, min: @min)\n          elsif @max\n            translate(:length_max, max: @max)\n          else\n            translate(:length_is, is: @is)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/validators/multiple_params_base.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Validators\n      class MultipleParamsBase < Base\n        def validate!(params)\n          attributes = MultipleAttributesIterator.new(@attrs, @scope, params)\n          array_errors = []\n\n          attributes.each do |resource_params|\n            validate_params!(resource_params)\n          rescue Grape::Exceptions::Validation => e\n            array_errors << e\n          end\n\n          raise Grape::Exceptions::ValidationArrayErrors.new(array_errors) if array_errors.any?\n        end\n\n        private\n\n        def keys_in_common(resource_params)\n          return [] unless resource_params.is_a?(Hash)\n\n          all_keys & resource_params.keys.map! { |attr| @scope.full_name(attr) }\n        end\n\n        def all_keys\n          @attrs.map { |attr| @scope.full_name(attr) }\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/validators/mutually_exclusive_validator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Validators\n      class MutuallyExclusiveValidator < MultipleParamsBase\n        def validate_params!(params)\n          keys = keys_in_common(params)\n          return if keys.length <= 1\n\n          raise Grape::Exceptions::Validation.new(params: keys, message: message(:mutual_exclusion))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/validators/presence_validator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Validators\n      class PresenceValidator < Base\n        def validate_param!(attr_name, params)\n          return if params.respond_to?(:key?) && params.key?(attr_name)\n\n          raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:presence))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/validators/regexp_validator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Validators\n      class RegexpValidator < Base\n        def validate_param!(attr_name, params)\n          return unless params.respond_to?(:key) && params.key?(attr_name)\n\n          value = options_key?(:value) ? @option[:value] : @option\n          return if Array.wrap(params[attr_name]).all? { |param| param.nil? || scrub(param.to_s).match?(value) }\n\n          raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:regexp))\n        end\n\n        private\n\n        def scrub(param)\n          return param if param.valid_encoding?\n\n          param.scrub\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/validators/same_as_validator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Validators\n      class SameAsValidator < Base\n        def validate_param!(attr_name, params)\n          confirmation = options_key?(:value) ? @option[:value] : @option\n          return if params[attr_name] == params[confirmation]\n\n          raise Grape::Exceptions::Validation.new(\n            params: [@scope.full_name(attr_name)],\n            message: build_message\n          )\n        end\n\n        private\n\n        def build_message\n          if options_key?(:message)\n            @option[:message]\n          else\n            translate(:same_as, parameter: @option)\n          end\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations/validators/values_validator.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    module Validators\n      class ValuesValidator < Base\n        def initialize(attrs, options, required, scope, opts)\n          @values = options.is_a?(Hash) ? options[:value] : options\n          super\n        end\n\n        def validate_param!(attr_name, params)\n          return unless params.is_a?(Hash)\n\n          val = params[attr_name]\n\n          return if val.nil? && !required_for_root_scope?\n\n          val = val.scrub if val.respond_to?(:valid_encoding?) && !val.valid_encoding?\n\n          # don't forget that +false.blank?+ is true\n          return if val != false && val.blank? && @allow_blank\n\n          return if check_values?(val, attr_name)\n\n          raise Grape::Exceptions::Validation.new(\n            params: [@scope.full_name(attr_name)],\n            message: message(:values)\n          )\n        end\n\n        private\n\n        def check_values?(val, attr_name)\n          values = @values.is_a?(Proc) && @values.arity.zero? ? @values.call : @values\n          return true if values.nil?\n\n          param_array = val.nil? ? [nil] : Array.wrap(val)\n          return param_array.all? { |param| values.include?(param) } unless values.is_a?(Proc)\n\n          begin\n            param_array.all? { |param| values.call(param) }\n          rescue StandardError => e\n            warn \"Error '#{e}' raised while validating attribute '#{attr_name}'\"\n            false\n          end\n        end\n\n        def required_for_root_scope?\n          return false unless @required\n\n          scope = @scope\n          scope = scope.parent while scope.lateral?\n\n          scope.root?\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/validations.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  module Validations\n    extend Grape::Util::Registry\n\n    module_function\n\n    def require_validator(short_name)\n      raise Grape::Exceptions::UnknownValidator, short_name unless registry.key?(short_name)\n\n      registry[short_name]\n    end\n\n    def build_short_name(klass)\n      return if klass.name.blank?\n\n      klass.name.demodulize.underscore.delete_suffix('_validator')\n    end\n  end\nend\n"
  },
  {
    "path": "lib/grape/version.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  # The current version of Grape.\n  VERSION = '3.2.0'\nend\n"
  },
  {
    "path": "lib/grape/xml.rb",
    "content": "# frozen_string_literal: true\n\nmodule Grape\n  if defined?(::MultiXml)\n    Xml = ::MultiXml\n  else\n    Xml = ::ActiveSupport::XmlMini\n    Xml::ParseError = StandardError\n  end\nend\n"
  },
  {
    "path": "lib/grape.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'logger'\nrequire 'active_support'\nrequire 'active_support/version'\nrequire 'active_support/isolated_execution_state'\nrequire 'active_support/core_ext/array/conversions' # to_xml\nrequire 'active_support/core_ext/array/wrap'\nrequire 'active_support/core_ext/hash/conversions' # to_xml\nrequire 'active_support/core_ext/hash/deep_merge'\nrequire 'active_support/core_ext/hash/deep_transform_values'\nrequire 'active_support/core_ext/hash/indifferent_access'\nrequire 'active_support/core_ext/hash/reverse_merge'\nrequire 'active_support/core_ext/module/delegation' # delegate_missing_to\nrequire 'active_support/core_ext/object/blank'\nrequire 'active_support/core_ext/object/deep_dup'\nrequire 'active_support/core_ext/object/duplicable'\nrequire 'active_support/deprecation'\nrequire 'active_support/inflector'\nrequire 'active_support/ordered_options'\nrequire 'active_support/notifications'\n\nrequire 'English'\nrequire 'bigdecimal'\nrequire 'date'\nrequire 'dry-types'\nrequire 'dry-configurable'\nrequire 'forwardable'\nrequire 'json'\nrequire 'mustermann/grape'\nrequire 'pathname'\nrequire 'rack'\nrequire 'rack/auth/basic'\nrequire 'rack/builder'\nrequire 'rack/head'\nrequire 'singleton'\nrequire 'zeitwerk'\n\nloader = Zeitwerk::Loader.for_gem\nloader.inflector.inflect(\n  'api' => 'API',\n  'dsl' => 'DSL'\n)\nrailtie = \"#{__dir__}/grape/railtie.rb\"\nloader.do_not_eager_load(railtie)\nloader.setup\n\nI18n.load_path << File.expand_path('grape/locale/en.yml', __dir__)\n\nmodule Grape\n  extend Dry::Configurable\n\n  setting :param_builder, default: :hash_with_indifferent_access\n  setting :lint, default: false\n\n  HTTP_SUPPORTED_METHODS = [\n    Rack::GET,\n    Rack::POST,\n    Rack::PUT,\n    Rack::PATCH,\n    Rack::DELETE,\n    Rack::HEAD,\n    Rack::OPTIONS\n  ].freeze\n\n  def self.deprecator\n    @deprecator ||= ActiveSupport::Deprecation.new('2.0', 'Grape')\n  end\nend\n\n# https://api.rubyonrails.org/classes/ActiveSupport/Deprecation.html\n# adding Grape.deprecator to Rails App if any\nrequire 'grape/railtie' if defined?(Rails::Railtie)\nloader.eager_load\n"
  },
  {
    "path": "spec/grape/api/custom_validations_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations do\n  describe 'using a custom length validator' do\n    subject do\n      Class.new(Grape::API) do\n        params do\n          requires :text, default_length: 140\n        end\n        get do\n          'bacon'\n        end\n      end\n    end\n\n    let(:default_length_validator) do\n      Class.new(Grape::Validations::Validators::Base) do\n        def validate_param!(attr_name, params)\n          @option = params[:max].to_i if params.key?(:max)\n          return if params[attr_name].length <= @option\n\n          raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: \"must be at the most #{@option} characters long\")\n        end\n      end\n    end\n    let(:app) { subject }\n\n    before do\n      stub_const('DefaultLengthValidator', default_length_validator)\n      described_class.register(DefaultLengthValidator)\n    end\n\n    after do\n      described_class.deregister(:default_length)\n    end\n\n    it 'under 140 characters' do\n      get '/', text: 'abc'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'bacon'\n    end\n\n    it 'over 140 characters' do\n      get '/', text: 'a' * 141\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq 'text must be at the most 140 characters long'\n    end\n\n    it 'specified in the query string' do\n      get '/', text: 'a' * 141, max: 141\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'bacon'\n    end\n  end\n\n  describe 'using a custom body-only validator' do\n    subject do\n      Class.new(Grape::API) do\n        params do\n          requires :text, in_body: true\n        end\n        get do\n          'bacon'\n        end\n      end\n    end\n\n    let(:in_body_validator) do\n      Class.new(Grape::Validations::Validators::PresenceValidator) do\n        def validate(request)\n          validate!(request.env[Grape::Env::API_REQUEST_BODY])\n        end\n      end\n    end\n    let(:app) { subject }\n\n    before do\n      stub_const('InBodyValidator', in_body_validator)\n      described_class.register(InBodyValidator)\n    end\n\n    after do\n      described_class.deregister(:in_body)\n    end\n\n    it 'allows field in body' do\n      get '/', text: 'abc'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'bacon'\n    end\n\n    it 'ignores field in query' do\n      get '/', nil, text: 'abc'\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq 'text is missing'\n    end\n  end\n\n  describe 'using a custom validator with message_key' do\n    subject do\n      Class.new(Grape::API) do\n        params do\n          requires :text, with_message_key: true\n        end\n        get do\n          'bacon'\n        end\n      end\n    end\n\n    let(:message_key_validator) do\n      Class.new(Grape::Validations::Validators::PresenceValidator) do\n        def validate_param!(attr_name, _params)\n          raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: :presence)\n        end\n      end\n    end\n    let(:app) { subject }\n\n    before do\n      stub_const('WithMessageKeyValidator', message_key_validator)\n      described_class.register(WithMessageKeyValidator)\n    end\n\n    after do\n      described_class.deregister(:with_message_key)\n    end\n\n    it 'fails with message' do\n      get '/', text: 'foobar'\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq 'text is missing'\n    end\n  end\n\n  describe 'using a custom request/param validator' do\n    subject do\n      Class.new(Grape::API) do\n        params do\n          optional :admin_field, type: String, admin: true\n          optional :non_admin_field, type: String\n          optional :admin_false_field, type: String, admin: false\n        end\n        get do\n          'bacon'\n        end\n      end\n    end\n\n    let(:admin_validator) do\n      Class.new(Grape::Validations::Validators::Base) do\n        def validate(request)\n          # return if the param we are checking was not in request\n          # @attrs is a list containing the attribute we are currently validating\n          return unless request.params.key? @attrs.first\n          # check if admin flag is set to true\n          return unless @option\n\n          # check if user is admin or not\n          # as an example get a token from request and check if it's admin or not\n          raise Grape::Exceptions::Validation.new(params: @attrs, message: 'Can not set Admin only field.') unless request.headers[access_header] == 'admin'\n        end\n\n        def access_header\n          'x-access-token'\n        end\n      end\n    end\n\n    let(:app) { subject }\n    let(:x_access_token_header) { 'x-access-token' }\n\n    before do\n      stub_const('AdminValidator', admin_validator)\n      described_class.register(AdminValidator)\n    end\n\n    after do\n      described_class.deregister(:admin)\n    end\n\n    it 'fail when non-admin user sets an admin field' do\n      get '/', admin_field: 'tester', non_admin_field: 'toaster'\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to include 'Can not set Admin only field.'\n    end\n\n    it 'does not fail when we send non-admin fields only' do\n      get '/', non_admin_field: 'toaster'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'bacon'\n    end\n\n    it 'does not fail when we send non-admin and admin=false fields only' do\n      get '/', non_admin_field: 'toaster', admin_false_field: 'test'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'bacon'\n    end\n\n    it 'does not fail when we send admin fields and we are admin' do\n      header x_access_token_header, 'admin'\n      get '/', admin_field: 'tester', non_admin_field: 'toaster', admin_false_field: 'test'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'bacon'\n    end\n\n    it 'fails when we send admin fields and we are not admin' do\n      header x_access_token_header, 'user'\n      get '/', admin_field: 'tester', non_admin_field: 'toaster', admin_false_field: 'test'\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to include 'Can not set Admin only field.'\n    end\n  end\n\n  describe 'using a custom validator with instance variable' do\n    let(:validator_type) do\n      Class.new(Grape::Validations::Validators::Base) do\n        def validate_param!(_attr_name, _params)\n          if @instance_variable\n            raise Grape::Exceptions::Validation.new(params: ['params'],\n                                                    message: 'This should never happen')\n          end\n          @instance_variable = true\n        end\n      end\n    end\n    let(:app) do\n      Class.new(Grape::API) do\n        params do\n          optional :param_to_validate, instance_validator: true\n          optional :another_param_to_validate, instance_validator: true\n        end\n        get do\n          'noop'\n        end\n      end\n    end\n\n    before do\n      stub_const('InstanceValidatorValidator', validator_type)\n      described_class.register(InstanceValidatorValidator)\n    end\n\n    after do\n      described_class.deregister(:instance_validator)\n    end\n\n    it 'passes validation every time' do\n      expect(validator_type).to receive(:new).twice.and_call_original\n      get '/', param_to_validate: 'value', another_param_to_validate: 'value'\n      expect(last_response.status).to eq 200\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/deeply_included_options_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::API do\n  let(:app) do\n    main_api = api\n    Class.new(Grape::API) do\n      mount main_api\n    end\n  end\n\n  let(:api) do\n    deeply_included_options = options\n    Class.new(Grape::API) do\n      include deeply_included_options\n\n      resource :users do\n        get do\n          status 200\n        end\n      end\n    end\n  end\n\n  let(:options) do\n    deep_included_options_default = default\n    Module.new do\n      extend ActiveSupport::Concern\n      include deep_included_options_default\n    end\n  end\n\n  let(:default) do\n    Module.new do\n      extend ActiveSupport::Concern\n\n      included do\n        format :json\n      end\n    end\n  end\n\n  it 'works for unspecified format' do\n    get '/users'\n    expect(last_response.status).to be 200\n    expect(last_response.content_type).to eql 'application/json'\n  end\n\n  it 'works for specified format' do\n    get '/users.json'\n    expect(last_response.status).to be 200\n    expect(last_response.content_type).to eql 'application/json'\n  end\n\n  it \"doesn't work for format different than specified\" do\n    get '/users.txt'\n    expect(last_response.status).to be 404\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/defines_boolean_in_params_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::API::Instance do\n  describe 'boolean constant' do\n    let(:app) do\n      Class.new(Grape::API) do\n        params do\n          requires :message, type: Grape::API::Boolean\n        end\n        post :echo do\n          { class: params[:message].class.name, value: params[:message] }\n        end\n      end\n    end\n\n    let(:expected_body) do\n      { class: 'TrueClass', value: true }.to_s\n    end\n\n    it 'sets Boolean as a type' do\n      post '/echo?message=true'\n      expect(last_response.status).to eq(201)\n      expect(last_response.body).to eq expected_body\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/documentation_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::API do\n  subject { Class.new(described_class) }\n\n  let(:app) { subject }\n\n  context 'an endpoint with documentation' do\n    it 'documents parameters' do\n      subject.params do\n        requires 'price', type: Float, desc: 'Sales price'\n      end\n      subject.get '/'\n\n      expect(subject.routes.first.params['price']).to eq(required: true,\n                                                         type: 'Float',\n                                                         desc: 'Sales price')\n    end\n\n    it 'allows documentation with a hash' do\n      documentation = { example: 'Joe' }\n\n      subject.params do\n        requires 'first_name', documentation: documentation\n      end\n      subject.get '/'\n\n      expect(subject.routes.first.params['first_name'][:documentation]).to eq(documentation)\n    end\n  end\n\n  context 'an endpoint without documentation' do\n    before do\n      subject.do_not_document!\n\n      subject.params do\n        requires :city, type: String, desc: 'Should be ignored'\n        optional :postal_code, type: Integer\n      end\n      subject.post '/' do\n        declared(params).to_json\n      end\n    end\n\n    it 'does not document parameters for the endpoint' do\n      expect(subject.routes.first.params).to eq({})\n    end\n\n    it 'still declares params internally' do\n      data = { city: 'Berlin', postal_code: 10_115 }\n\n      post '/', data\n\n      expect(last_response.body).to eq(data.to_json)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/inherited_helpers_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::API::Helpers do\n  let(:user) { 'Miguel Caneo' }\n  let(:id)   { '42' }\n  let(:api_super_class) do\n    Class.new(Grape::API) do\n      helpers do\n        params(:superclass_params) { requires :id, type: String }\n\n        def current_user\n          params[:user]\n        end\n      end\n    end\n  end\n  let(:api_overridden_sub_class) do\n    Class.new(api_super_class) do\n      params { use :superclass_params }\n\n      helpers do\n        def current_user\n          \"#{params[:user]} with id\"\n        end\n      end\n\n      get 'resource' do\n        \"#{current_user}: #{params['id']}\"\n      end\n    end\n  end\n  let(:api_sub_class) do\n    Class.new(api_super_class) do\n      params { use :superclass_params }\n\n      get 'resource' do\n        \"#{current_user}: #{params['id']}\"\n      end\n    end\n  end\n  let(:api_example) do\n    Class.new(api_sub_class) do\n      params { use :superclass_params }\n\n      get 'resource' do\n        \"#{current_user}: #{params['id']}\"\n      end\n    end\n  end\n\n  context 'non overriding subclass' do\n    subject { api_sub_class }\n\n    def app\n      subject\n    end\n\n    context 'given expected params' do\n      it 'inherits helpers from a superclass' do\n        get '/resource', id: id, user: user\n        expect(last_response.body).to eq(\"#{user}: #{id}\")\n      end\n    end\n\n    context 'with lack of expected params' do\n      it 'returns missing error' do\n        get '/resource'\n        expect(last_response.body).to eq('id is missing')\n      end\n    end\n  end\n\n  context 'overriding subclass' do\n    def app\n      api_overridden_sub_class\n    end\n\n    context 'given expected params' do\n      it 'overrides helpers from a superclass' do\n        get '/resource', id: id, user: user\n        expect(last_response.body).to eq(\"#{user} with id: #{id}\")\n      end\n    end\n\n    context 'with lack of expected params' do\n      it 'returns missing error' do\n        get '/resource'\n        expect(last_response.body).to eq('id is missing')\n      end\n    end\n  end\n\n  context 'example subclass' do\n    def app\n      api_example\n    end\n\n    context 'given expected params' do\n      it 'inherits helpers from a superclass' do\n        get '/resource', id: id, user: user\n        expect(last_response.body).to eq(\"#{user}: #{id}\")\n      end\n    end\n\n    context 'with lack of expected params' do\n      it 'returns missing error' do\n        get '/resource'\n        expect(last_response.body).to eq('id is missing')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/instance_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'shared/versioning_examples'\n\ndescribe Grape::API::Instance do\n  subject(:an_instance) do\n    Class.new(Grape::API::Instance) do\n      namespace :some_namespace do\n        get 'some_endpoint' do\n          'success'\n        end\n      end\n    end\n  end\n\n  let(:root_api) do\n    to_mount = an_instance\n    Class.new(Grape::API) do\n      mount to_mount\n    end\n  end\n\n  def app\n    root_api\n  end\n\n  context 'when an instance is mounted on the root' do\n    it 'can call the instance endpoint' do\n      get '/some_namespace/some_endpoint'\n      expect(last_response.body).to eq 'success'\n    end\n  end\n\n  context 'when an instance is the root' do\n    let(:root_api) do\n      to_mount = an_instance\n      Class.new(Grape::API::Instance) do\n        mount to_mount\n      end\n    end\n\n    it 'can call the instance endpoint' do\n      get '/some_namespace/some_endpoint'\n      expect(last_response.body).to eq 'success'\n    end\n  end\n\n  context 'with multiple moutes' do\n    let(:first) do\n      Class.new(Grape::API::Instance) do\n        namespace(:some_namespace) do\n          route :any, '*path' do\n            error!('Not found! (1)', 404)\n          end\n        end\n      end\n    end\n    let(:second) do\n      Class.new(Grape::API::Instance) do\n        namespace(:another_namespace) do\n          route :any, '*path' do\n            error!('Not found! (2)', 404)\n          end\n        end\n      end\n    end\n    let(:root_api) do\n      first_instance = first\n      second_instance = second\n      Class.new(Grape::API) do\n        mount first_instance\n        mount first_instance\n        mount second_instance\n      end\n    end\n\n    it 'does not raise a FrozenError on first instance' do\n      expect { patch '/some_namespace/anything' }.not_to \\\n        raise_error\n    end\n\n    it 'responds the correct body at the first instance' do\n      patch '/some_namespace/anything'\n      expect(last_response.body).to eq 'Not found! (1)'\n    end\n\n    it 'does not raise a FrozenError on second instance' do\n      expect { get '/another_namespace/other' }.not_to \\\n        raise_error\n    end\n\n    it 'responds the correct body at the second instance' do\n      get '/another_namespace/foobar'\n      expect(last_response.body).to eq 'Not found! (2)'\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/invalid_format_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Endpoint do\n  subject { Class.new(Grape::API) }\n\n  def app\n    subject\n  end\n\n  before do\n    subject.namespace do\n      format :json\n      content_type :json, 'application/json'\n      params do\n        requires :id, desc: 'Identifier.'\n      end\n      get ':id' do\n        {\n          id: params[:id],\n          format: params[:format]\n        }\n      end\n    end\n  end\n\n  context 'get' do\n    it 'no format' do\n      get '/foo'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq(Grape::Json.dump(id: 'foo', format: nil))\n    end\n\n    it 'json format' do\n      get '/foo.json'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq(Grape::Json.dump(id: 'foo', format: 'json'))\n    end\n\n    it 'invalid format' do\n      get '/foo.invalid'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq(Grape::Json.dump(id: 'foo', format: 'invalid'))\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/mount_and_helpers_order_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::API do\n  describe 'rescue_from' do\n    context 'when the API is mounted AFTER defining the class rescue_from handler' do\n      let(:api_rescue_from) do\n        Class.new(Grape::API) do\n          rescue_from :all do\n            error!({ type: 'all' }, 404)\n          end\n\n          get do\n            { count: 1 / 0 }\n          end\n        end\n      end\n\n      let(:main_rescue_from_after) do\n        context = self\n\n        Class.new(Grape::API) do\n          rescue_from ZeroDivisionError do\n            error!({ type: 'zero' }, 500)\n          end\n\n          mount context.api_rescue_from\n        end\n      end\n\n      def app\n        main_rescue_from_after\n      end\n\n      it 'is rescued by the rescue_from ZeroDivisionError handler from Main class' do\n        get '/'\n\n        expect(last_response.status).to eq(500)\n        expect(last_response.body).to eq({ type: 'zero' }.to_json)\n      end\n    end\n\n    context 'when the API is mounted BEFORE defining the class rescue_from handler' do\n      let(:api_rescue_from) do\n        Class.new(Grape::API) do\n          rescue_from :all do\n            error!({ type: 'all' }, 404)\n          end\n\n          get do\n            { count: 1 / 0 }\n          end\n        end\n      end\n      let(:main_rescue_from_before) do\n        context = self\n\n        Class.new(Grape::API) do\n          mount context.api_rescue_from\n\n          rescue_from ZeroDivisionError do\n            error!({ type: 'zero' }, 500)\n          end\n        end\n      end\n\n      def app\n        main_rescue_from_before\n      end\n\n      it 'is rescued by the rescue_from ZeroDivisionError handler from Main class' do\n        get '/'\n\n        expect(last_response.status).to eq(500)\n        expect(last_response.body).to eq({ type: 'zero' }.to_json)\n      end\n    end\n  end\n\n  describe 'before' do\n    context 'when the API is mounted AFTER defining the before helper' do\n      let(:api_before_handler) do\n        Class.new(Grape::API) do\n          get do\n            { count: @count }.to_json\n          end\n        end\n      end\n      let(:main_before_handler_after) do\n        context = self\n\n        Class.new(Grape::API) do\n          before do\n            @count = 1\n          end\n\n          mount context.api_before_handler\n        end\n      end\n\n      def app\n        main_before_handler_after\n      end\n\n      it 'is able to access the variables defined in the before helper' do\n        get '/'\n\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq({ count: 1 }.to_json)\n      end\n    end\n\n    context 'when the API is mounted BEFORE defining the before helper' do\n      let(:api_before_handler) do\n        Class.new(Grape::API) do\n          get do\n            { count: @count }.to_json\n          end\n        end\n      end\n      let(:main_before_handler_before) do\n        context = self\n\n        Class.new(Grape::API) do\n          mount context.api_before_handler\n\n          before do\n            @count = 1\n          end\n        end\n      end\n\n      def app\n        main_before_handler_before\n      end\n\n      it 'is able to access the variables defined in the before helper' do\n        get '/'\n\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq({ count: 1 }.to_json)\n      end\n    end\n  end\n\n  describe 'after' do\n    context 'when the API is mounted AFTER defining the after handler' do\n      let(:api_after_handler) do\n        Class.new(Grape::API) do\n          get do\n            { count: 1 }.to_json\n          end\n        end\n      end\n      let(:main_after_handler_after) do\n        context = self\n\n        Class.new(Grape::API) do\n          after do\n            error!({ type: 'after' }, 500)\n          end\n\n          mount context.api_after_handler\n        end\n      end\n\n      def app\n        main_after_handler_after\n      end\n\n      it 'is able to access the variables defined in the after helper' do\n        get '/'\n\n        expect(last_response.status).to eq(500)\n        expect(last_response.body).to eq({ type: 'after' }.to_json)\n      end\n    end\n\n    context 'when the API is mounted BEFORE defining the after helper' do\n      let(:api_after_handler) do\n        Class.new(Grape::API) do\n          get do\n            { count: 1 }.to_json\n          end\n        end\n      end\n      let(:main_after_handler_before) do\n        context = self\n\n        Class.new(Grape::API) do\n          mount context.api_after_handler\n\n          after do\n            error!({ type: 'after' }, 500)\n          end\n        end\n      end\n\n      def app\n        main_after_handler_before\n      end\n\n      it 'is able to access the variables defined in the after helper' do\n        get '/'\n\n        expect(last_response.status).to eq(500)\n        expect(last_response.body).to eq({ type: 'after' }.to_json)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/mount_and_rescue_from_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::API do\n  context 'when multiple classes defines the same rescue_from' do\n    let(:an_api) do\n      Class.new(Grape::API) do\n        rescue_from ZeroDivisionError do\n          error!({ type: 'an-api-zero' }, 404)\n        end\n\n        get '/an-api' do\n          { count: 1 / 0 }\n        end\n      end\n    end\n    let(:another_api) do\n      Class.new(Grape::API) do\n        rescue_from ZeroDivisionError do\n          error!({ type: 'another-api-zero' }, 322)\n        end\n\n        get '/another-api' do\n          { count: 1 / 0 }\n        end\n      end\n    end\n    let(:other_main) do\n      context = self\n\n      Class.new(Grape::API) do\n        mount context.an_api\n        mount context.another_api\n      end\n    end\n\n    def app\n      other_main\n    end\n\n    it 'is rescued by the rescue_from ZeroDivisionError handler defined inside each of the classes' do\n      get '/an-api'\n\n      expect(last_response.status).to eq(404)\n      expect(last_response.body).to eq({ type: 'an-api-zero' }.to_json)\n\n      get '/another-api'\n\n      expect(last_response.status).to eq(322)\n      expect(last_response.body).to eq({ type: 'another-api-zero' }.to_json)\n    end\n\n    context 'when some class does not define a rescue_from but it was defined in a previous mounted endpoint' do\n      let(:an_api_without_defined_rescue_from) do\n        Class.new(Grape::API) do\n          get '/another-api-without-defined-rescue-from' do\n            { count: 1 / 0 }\n          end\n        end\n      end\n      let(:other_main_with_not_defined_rescue_from) do\n        context = self\n\n        Class.new(Grape::API) do\n          mount context.an_api\n          mount context.another_api\n          mount context.an_api_without_defined_rescue_from\n        end\n      end\n\n      def app\n        other_main_with_not_defined_rescue_from\n      end\n\n      it 'is not rescued by any of the previous defined rescue_from ZeroDivisionError handlers' do\n        get '/an-api'\n\n        expect(last_response.status).to eq(404)\n        expect(last_response.body).to eq({ type: 'an-api-zero' }.to_json)\n\n        get '/another-api'\n\n        expect(last_response.status).to eq(322)\n        expect(last_response.body).to eq({ type: 'another-api-zero' }.to_json)\n\n        expect do\n          get '/another-api-without-defined-rescue-from'\n        end.to raise_error(ZeroDivisionError)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/mounted_helpers_inheritance_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::API do\n  context 'when mounting a child API that inherits helpers from parent API' do\n    let(:child_api) do\n      Class.new(Grape::API) do\n        get '/test' do\n          parent_helper\n        end\n      end\n    end\n\n    let(:parent_api) do\n      context = self\n      Class.new(Grape::API) do\n        helpers do\n          def parent_helper\n            'parent helper value'\n          end\n        end\n\n        mount context.child_api\n      end\n    end\n\n    def app\n      parent_api\n    end\n\n    it 'inherits helpers from parent API to mounted child API' do\n      get '/test'\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('parent helper value')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/namespace_parameters_in_route_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Endpoint do\n  subject { Class.new(Grape::API) }\n\n  def app\n    subject\n  end\n\n  before do\n    subject.namespace :me do\n      namespace :pending do\n        get '/' do\n          'banana'\n        end\n      end\n      put ':id' do\n        params[:id]\n      end\n    end\n  end\n\n  context 'get' do\n    it 'responds without ext' do\n      get '/me/pending'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'banana'\n    end\n  end\n\n  context 'put' do\n    it 'responds' do\n      put '/me/foo'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'foo'\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/nested_helpers_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::API::Helpers do\n  let(:helper_methods) do\n    Module.new do\n      extend Grape::API::Helpers\n\n      def current_user\n        @current_user ||= params[:current_user]\n      end\n    end\n  end\n  let(:nested) do\n    context = self\n\n    Class.new(Grape::API) do\n      resource :level1 do\n        helpers context.helper_methods\n\n        get do\n          current_user\n        end\n\n        resource :level2 do\n          get do\n            current_user\n          end\n        end\n      end\n    end\n  end\n  let(:main) do\n    context = self\n\n    Class.new(Grape::API) do\n      mount context.nested\n    end\n  end\n\n  def app\n    main\n  end\n\n  it 'can access helpers from a mounted resource' do\n    get '/level1', current_user: 'hello'\n    expect(last_response.body).to eq('hello')\n  end\n\n  it 'can access helpers from a mounted resource in a nested resource' do\n    get '/level1/level2', current_user: 'world'\n    expect(last_response.body).to eq('world')\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/optional_parameters_in_route_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Endpoint do\n  subject { Class.new(Grape::API) }\n\n  def app\n    subject\n  end\n\n  before do\n    subject.namespace :api do\n      get ':id(/:ext)' do\n        [params[:id], params[:ext]].compact.join('/')\n      end\n\n      put ':id' do\n        params[:id]\n      end\n    end\n  end\n\n  context 'get' do\n    it 'responds without ext' do\n      get '/api/foo'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'foo'\n    end\n\n    it 'responds with ext' do\n      get '/api/foo/bar'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'foo/bar'\n    end\n  end\n\n  context 'put' do\n    it 'responds' do\n      put '/api/foo'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'foo'\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/parameters_modification_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Endpoint do\n  subject { Class.new(Grape::API) }\n\n  def app\n    subject\n  end\n\n  before do\n    subject.namespace :test do\n      params do\n        optional :foo, default: +'-abcdef'\n      end\n      get do\n        params[:foo].slice!(0)\n        params[:foo]\n      end\n    end\n  end\n\n  context 'when route modifies param value' do\n    it 'param default should not change' do\n      get '/test'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'abcdef'\n\n      get '/test'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'abcdef'\n\n      get '/test?foo=-123456'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq '123456'\n\n      get '/test'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'abcdef'\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/patch_method_helpers_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::API::Helpers do\n  let(:patch_public) do\n    Class.new(Grape::API) do\n      format :json\n      version 'public-v1', using: :header, vendor: 'grape'\n\n      get do\n        { ok: 'public' }\n      end\n    end\n  end\n  let(:auth_methods) do\n    Module.new do\n      def authenticate!; end\n    end\n  end\n  let(:patch_private) do\n    context = self\n\n    Class.new(Grape::API) do\n      format :json\n      version 'private-v1', using: :header, vendor: 'grape'\n\n      helpers context.auth_methods\n\n      before do\n        authenticate!\n      end\n\n      get do\n        { ok: 'private' }\n      end\n    end\n  end\n  let(:main) do\n    context = self\n\n    Class.new(Grape::API) do\n      mount context.patch_public\n      mount context.patch_private\n    end\n  end\n\n  def app\n    main\n  end\n\n  context 'patch' do\n    it 'public' do\n      patch '/', {}, 'HTTP_ACCEPT' => 'application/vnd.grape-public-v1+json'\n      expect(last_response.status).to eq 405\n    end\n\n    it 'private' do\n      patch '/', {}, 'HTTP_ACCEPT' => 'application/vnd.grape-private-v1+json'\n      expect(last_response.status).to eq 405\n    end\n\n    it 'default' do\n      patch '/'\n      expect(last_response.status).to eq 405\n    end\n  end\n\n  context 'default' do\n    it 'public' do\n      get '/', {}, 'HTTP_ACCEPT' => 'application/vnd.grape-public-v1+json'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ ok: 'public' }.to_json)\n    end\n\n    it 'private' do\n      get '/', {}, 'HTTP_ACCEPT' => 'application/vnd.grape-private-v1+json'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ ok: 'private' }.to_json)\n    end\n\n    it 'default' do\n      get '/'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ ok: 'public' }.to_json)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/recognize_path_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::API do\n  describe '.recognize_path' do\n    subject { Class.new(described_class) }\n\n    it 'fetches endpoint by given path' do\n      subject.get('/foo/:id') {}\n      subject.get('/bar/:id') {}\n      subject.get('/baz/:id') {}\n\n      actual = subject.recognize_path('/bar/1234').routes[0].origin\n      expect(actual).to eq('/bar/:id')\n    end\n\n    it 'returns nil if given path does not match with registered routes' do\n      subject.get {}\n      expect(subject.recognize_path('/bar/1234')).to be_nil\n    end\n\n    context 'when parametrized route with type specified together with a static route' do\n      subject do\n        Class.new(described_class) do\n          resource :books do\n            route_param :id, type: Integer do\n              get do\n              end\n\n              resource :loans do\n                route_param :loan_id, type: Integer do\n                  get do\n                  end\n                end\n\n                resource :print do\n                  post do\n                  end\n                end\n              end\n            end\n\n            resource :share do\n              post do\n              end\n            end\n          end\n        end\n      end\n\n      it 'recognizes the static route when the parameter does not match with the specified type' do\n        actual = subject.recognize_path('/books/share').routes[0].origin\n        expect(actual).to eq('/books/share')\n      end\n\n      it 'does not recognize any endpoint when there is not other endpoint that matches with the requested path' do\n        actual = subject.recognize_path('/books/other')\n        expect(actual).to be_nil\n      end\n\n      it 'recognizes the parametrized route when the parameter matches with the specified type' do\n        actual = subject.recognize_path('/books/1').routes[0].origin\n        expect(actual).to eq('/books/:id')\n      end\n\n      it 'recognizes the static nested route when the parameter does not match with the specified type' do\n        actual = subject.recognize_path('/books/1/loans/print').routes[0].origin\n        expect(actual).to eq('/books/:id/loans/print')\n      end\n\n      it 'recognizes the nested parametrized route when the parameter matches with the specified type' do\n        actual = subject.recognize_path('/books/1/loans/33').routes[0].origin\n        expect(actual).to eq('/books/:id/loans/:loan_id')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/required_parameters_in_route_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Endpoint do\n  subject { Class.new(Grape::API) }\n\n  def app\n    subject\n  end\n\n  before do\n    subject.namespace :api do\n      get ':id' do\n        [params[:id], params[:ext]].compact.join('/')\n      end\n\n      put ':something_id' do\n        params[:something_id]\n      end\n    end\n  end\n\n  context 'get' do\n    it 'responds' do\n      get '/api/foo'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'foo'\n    end\n  end\n\n  context 'put' do\n    it 'responds' do\n      put '/api/foo'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'foo'\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/required_parameters_with_invalid_method_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Endpoint do\n  subject { Class.new(Grape::API) }\n\n  def app\n    subject\n  end\n\n  before do\n    subject.namespace do\n      params do\n        requires :id, desc: 'Identifier.'\n      end\n      get ':id' do\n      end\n    end\n  end\n\n  context 'post' do\n    it '405' do\n      post '/something'\n      expect(last_response.status).to eq 405\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/routes_with_requirements_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Endpoint do\n  subject { Class.new(Grape::API) }\n\n  def app\n    subject\n  end\n\n  context 'get' do\n    it 'routes to a namespace param with dots' do\n      subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^/]+} } do\n        get '/' do\n          params[:ns_with_dots]\n        end\n      end\n\n      get '/test.id.with.dots'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'test.id.with.dots'\n    end\n\n    it 'routes to a path with multiple params with dots' do\n      subject.get ':id_with_dots/:another_id_with_dots', requirements: { id_with_dots: %r{[^/]+},\n                                                                         another_id_with_dots: %r{[^/]+} } do\n        \"#{params[:id_with_dots]}/#{params[:another_id_with_dots]}\"\n      end\n\n      get '/test.id/test2.id'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'test.id/test2.id'\n    end\n\n    it 'routes to namespace and path params with dots, with overridden requirements' do\n      subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^/]+} } do\n        get ':another_id_with_dots',     requirements: { ns_with_dots: %r{[^/]+},\n                                                         another_id_with_dots: %r{[^/]+} } do\n          \"#{params[:ns_with_dots]}/#{params[:another_id_with_dots]}\"\n        end\n      end\n\n      get '/test.id/test2.id'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'test.id/test2.id'\n    end\n\n    it 'routes to namespace and path params with dots, with merged requirements' do\n      subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^/]+} } do\n        get ':another_id_with_dots',     requirements: { another_id_with_dots: %r{[^/]+} } do\n          \"#{params[:ns_with_dots]}/#{params[:another_id_with_dots]}\"\n        end\n      end\n\n      get '/test.id/test2.id'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq 'test.id/test2.id'\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/shared_helpers_exactly_one_of_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::API::Helpers do\n  let(:app) do\n    Class.new(Grape::API) do\n      helpers Module.new do\n        extend Grape::API::Helpers\n\n        params :drink do\n          optional :beer\n          optional :wine\n          exactly_one_of :beer, :wine\n        end\n      end\n      format :json\n\n      params do\n        requires :orderType, type: String, values: %w[food drink]\n        given orderType: ->(val) { val == 'food' } do\n          optional :pasta\n          optional :pizza\n          exactly_one_of :pasta, :pizza\n        end\n        given orderType: ->(val) { val == 'drink' } do\n          use :drink\n        end\n      end\n      get do\n        declared(params, include_missing: true)\n      end\n    end\n  end\n\n  it 'defines parameters' do\n    get '/', orderType: 'food', pizza: 'mista'\n    expect(last_response.status).to eq 200\n    expect(last_response.body).to eq({ orderType: 'food',\n                                       pasta: nil, pizza: 'mista',\n                                       beer: nil, wine: nil }.to_json)\n  end\nend\n"
  },
  {
    "path": "spec/grape/api/shared_helpers_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::API::Helpers do\n  subject do\n    shared_params = Module.new do\n      extend Grape::API::Helpers\n\n      params :pagination do\n        optional :page, type: Integer\n        optional :size, type: Integer\n      end\n    end\n\n    Class.new(Grape::API) do\n      helpers shared_params\n      format :json\n\n      params do\n        use :pagination\n      end\n      get do\n        declared(params, include_missing: true)\n      end\n    end\n  end\n\n  def app\n    subject\n  end\n\n  it 'defines parameters' do\n    get '/'\n    expect(last_response.status).to eq 200\n    expect(last_response.body).to eq({ page: nil, size: nil }.to_json)\n  end\nend\n"
  },
  {
    "path": "spec/grape/api_remount_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'shared/versioning_examples'\n\ndescribe Grape::API do\n  subject(:a_remounted_api) { Class.new(described_class) }\n\n  let(:root_api) { Class.new(described_class) }\n\n  let(:app) { root_api }\n\n  describe 'remounting an API' do\n    context 'with a defined route' do\n      before do\n        a_remounted_api.get '/votes' do\n          '10 votes'\n        end\n      end\n\n      context 'when mounting one instance' do\n        before do\n          root_api.mount a_remounted_api\n        end\n\n        it 'can access the endpoint' do\n          get '/votes'\n          expect(last_response.body).to eql '10 votes'\n        end\n      end\n\n      context 'when mounting twice' do\n        before do\n          root_api.mount a_remounted_api => '/posts'\n          root_api.mount a_remounted_api => '/comments'\n        end\n\n        it 'can access the votes in both places' do\n          get '/posts/votes'\n          expect(last_response.body).to eql '10 votes'\n          get '/comments/votes'\n          expect(last_response.body).to eql '10 votes'\n        end\n      end\n\n      context 'when mounting on namespace' do\n        before do\n          stub_const('StaticRefToAPI', a_remounted_api)\n          root_api.namespace 'posts' do\n            mount StaticRefToAPI\n          end\n\n          root_api.namespace 'comments' do\n            mount StaticRefToAPI\n          end\n        end\n\n        it 'can access the votes in both places' do\n          get '/posts/votes'\n          expect(last_response.body).to eql '10 votes'\n          get '/comments/votes'\n          expect(last_response.body).to eql '10 votes'\n        end\n      end\n    end\n\n    describe 'with dynamic configuration' do\n      context 'when mounting an endpoint conditional on a configuration' do\n        subject(:a_remounted_api) do\n          Class.new(described_class) do\n            get 'always' do\n              'success'\n            end\n\n            given configuration[:mount_sometimes] do\n              get 'sometimes' do\n                'sometimes'\n              end\n            end\n          end\n        end\n\n        it 'mounts the endpoints only when configured to do so' do\n          root_api.mount({ a_remounted_api => 'with_conditional' }, with: { mount_sometimes: true })\n          root_api.mount({ a_remounted_api => 'without_conditional' }, with: { mount_sometimes: false })\n\n          get '/with_conditional/always'\n          expect(last_response.body).to eq 'success'\n\n          get '/with_conditional/sometimes'\n          expect(last_response.body).to eq 'sometimes'\n\n          get '/without_conditional/always'\n          expect(last_response.body).to eq 'success'\n\n          get '/without_conditional/sometimes'\n          expect(last_response).to be_not_found\n        end\n      end\n\n      context 'when using an expression derived from a configuration' do\n        subject(:a_remounted_api) do\n          Class.new(described_class) do\n            get(mounted { \"api_name_#{configuration[:api_name]}\" }) do\n              'success'\n            end\n          end\n        end\n\n        before do\n          root_api.mount a_remounted_api, with: {\n            api_name: 'a_name'\n          }\n        end\n\n        it 'mounts the endpoint with the name' do\n          get 'api_name_a_name'\n          expect(last_response.body).to eq 'success'\n        end\n\n        it 'does not mount the endpoint with a null name' do\n          get 'api_name_'\n          expect(last_response.body).not_to eq 'success'\n        end\n\n        context 'when the expression lives in a namespace' do\n          subject(:a_remounted_api) do\n            Class.new(described_class) do\n              namespace :base do\n                get(mounted { \"api_name_#{configuration[:api_name]}\" }) do\n                  'success'\n                end\n              end\n            end\n          end\n\n          it 'mounts the endpoint with the name' do\n            get 'base/api_name_a_name'\n            expect(last_response.body).to eq 'success'\n          end\n\n          it 'does not mount the endpoint with a null name' do\n            get 'base/api_name_'\n            expect(last_response.body).not_to eq 'success'\n          end\n        end\n      end\n\n      context 'when the params are configured via a configuration' do\n        subject(:a_remounted_api) do\n          Class.new(described_class) do\n            params do\n              requires configuration[:required_attr_name], type: String\n            end\n\n            get(mounted { configuration[:endpoint] }) do\n              status 200\n            end\n          end\n        end\n\n        context 'when the configured param is my_attr' do\n          it 'requires the configured params' do\n            root_api.mount a_remounted_api, with: {\n              required_attr_name: 'my_attr',\n              endpoint: 'test'\n            }\n            get 'test?another_attr=1'\n            expect(last_response).to be_bad_request\n            get 'test?my_attr=1'\n            expect(last_response).to be_successful\n\n            root_api.mount a_remounted_api, with: {\n              required_attr_name: 'another_attr',\n              endpoint: 'test_b'\n            }\n            get 'test_b?another_attr=1'\n            expect(last_response).to be_successful\n            get 'test_b?my_attr=1'\n            expect(last_response).to be_bad_request\n          end\n        end\n      end\n\n      context 'when executing a standard block within a `mounted` block with all dynamic params' do\n        subject(:a_remounted_api) do\n          Class.new(described_class) do\n            mounted do\n              desc configuration[:description] do\n                headers configuration[:headers]\n              end\n              get configuration[:endpoint] do\n                configuration[:response]\n              end\n            end\n          end\n        end\n\n        let(:api_endpoint) { 'custom_endpoint' }\n        let(:api_response) { 'custom response' }\n        let(:endpoint_description) { 'this is a custom API' }\n        let(:headers) do\n          {\n            'XAuthToken' => {\n              'description' => 'Validates your identity',\n              'required' => true\n            }\n          }\n        end\n\n        it 'mounts the API and obtains the description and headers definition' do\n          root_api.mount a_remounted_api, with: {\n            description: endpoint_description,\n            headers: headers,\n            endpoint: api_endpoint,\n            response: api_response\n          }\n          get api_endpoint\n          expect(last_response.body).to eq api_response\n          expect(a_remounted_api.instances.last.endpoints.first.options[:route_options][:description])\n            .to eq endpoint_description\n          expect(a_remounted_api.instances.last.endpoints.first.options[:route_options][:headers])\n            .to eq headers\n        end\n      end\n\n      context 'when executing a custom block on mount' do\n        subject(:a_remounted_api) do\n          Class.new(described_class) do\n            get 'always' do\n              'success'\n            end\n\n            mounted do\n              configuration[:endpoints].each do |endpoint_name, endpoint_response|\n                get endpoint_name do\n                  endpoint_response\n                end\n              end\n            end\n          end\n        end\n\n        it 'mounts the endpoints only when configured to do so' do\n          root_api.mount a_remounted_api, with: { endpoints: { 'api_name' => 'api_response' } }\n          get 'api_name'\n          expect(last_response.body).to eq 'api_response'\n        end\n      end\n\n      context 'when the configuration is part of the arguments of a method' do\n        subject(:a_remounted_api) do\n          Class.new(described_class) do\n            get configuration[:endpoint_name] do\n              'success'\n            end\n          end\n        end\n\n        it 'mounts the endpoint in the location it is configured' do\n          root_api.mount a_remounted_api, with: { endpoint_name: 'some_location' }\n          get '/some_location'\n          expect(last_response.body).to eq 'success'\n\n          get '/different_location'\n          expect(last_response).to be_not_found\n\n          root_api.mount a_remounted_api, with: { endpoint_name: 'new_location' }\n          get '/new_location'\n          expect(last_response.body).to eq 'success'\n        end\n\n        context 'when the configuration is the value in a key-arg pair' do\n          subject(:a_remounted_api) do\n            Class.new(described_class) do\n              version 'v1', using: :param, parameter: configuration[:version_param]\n              get 'endpoint' do\n                'version 1'\n              end\n\n              version 'v2', using: :param, parameter: configuration[:version_param]\n              get 'endpoint' do\n                'version 2'\n              end\n            end\n          end\n\n          it 'takes the param from the configuration' do\n            root_api.mount a_remounted_api, with: { version_param: 'param_name' }\n\n            get '/endpoint?param_name=v1'\n            expect(last_response.body).to eq 'version 1'\n\n            get '/endpoint?param_name=v2'\n            expect(last_response.body).to eq 'version 2'\n\n            get '/endpoint?wrong_param_name=v2'\n            expect(last_response.body).to eq 'version 1'\n          end\n        end\n      end\n\n      context 'on the DescSCope' do\n        subject(:a_remounted_api) do\n          Class.new(described_class) do\n            desc 'The description of this' do\n              tags ['not_configurable_tag', configuration[:a_configurable_tag]]\n            end\n            get 'location' do\n              route.tags\n            end\n          end\n        end\n\n        it 'mounts the endpoint with the appropiate tags' do\n          root_api.mount({ a_remounted_api => 'integer' }, with: { a_configurable_tag: 'a configured tag' })\n          get '/integer/location', param_key: 'a'\n          expect(JSON.parse(last_response.body)).to eq ['not_configurable_tag', 'a configured tag']\n        end\n      end\n\n      context 'on the ParamScope' do\n        subject(:a_remounted_api) do\n          Class.new(described_class) do\n            params do\n              requires configuration[:required_param], type: configuration[:required_type]\n            end\n\n            get 'location' do\n              'success'\n            end\n          end\n        end\n\n        it 'mounts the endpoint in the location it is configured' do\n          root_api.mount({ a_remounted_api => 'string' }, with: { required_param: 'param_key', required_type: String })\n          root_api.mount({ a_remounted_api => 'integer' }, with: { required_param: 'param_integer', required_type: Integer })\n\n          get '/string/location', param_key: 'a'\n          expect(last_response.body).to eq 'success'\n\n          get '/string/location', param_integer: 1\n          expect(last_response).to be_bad_request\n\n          get '/integer/location', param_integer: 1\n          expect(last_response.body).to eq 'success'\n\n          get '/integer/location', param_integer: 'a'\n          expect(last_response).to be_bad_request\n        end\n\n        context 'on dynamic checks' do\n          subject(:a_remounted_api) do\n            Class.new(described_class) do\n              params do\n                optional :restricted_values, values: -> { [configuration[:allowed_value], 'always'] }\n              end\n\n              get 'location' do\n                'success'\n              end\n            end\n          end\n\n          it 'can read the configuration on lambdas' do\n            root_api.mount a_remounted_api, with: { allowed_value: 'sometimes' }\n            get '/location', restricted_values: 'always'\n            expect(last_response.body).to eq 'success'\n            get '/location', restricted_values: 'sometimes'\n            expect(last_response.body).to eq 'success'\n            get '/location', restricted_values: 'never'\n            expect(last_response).to be_bad_request\n          end\n        end\n      end\n\n      context 'when the configuration is read within a namespace' do\n        before do\n          a_remounted_api.namespace 'api' do\n            params do\n              requires configuration[:required_param]\n            end\n            get \"/#{configuration[:path]}\" do\n              '10 votes'\n            end\n          end\n          root_api.mount a_remounted_api, with: { path: 'votes', required_param: 'param_key' }\n          root_api.mount a_remounted_api, with: { path: 'scores', required_param: 'param_key' }\n        end\n\n        it 'uses the dynamic configuration on all routes' do\n          get 'api/votes', param_key: 'a'\n          expect(last_response.body).to eql '10 votes'\n          get 'api/scores', param_key: 'a'\n          expect(last_response.body).to eql '10 votes'\n          get 'api/votes'\n          expect(last_response).to be_bad_request\n        end\n      end\n\n      context 'a very complex configuration example' do\n        before do\n          top_level_api = Class.new(described_class) do\n            remounted_api = Class.new(Grape::API) do\n              get configuration[:endpoint_name] do\n                configuration[:response]\n              end\n            end\n\n            expression_namespace = mounted { configuration[:namespace].to_s * 2 }\n            given(mounted { configuration[:should_mount_expressed] != false }) do\n              namespace expression_namespace do\n                mount remounted_api, with: { endpoint_name: configuration[:endpoint_name], response: configuration[:endpoint_response] }\n              end\n            end\n          end\n          root_api.mount top_level_api, with: configuration_options\n        end\n\n        context 'when the namespace should be mounted' do\n          let(:configuration_options) do\n            {\n              should_mount_expressed: true,\n              namespace: 'bang',\n              endpoint_name: 'james',\n              endpoint_response: 'bond'\n            }\n          end\n\n          it 'gets a response' do\n            get 'bangbang/james'\n            expect(last_response.body).to eq 'bond'\n          end\n        end\n\n        context 'when should be mounted is nil' do\n          let(:configuration_options) do\n            {\n              should_mount_expressed: nil,\n              namespace: 'bang',\n              endpoint_name: 'james',\n              endpoint_response: 'bond'\n            }\n          end\n\n          it 'gets a response' do\n            get 'bangbang/james'\n            expect(last_response.body).to eq 'bond'\n          end\n        end\n\n        context 'when it should not be mounted' do\n          let(:configuration_options) do\n            {\n              should_mount_expressed: false,\n              namespace: 'bang',\n              endpoint_name: 'james',\n              endpoint_response: 'bond'\n            }\n          end\n\n          it 'gets a response' do\n            get 'bangbang/james'\n            expect(last_response.body).not_to eq 'bond'\n          end\n        end\n      end\n\n      context 'when the configuration is read in a helper' do\n        subject(:a_remounted_api) do\n          Class.new(described_class) do\n            helpers do\n              def printed_response\n                configuration[:some_value]\n              end\n            end\n\n            get 'location' do\n              printed_response\n            end\n          end\n        end\n\n        it 'uses the dynamic configuration on all routes' do\n          root_api.mount(a_remounted_api, with: { some_value: 'response value' })\n\n          get '/location'\n          expect(last_response.body).to eq 'response value'\n        end\n      end\n\n      context 'when the configuration is read within the response block' do\n        subject(:a_remounted_api) do\n          Class.new(described_class) do\n            get 'location' do\n              configuration[:some_value]\n            end\n          end\n        end\n\n        it 'uses the dynamic configuration on all routes' do\n          root_api.mount(a_remounted_api, with: { some_value: 'response value' })\n\n          get '/location'\n          expect(last_response.body).to eq 'response value'\n        end\n      end\n    end\n\n    context 'with route settings' do\n      before do\n        a_remounted_api.desc 'Identical description'\n        a_remounted_api.route_setting :custom, key: 'value'\n        a_remounted_api.route_setting :custom_diff, key: 'foo'\n        a_remounted_api.get '/api1' do\n          status 200\n        end\n\n        a_remounted_api.desc 'Identical description'\n        a_remounted_api.route_setting :custom, key: 'value'\n        a_remounted_api.route_setting :custom_diff, key: 'bar'\n        a_remounted_api.get '/api2' do\n          status 200\n        end\n      end\n\n      it 'has all the settings for both routes' do\n        expect(a_remounted_api.routes.count).to be(2)\n        expect(a_remounted_api.routes[0].settings).to include(\n          {\n            description: { description: 'Identical description' },\n            custom: { key: 'value' },\n            custom_diff: { key: 'foo' }\n          }\n        )\n        expect(a_remounted_api.routes[1].settings).to include(\n          {\n            description: { description: 'Identical description' },\n            custom: { key: 'value' },\n            custom_diff: { key: 'bar' }\n          }\n        )\n      end\n\n      context 'when mounting it' do\n        before do\n          root_api.mount a_remounted_api\n        end\n\n        it 'still has all the settings for both routes' do\n          expect(root_api.routes.count).to be(2)\n          expect(root_api.routes[0].settings).to include(\n            {\n              description: { description: 'Identical description' },\n              custom: { key: 'value' },\n              custom_diff: { key: 'foo' }\n            }\n          )\n          expect(root_api.routes[1].settings).to include(\n            {\n              description: { description: 'Identical description' },\n              custom: { key: 'value' },\n              custom_diff: { key: 'bar' }\n            }\n          )\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/api_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'shared/versioning_examples'\n\ndescribe Grape::API do\n  subject { Class.new(described_class) }\n\n  let(:app) { subject }\n\n  describe '.prefix' do\n    it 'routes root through with the prefix' do\n      subject.prefix 'awesome/sauce'\n      subject.get do\n        'Hello there.'\n      end\n\n      get 'awesome/sauce/'\n      expect(last_response).to be_successful\n      expect(last_response.body).to eql 'Hello there.'\n    end\n\n    it 'routes through with the prefix' do\n      subject.prefix 'awesome/sauce'\n      subject.get :hello do\n        'Hello there.'\n      end\n\n      get 'awesome/sauce/hello'\n      expect(last_response.body).to eql 'Hello there.'\n\n      get '/hello'\n      expect(last_response).to be_not_found\n    end\n\n    it 'supports OPTIONS' do\n      subject.prefix 'awesome/sauce'\n      subject.get do\n        'Hello there.'\n      end\n\n      options 'awesome/sauce'\n      expect(last_response).to be_no_content\n      expect(last_response.body).to be_blank\n    end\n\n    it 'disallows POST' do\n      subject.prefix 'awesome/sauce'\n      subject.get\n\n      post 'awesome/sauce'\n      expect(last_response).to be_method_not_allowed\n    end\n  end\n\n  describe '.version' do\n    context 'when defined' do\n      it 'returns version value' do\n        subject.version 'v1'\n        expect(subject.version).to eq('v1')\n      end\n    end\n\n    context 'when not defined' do\n      it 'returns nil' do\n        expect(subject.version).to be_nil\n      end\n    end\n  end\n\n  describe '.version using path' do\n    it_behaves_like 'versioning' do\n      let(:macro_options) do\n        {\n          using: :path\n        }\n      end\n    end\n  end\n\n  describe '.version using param' do\n    it_behaves_like 'versioning' do\n      let(:macro_options) do\n        {\n          using: :param,\n          parameter: 'apiver'\n        }\n      end\n    end\n  end\n\n  describe '.version using header' do\n    it_behaves_like 'versioning' do\n      let(:macro_options) do\n        {\n          using: :header,\n          vendor: 'mycompany',\n          format: 'json'\n        }\n      end\n    end\n  end\n\n  describe '.version using accept_version_header' do\n    it_behaves_like 'versioning' do\n      let(:macro_options) do\n        {\n          using: :accept_version_header\n        }\n      end\n    end\n  end\n\n  describe '.represent' do\n    it 'requires a :with option' do\n      expect { subject.represent Object, {} }.to raise_error(Grape::Exceptions::InvalidWithOptionForRepresent)\n    end\n\n    it 'adds the association to the :representations setting' do\n      dummy_presenter_klass = Class.new\n      represent_object = Class.new\n      subject.represent represent_object, with: dummy_presenter_klass\n      expect(subject.inheritable_setting.namespace_stackable[:representations]).to eq([represent_object => dummy_presenter_klass])\n    end\n  end\n\n  describe '.namespace' do\n    it 'is retrievable and converted to a path' do\n      internal_namespace = nil\n      subject.namespace :awesome do\n        internal_namespace = namespace\n      end\n      expect(internal_namespace).to eql('/awesome')\n    end\n\n    it 'comes after the prefix and version' do\n      subject.prefix :rad\n      subject.version 'v1', using: :path\n\n      subject.namespace :awesome do\n        get('/hello') { 'worked' }\n      end\n\n      get '/rad/v1/awesome/hello'\n      expect(last_response.body).to eq('worked')\n    end\n\n    it 'cancels itself after the block is over' do\n      internal_namespace = nil\n      subject.namespace :awesome do\n        internal_namespace = namespace\n      end\n      expect(subject.namespace).to eql('/')\n    end\n\n    it 'is stackable' do\n      internal_namespace = nil\n      internal_second_namespace = nil\n      subject.namespace :awesome do\n        internal_namespace = namespace\n        namespace :rad do\n          internal_second_namespace = namespace\n        end\n      end\n      expect(internal_namespace).to eq('/awesome')\n      expect(internal_second_namespace).to eq('/awesome/rad')\n    end\n\n    it 'accepts path segments correctly' do\n      inner_namespace = nil\n      subject.namespace :members do\n        namespace '/:member_id' do\n          inner_namespace = namespace\n          get '/' do\n            params[:member_id]\n          end\n        end\n      end\n      get '/members/23'\n      expect(last_response.body).to eq('23')\n      expect(inner_namespace).to eq('/members/:member_id')\n    end\n\n    it 'is callable with nil just to push onto the stack' do\n      subject.namespace do\n        version 'v2', using: :path\n        get('/hello') { 'inner' }\n      end\n      subject.get('/hello') { 'outer' }\n\n      get '/v2/hello'\n      expect(last_response.body).to eq('inner')\n      get '/hello'\n      expect(last_response.body).to eq('outer')\n    end\n\n    %w[group resource resources segment].each do |als|\n      it \"`.#{als}` is an alias\" do\n        inner_namespace = nil\n        subject.__send__(als, :awesome) do\n          inner_namespace = namespace\n        end\n        expect(inner_namespace).to eq '/awesome'\n      end\n    end\n  end\n\n  describe '.call' do\n    context 'it does not add to the app setup' do\n      it 'calls the app' do\n        expect(subject).not_to receive(:add_setup)\n        subject.call({})\n      end\n    end\n  end\n\n  describe '.route_param' do\n    it 'adds a parameterized route segment namespace' do\n      subject.namespace :users do\n        route_param :id do\n          get do\n            params[:id]\n          end\n        end\n      end\n\n      get '/users/23'\n      expect(last_response.body).to eq('23')\n    end\n\n    it 'defines requirements with a single hash' do\n      subject.namespace :users do\n        route_param :id, requirements: /[0-9]+/ do\n          get do\n            params[:id]\n          end\n        end\n      end\n\n      get '/users/michael'\n      expect(last_response).to be_not_found\n      get '/users/23'\n      expect(last_response).to be_successful\n    end\n\n    context 'with param type definitions' do\n      it 'is used by passing to options' do\n        subject.namespace :route_param do\n          route_param :foo, type: Integer do\n            get { params.to_json }\n          end\n        end\n        get '/route_param/1234'\n        expect(last_response.body).to eq('{\"foo\":1234}')\n      end\n    end\n  end\n\n  describe '.route' do\n    it 'allows for no path' do\n      subject.namespace :votes do\n        get do\n          'Votes'\n        end\n        post do\n          'Created a Vote'\n        end\n      end\n\n      get '/votes'\n      expect(last_response.body).to eql 'Votes'\n      post '/votes'\n      expect(last_response.body).to eql 'Created a Vote'\n    end\n\n    it 'handles empty calls' do\n      subject.get '/'\n      get '/'\n      expect(last_response.body).to eql ''\n    end\n\n    describe 'root routes should work with' do\n      before do\n        subject.format :txt\n        subject.content_type :json, 'application/json'\n        subject.formatter :json, ->(object, _env) { object }\n        def subject.enable_root_route!\n          get('/') { 'root' }\n        end\n      end\n\n      shared_examples_for 'a root route' do\n        it 'returns root' do\n          expect(last_response.body).to eql 'root'\n        end\n      end\n\n      describe 'path versioned APIs' do\n        before do\n          subject.version version, using: :path\n          subject.enable_root_route!\n        end\n\n        context 'when a single version provided' do\n          let(:version) { 'v1' }\n\n          context 'without a format' do\n            before do\n              versioned_get '/', 'v1', using: :path\n            end\n\n            it_behaves_like 'a root route'\n          end\n\n          context 'with a format' do\n            before do\n              get '/v1/.json'\n            end\n\n            it_behaves_like 'a root route'\n          end\n        end\n\n        context 'when array of versions provided' do\n          let(:version) { %w[v1 v2] }\n\n          context 'when v1' do\n            before do\n              versioned_get '/', 'v1', using: :path\n            end\n\n            it_behaves_like 'a root route'\n          end\n\n          context 'when v2' do\n            before do\n              versioned_get '/', 'v2', using: :path\n            end\n\n            it_behaves_like 'a root route'\n          end\n        end\n      end\n\n      context 'when header versioned APIs' do\n        before do\n          subject.version 'v1', using: :header, vendor: 'test'\n          subject.enable_root_route!\n          versioned_get '/', 'v1', using: :header, vendor: 'test'\n        end\n\n        it_behaves_like 'a root route'\n      end\n\n      context 'when header versioned APIs with multiple headers' do\n        before do\n          subject.version %w[v1 v2], using: :header, vendor: 'test'\n          subject.enable_root_route!\n        end\n\n        context 'when v1' do\n          before do\n            versioned_get '/', 'v1', using: :header, vendor: 'test'\n          end\n\n          it_behaves_like 'a root route'\n        end\n\n        context 'when v2' do\n          before do\n            versioned_get '/', 'v2', using: :header, vendor: 'test'\n          end\n\n          it_behaves_like 'a root route'\n        end\n      end\n\n      context 'param versioned APIs' do\n        before do\n          subject.version 'v1', using: :param\n          subject.enable_root_route!\n          versioned_get '/', 'v1', using: :param\n        end\n\n        it_behaves_like 'a root route'\n      end\n\n      context 'when Accept-Version header versioned APIs' do\n        before do\n          subject.version 'v1', using: :accept_version_header\n          subject.enable_root_route!\n          versioned_get '/', 'v1', using: :accept_version_header\n        end\n\n        it_behaves_like 'a root route'\n      end\n\n      context 'unversioned APIss' do\n        before do\n          subject.enable_root_route!\n          get '/'\n        end\n\n        it_behaves_like 'a root route'\n      end\n    end\n\n    it 'allows for multiple paths' do\n      subject.get(['/abc', '/def']) do\n        'foo'\n      end\n\n      get '/abc'\n      expect(last_response.body).to eql 'foo'\n      get '/def'\n      expect(last_response.body).to eql 'foo'\n    end\n\n    context 'format' do\n      before do\n        dummy_class = Class.new do\n          def to_json(*_rest)\n            'abc'\n          end\n\n          def to_txt\n            'def'\n          end\n        end\n\n        subject.get('/abc') do\n          dummy_class.new\n        end\n      end\n\n      it 'allows .json' do\n        get '/abc.json'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eql 'abc' # json-encoded symbol\n      end\n\n      it 'allows .txt' do\n        get '/abc.txt'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eql 'def' # raw text\n      end\n    end\n\n    it 'allows for format without corrupting a param' do\n      subject.get('/:id') do\n        { 'id' => params[:id] }\n      end\n\n      get '/awesome.json'\n      expect(last_response.body).to eql '{\"id\":\"awesome\"}'\n    end\n\n    it 'allows for format in namespace with no path' do\n      subject.namespace :abc do\n        get do\n          ['json']\n        end\n      end\n\n      get '/abc.json'\n      expect(last_response.body).to eql '[\"json\"]'\n    end\n\n    it 'allows for multiple verbs' do\n      subject.route(%i[get post], '/abc') do\n        'hiya'\n      end\n\n      subject.endpoints.first.routes.each do |route|\n        expect(route.path).to eql '/abc(.:format)'\n      end\n\n      get '/abc'\n      expect(last_response.body).to eql 'hiya'\n      post '/abc'\n      expect(last_response.body).to eql 'hiya'\n    end\n\n    objects = ['string', :symbol, 1, -1.1, {}, [], true, false, nil].freeze\n    %i[put post].each do |verb|\n      context verb.to_s do\n        objects.each do |object|\n          it \"allows a(n) #{object.class} json object in params\" do\n            subject.format :json\n            subject.__send__(verb) do\n              env[Grape::Env::API_REQUEST_BODY]\n            end\n            __send__ verb, '/', Grape::Json.dump(object), 'CONTENT_TYPE' => 'application/json'\n            expect(last_response.status).to eq(verb == :post ? 201 : 200)\n            expect(last_response.body).to eql Grape::Json.dump(object)\n            expect(last_request.params).to eql({})\n          end\n\n          it 'stores input in api.request.input' do\n            subject.format :json\n            subject.__send__(verb) do\n              env[Grape::Env::API_REQUEST_INPUT]\n            end\n            __send__ verb, '/', Grape::Json.dump(object), 'CONTENT_TYPE' => 'application/json'\n            expect(last_response.status).to eq(verb == :post ? 201 : 200)\n            expect(last_response.body).to eql Grape::Json.dump(object).to_json\n          end\n\n          context 'chunked transfer encoding' do\n            it 'stores input in api.request.input' do\n              subject.format :json\n              subject.__send__(verb) do\n                env[Grape::Env::API_REQUEST_INPUT]\n              end\n              __send__ verb, '/', Grape::Json.dump(object), 'CONTENT_TYPE' => 'application/json', 'HTTP_TRANSFER_ENCODING' => 'chunked'\n              expect(last_response.status).to eq(verb == :post ? 201 : 200)\n              expect(last_response.body).to eql Grape::Json.dump(object).to_json\n            end\n          end\n        end\n      end\n    end\n\n    it 'allows for multipart paths' do\n      subject.route(%i[get post], '/:id/first') do\n        'first'\n      end\n\n      subject.route(%i[get post], '/:id') do\n        'ola'\n      end\n      subject.route(%i[get post], '/:id/first/second') do\n        'second'\n      end\n\n      get '/1'\n      expect(last_response.body).to eql 'ola'\n      post '/1'\n      expect(last_response.body).to eql 'ola'\n      get '/1/first'\n      expect(last_response.body).to eql 'first'\n      post '/1/first'\n      expect(last_response.body).to eql 'first'\n      get '/1/first/second'\n      expect(last_response.body).to eql 'second'\n    end\n\n    it 'allows for :any as a verb' do\n      subject.route(:any, '/abc') do\n        'lol'\n      end\n\n      %w[get post put delete options patch].each do |m|\n        __send__(m, '/abc')\n        expect(last_response.body).to eql 'lol'\n      end\n    end\n\n    it 'allows for catch-all in a namespace' do\n      subject.namespace :nested do\n        get do\n          'root'\n        end\n\n        get 'something' do\n          'something'\n        end\n\n        route :any, '*path' do\n          'catch-all'\n        end\n      end\n\n      get 'nested'\n      expect(last_response.body).to eql 'root'\n\n      get 'nested/something'\n      expect(last_response.body).to eql 'something'\n\n      get 'nested/missing'\n      expect(last_response.body).to eql 'catch-all'\n\n      post 'nested'\n      expect(last_response.body).to eql 'catch-all'\n\n      post 'nested/something'\n      expect(last_response.body).to eql 'catch-all'\n    end\n\n    verbs = %w[post get head delete put options patch]\n    verbs.each do |verb|\n      it \"allows and properly constrain a #{verb.upcase} method\" do\n        subject.__send__(verb, '/example') do\n          verb\n        end\n        __send__(verb, '/example')\n        expect(last_response.body).to eql verb == 'head' ? '' : verb\n        # Call it with all methods other than the properly constrained one.\n        (verbs - [verb]).each do |other_verb|\n          __send__(other_verb, '/example')\n          expected_rc = if other_verb == 'options' then 204\n                        elsif other_verb == 'head' && verb == 'get' then 200\n                        else\n                          405\n                        end\n          expect(last_response.status).to eql expected_rc\n        end\n      end\n    end\n\n    it 'returns a 201 response code for POST by default' do\n      subject.post('example') do\n        'Created'\n      end\n\n      post '/example'\n      expect(last_response).to be_created\n      expect(last_response.body).to eql 'Created'\n    end\n\n    it 'returns a 405 for an unsupported method with an X-Custom-Header' do\n      subject.before { header 'X-Custom-Header', 'foo' }\n      subject.get 'example' do\n        'example'\n      end\n      put '/example'\n      expect(last_response).to be_method_not_allowed\n      expect(last_response.body).to eql '405 Not Allowed'\n      expect(last_response.headers['X-Custom-Header']).to eql 'foo'\n    end\n\n    it 'runs only the before filter on 405 bad method' do\n      subject.namespace :example do\n        before            { header 'X-Custom-Header', 'foo' }\n\n        before_validation { raise 'before_validation filter should not run' }\n        after_validation  { raise 'after_validation filter should not run' }\n        after             { raise 'after filter should not run' }\n\n        params { requires :only_for_get }\n        get\n      end\n\n      post '/example'\n      expect(last_response).to be_method_not_allowed\n      expect(last_response.headers['X-Custom-Header']).to eql 'foo'\n    end\n\n    it 'runs before filter exactly once on 405 bad method' do\n      already_run = false\n      subject.namespace :example do\n        before do\n          raise 'before filter ran twice' if already_run\n\n          already_run = true\n          header 'X-Custom-Header', 'foo'\n        end\n\n        get\n      end\n\n      post '/example'\n      expect(last_response).to be_method_not_allowed\n      expect(last_response.headers['X-Custom-Header']).to eql 'foo'\n    end\n\n    it 'runs all filters and body with a custom OPTIONS method' do\n      subject.namespace :example do\n        before            { header 'X-Custom-Header-1', 'foo' }\n\n        before_validation { header 'X-Custom-Header-2', 'foo' }\n        after_validation  { header 'X-Custom-Header-3', 'foo' }\n        after             { header 'X-Custom-Header-4', 'foo' }\n\n        options { 'yup' }\n        get\n      end\n\n      options '/example'\n      expect(last_response).to be_successful\n      expect(last_response.body).to eql 'yup'\n      expect(last_response.headers['Allow']).to be_nil\n      expect(last_response.headers['X-Custom-Header-1']).to eql 'foo'\n      expect(last_response.headers['X-Custom-Header-2']).to eql 'foo'\n      expect(last_response.headers['X-Custom-Header-3']).to eql 'foo'\n      expect(last_response.headers['X-Custom-Header-4']).to eql 'foo'\n    end\n\n    context 'when format is xml' do\n      it 'returns a 405 for an unsupported method' do\n        subject.format :xml\n        subject.get 'example' do\n          'example'\n        end\n\n        put '/example'\n        expect(last_response).to be_method_not_allowed\n        expect(last_response.body).to eq <<~XML\n          <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n          <error>\n            <message>405 Not Allowed</message>\n          </error>\n        XML\n      end\n    end\n\n    context 'when accessing env' do\n      it 'returns a 405 for an unsupported method' do\n        subject.before do\n          _customheader1 = headers['X-Custom-Header']\n          _customheader2 = env['HTTP_X_CUSTOM_HEADER']\n        end\n        subject.get 'example' do\n          'example'\n        end\n        put '/example'\n        expect(last_response).to be_method_not_allowed\n        expect(last_response.body).to eql '405 Not Allowed'\n      end\n    end\n\n    specify '405 responses includes an Allow header specifying supported methods' do\n      subject.get 'example' do\n        'example'\n      end\n      subject.post 'example' do\n        'example'\n      end\n      put '/example'\n      expect(last_response.headers['Allow']).to eql 'OPTIONS, GET, POST, HEAD'\n    end\n\n    specify '405 responses includes an Content-Type header' do\n      subject.get 'example' do\n        'example'\n      end\n      subject.post 'example' do\n        'example'\n      end\n      put '/example'\n      expect(last_response.content_type).to eql 'text/plain'\n    end\n\n    describe 'adds an OPTIONS route that' do\n      before do\n        subject.before            { header 'X-Custom-Header', 'foo' }\n        subject.before_validation { header 'X-Custom-Header-2', 'bar' }\n        subject.after_validation  { header 'X-Custom-Header-3', 'baz' }\n        subject.after             { header 'X-Custom-Header-4', 'bing' }\n        subject.params { requires :only_for_get }\n        subject.get 'example' do\n          'example'\n        end\n        subject.route :any, '*path' do\n          error! :not_found, 404\n        end\n        options '/example'\n      end\n\n      it 'returns a 204' do\n        expect(last_response).to be_no_content\n      end\n\n      it 'has an empty body' do\n        expect(last_response.body).to be_blank\n      end\n\n      it 'has an Allow header' do\n        expect(last_response.headers['Allow']).to eql 'OPTIONS, GET, HEAD'\n      end\n\n      it 'calls before hook' do\n        expect(last_response.headers['X-Custom-Header']).to eql 'foo'\n      end\n\n      it 'does not call before_validation hook' do\n        expect(last_response.headers.key?('X-Custom-Header-2')).to be false\n      end\n\n      it 'does not call after_validation hook' do\n        expect(last_response.headers.key?('X-Custom-Header-3')).to be false\n      end\n\n      it 'calls after hook' do\n        expect(last_response.headers['X-Custom-Header-4']).to eq 'bing'\n      end\n\n      it 'has no Content-Type' do\n        expect(last_response.content_type).to be_nil\n      end\n\n      it 'has no Content-Length' do\n        expect(last_response.content_length).to be_nil\n      end\n    end\n\n    describe 'when a resource routes by POST, GET, PATCH, PUT, and DELETE' do\n      before do\n        subject.namespace :example do\n          get do\n            'example'\n          end\n\n          patch do\n            'example'\n          end\n\n          post do\n            'example'\n          end\n\n          delete do\n            'example'\n          end\n\n          put do\n            'example'\n          end\n        end\n        options '/example'\n      end\n\n      describe 'it adds an OPTIONS route for namespaced endpoints that' do\n        it 'returns a 204' do\n          expect(last_response).to be_no_content\n        end\n\n        it 'has an empty body' do\n          expect(last_response.body).to be_blank\n        end\n\n        it 'has an Allow header' do\n          expect(last_response.headers['Allow']).to eql 'OPTIONS, GET, PATCH, POST, DELETE, PUT, HEAD'\n        end\n      end\n    end\n\n    describe 'adds an OPTIONS route for namespaced endpoints that' do\n      before do\n        subject.before { header 'X-Custom-Header', 'foo' }\n        subject.namespace :example do\n          before { header 'X-Custom-Header-2', 'foo' }\n\n          get :inner do\n            'example/inner'\n          end\n        end\n        options '/example/inner'\n      end\n\n      it 'returns a 204' do\n        expect(last_response).to be_no_content\n      end\n\n      it 'has an empty body' do\n        expect(last_response.body).to be_blank\n      end\n\n      it 'has an Allow header' do\n        expect(last_response.headers['Allow']).to eql 'OPTIONS, GET, HEAD'\n      end\n\n      it 'calls the outer before filter' do\n        expect(last_response.headers['X-Custom-Header']).to eql 'foo'\n      end\n\n      it 'calls the inner before filter' do\n        expect(last_response.headers['X-Custom-Header-2']).to eql 'foo'\n      end\n\n      it 'has no Content-Type' do\n        expect(last_response.content_type).to be_nil\n      end\n\n      it 'has no Content-Length' do\n        expect(last_response.content_length).to be_nil\n      end\n    end\n\n    describe 'adds a 405 Not Allowed route that' do\n      before do\n        subject.before { header 'X-Custom-Header', 'foo' }\n        subject.post :example do\n          'example'\n        end\n        get '/example'\n      end\n\n      it 'returns a 405' do\n        expect(last_response).to be_method_not_allowed\n      end\n\n      it 'contains error message in body' do\n        expect(last_response.body).to eq '405 Not Allowed'\n      end\n\n      it 'has an Allow header' do\n        expect(last_response.headers['Allow']).to eql 'OPTIONS, POST'\n      end\n\n      it 'has a X-Custom-Header' do\n        expect(last_response.headers['X-Custom-Header']).to eql 'foo'\n      end\n    end\n\n    describe 'when hook behaviour is controlled by attributes on the route' do\n      before do\n        subject.before do\n          error!('Access Denied', 401) unless route.options[:secret] == params[:secret]\n        end\n\n        subject.namespace 'example' do\n          before do\n            error!('Access Denied', 401) unless route.options[:namespace_secret] == params[:namespace_secret]\n          end\n\n          desc 'it gets with secret', secret: 'password'\n          get { status(params[:id] == '504' ? 200 : 404) }\n\n          desc 'it post with secret', secret: 'password', namespace_secret: 'namespace_password'\n          post {}\n        end\n      end\n\n      context 'when HTTP method is not defined' do\n        let(:response) { delete('/example') }\n\n        it 'responds with a 405 status' do\n          expect(response).to be_method_not_allowed\n        end\n      end\n\n      context 'when HTTP method is defined with attribute' do\n        let(:response) { post('/example?secret=incorrect_password') }\n\n        it 'responds with the defined error in the before hook' do\n          expect(response).to be_unauthorized\n        end\n      end\n\n      context 'when HTTP method is defined and the underlying before hook expectation is not met' do\n        let(:response) { post('/example?secret=password&namespace_secret=wrong_namespace_password') }\n\n        it 'ends up in the endpoint' do\n          expect(response).to be_unauthorized\n        end\n      end\n\n      context 'when HTTP method is defined and everything is like the before hooks expect' do\n        let(:response) { post('/example?secret=password&namespace_secret=namespace_password') }\n\n        it 'ends up in the endpoint' do\n          expect(response).to be_created\n        end\n      end\n\n      context 'when HEAD is called for the defined GET' do\n        let(:response) { head('/example?id=504') }\n\n        it 'responds with 401 because before expectations in before hooks are not met' do\n          expect(response).to be_unauthorized\n        end\n      end\n\n      context 'when HEAD is called for the defined GET' do\n        let(:response) { head('/example?id=504&secret=password') }\n\n        it 'responds with 200 because before hooks are not called' do\n          expect(response).to be_successful\n        end\n      end\n    end\n\n    context 'allows HEAD on a GET request that' do\n      before do\n        subject.get 'example' do\n          'example'\n        end\n        subject.route :any, '*path' do\n          error! :not_found, 404\n        end\n        head '/example'\n      end\n\n      it 'returns a 200' do\n        expect(last_response).to be_successful\n      end\n\n      it 'has an empty body' do\n        expect(last_response.body).to eql ''\n      end\n    end\n\n    it 'overwrites the default HEAD request' do\n      subject.head 'example' do\n        error! 'nothing to see here', 400\n      end\n      subject.get 'example' do\n        'example'\n      end\n      head '/example'\n      expect(last_response).to be_bad_request\n    end\n  end\n\n  context 'do_not_route_head!' do\n    before do\n      subject.do_not_route_head!\n      subject.get 'example' do\n        'example'\n      end\n    end\n\n    it 'options does not contain HEAD' do\n      options '/example'\n      expect(last_response).to be_no_content\n      expect(last_response.body).to eql ''\n      expect(last_response.headers['Allow']).to eql 'OPTIONS, GET'\n    end\n\n    it 'does not allow HEAD on a GET request' do\n      head '/example'\n      expect(last_response).to be_method_not_allowed\n    end\n  end\n\n  context 'do_not_route_options!' do\n    before do\n      subject.do_not_route_options!\n      subject.get 'example' do\n        'example'\n      end\n    end\n\n    it 'does not create an OPTIONS route' do\n      options '/example'\n      expect(last_response).to be_method_not_allowed\n    end\n\n    it 'does not include OPTIONS in Allow header' do\n      options '/example'\n      expect(last_response).to be_method_not_allowed\n      expect(last_response.headers['Allow']).to eql 'GET, HEAD'\n    end\n  end\n\n  describe '.compile!' do\n    let(:base_instance) { app.base_instance }\n\n    before do\n      allow(base_instance).to receive(:compile!).and_return(:compiled!)\n    end\n\n    it 'returns compiled!' do\n      expect(app.__send__(:compile!)).to eq(:compiled!)\n    end\n  end\n\n  describe 'filters' do\n    it 'adds a before filter' do\n      subject.before { @foo = 'first'  }\n      subject.before { @bar = 'second' }\n      subject.get '/' do\n        \"#{@foo} #{@bar}\"\n      end\n\n      get '/'\n      expect(last_response.body).to eql 'first second'\n    end\n\n    it 'adds a before filter to current and child namespaces only' do\n      subject.get '/' do\n        \"root - #{@foo if @foo}\"\n      end\n      subject.namespace :blah do\n        before { @foo = 'foo' }\n\n        get '/' do\n          \"blah - #{@foo}\"\n        end\n\n        namespace :bar do\n          get '/' do\n            \"blah - bar - #{@foo}\"\n          end\n        end\n      end\n\n      get '/'\n      expect(last_response.body).to eql 'root - '\n      get '/blah'\n      expect(last_response.body).to eql 'blah - foo'\n      get '/blah/bar'\n      expect(last_response.body).to eql 'blah - bar - foo'\n    end\n\n    it 'adds a after_validation filter' do\n      subject.after_validation { @foo = \"first #{params[:id]}:#{params[:id].class}\" }\n      subject.after_validation { @bar = 'second' }\n      subject.params do\n        requires :id, type: Integer\n      end\n      subject.get '/' do\n        \"#{@foo} #{@bar}\"\n      end\n\n      get '/', id: '32'\n      expect(last_response.body).to eql \"first 32:#{integer_class_name} second\"\n    end\n\n    it 'adds a after filter' do\n      m = double('after mock')\n      subject.after { m.do_something! }\n      subject.after { m.do_something! }\n      subject.get '/' do\n        @var ||= 'default'\n      end\n\n      expect(m).to receive(:do_something!).twice\n      get '/'\n      expect(last_response.body).to eql 'default'\n    end\n\n    it 'calls all filters when validation passes' do\n      a = double('before mock')\n      b = double('before_validation mock')\n      c = double('after_validation mock')\n      d = double('after mock')\n\n      subject.params do\n        requires :id, type: Integer\n      end\n      subject.resource ':id' do\n        before { a.do_something! }\n\n        before_validation { b.do_something! }\n        after_validation { c.do_something! }\n        after { d.do_something! }\n\n        get do\n          'got it'\n        end\n      end\n\n      expect(a).to receive(:do_something!).once\n      expect(b).to receive(:do_something!).once\n      expect(c).to receive(:do_something!).once\n      expect(d).to receive(:do_something!).once\n\n      get '/123'\n      expect(last_response).to be_successful\n      expect(last_response.body).to eql 'got it'\n    end\n\n    it 'calls only before filters when validation fails' do\n      a = double('before mock')\n      b = double('before_validation mock')\n      c = double('after_validation mock')\n      d = double('after mock')\n\n      subject.params do\n        requires :id, type: Integer, values: [1, 2, 3]\n      end\n      subject.resource ':id' do\n        before { a.do_something! }\n\n        before_validation { b.do_something! }\n        after_validation { c.do_something! }\n        after { d.do_something! }\n\n        get do\n          'got it'\n        end\n      end\n\n      expect(a).to receive(:do_something!).once\n      expect(b).to receive(:do_something!).once\n      expect(c).to receive(:do_something!).exactly(0).times\n      expect(d).to receive(:do_something!).exactly(0).times\n\n      get '/4'\n      expect(last_response).to be_bad_request\n      expect(last_response.body).to eql 'id does not have a valid value'\n    end\n\n    it 'calls filters in the correct order' do\n      i = 0\n      a = double('before mock')\n      b = double('before_validation mock')\n      c = double('after_validation mock')\n      d = double('after mock')\n\n      subject.params do\n        requires :id, type: Integer\n      end\n      subject.resource ':id' do\n        before { a.here(i += 1) }\n\n        before_validation { b.here(i += 1) }\n        after_validation { c.here(i += 1) }\n        after { d.here(i += 1) }\n\n        get do\n          'got it'\n        end\n      end\n\n      expect(a).to receive(:here).with(1).once\n      expect(b).to receive(:here).with(2).once\n      expect(c).to receive(:here).with(3).once\n      expect(d).to receive(:here).with(4).once\n\n      get '/123'\n      expect(last_response).to be_successful\n      expect(last_response.body).to eql 'got it'\n    end\n  end\n\n  context 'format' do\n    before do\n      subject.get('/foo') { 'bar' }\n    end\n\n    it 'sets content type for txt format' do\n      get '/foo'\n      expect(last_response.content_type).to eq('text/plain')\n    end\n\n    it 'does not set Cache-Control' do\n      get '/foo'\n      expect(last_response.headers[Rack::CACHE_CONTROL]).to be_nil\n    end\n\n    it 'sets content type for xml' do\n      get '/foo.xml'\n      expect(last_response.content_type).to eq('application/xml')\n    end\n\n    it 'sets content type for json' do\n      get '/foo.json'\n      expect(last_response.content_type).to eq('application/json')\n    end\n\n    it 'sets content type for serializable hash format' do\n      get '/foo.serializable_hash'\n      expect(last_response.content_type).to eq('application/json')\n    end\n\n    it 'sets content type for binary format' do\n      get '/foo.binary'\n      expect(last_response.content_type).to eq('application/octet-stream')\n    end\n\n    it 'returns raw data when content type binary' do\n      image_filename = 'grape.png'\n      file = File.binread(image_filename)\n      subject.format :binary\n      subject.get('/binary_file') { File.binread(image_filename) }\n      get '/binary_file'\n      expect(last_response.content_type).to eq('application/octet-stream')\n      expect(last_response.body).to eq(file)\n    end\n\n    it 'returns the content of the file with file' do\n      file_content = 'This is some file content'\n      test_file = Tempfile.new('test')\n      test_file.write file_content\n      test_file.rewind\n\n      subject.get('/file') { stream test_file }\n      get '/file'\n      expect(last_response.content_length).to eq(25)\n      expect(last_response.content_type).to eq('text/plain')\n      expect(last_response.body).to eq(file_content)\n    end\n\n    it 'streams the content of the file with stream' do\n      test_stream = Enumerator.new do |blk|\n        blk.yield 'This is some'\n        blk.yield ' file content'\n      end\n\n      subject.use Gem::Version.new(Rack.release) < Gem::Version.new('3') ? Rack::Chunked : ChunkedResponse\n      subject.get('/stream') { stream test_stream }\n      get '/stream', {}, 'HTTP_VERSION' => 'HTTP/1.1', Rack::SERVER_PROTOCOL => 'HTTP/1.1'\n\n      expect(last_response.content_type).to eq('text/plain')\n      expect(last_response.content_length).to be_nil\n      expect(last_response.headers[Rack::CACHE_CONTROL]).to eq('no-cache')\n      expect(last_response.headers['Transfer-Encoding']).to eq('chunked')\n\n      expect(last_response.body).to eq(\"c\\r\\nThis is some\\r\\nd\\r\\n file content\\r\\n0\\r\\n\\r\\n\")\n    end\n\n    it 'sets content type for error' do\n      subject.get('/error') { error!('error in plain text', 500) }\n      get '/error'\n      expect(last_response.content_type).to eql 'text/plain'\n    end\n\n    it 'sets content type for json error' do\n      subject.format :json\n      subject.get('/error') { error!('error in json', 500) }\n      get '/error.json'\n      expect(last_response).to be_server_error\n      expect(last_response.content_type).to eql 'application/json'\n    end\n\n    it 'sets content type for xml error' do\n      subject.format :xml\n      subject.get('/error') { error!('error in xml', 500) }\n      get '/error'\n      expect(last_response).to be_server_error\n      expect(last_response.content_type).to eql 'application/xml'\n    end\n\n    it 'includes extension in format' do\n      subject.get(':id') { params[:format] }\n\n      get '/baz.bar'\n      expect(last_response).to be_successful\n      expect(last_response.body).to eq 'bar'\n    end\n\n    it 'does not include extension in id' do\n      subject.format :json\n      subject.get(':id') { params }\n\n      get '/baz.bar'\n      expect(last_response).to be_not_found\n    end\n\n    context 'with a custom content_type' do\n      before do\n        subject.content_type :custom, 'application/custom'\n        subject.formatter :custom, ->(_object, _env) { 'custom' }\n\n        subject.get('/custom') { 'bar' }\n        subject.get('/error') { error!('error in custom', 500) }\n      end\n\n      it 'sets content type' do\n        get '/custom.custom'\n        expect(last_response.content_type).to eql 'application/custom'\n      end\n\n      it 'sets content type for error' do\n        get '/error.custom'\n        expect(last_response.content_type).to eql 'application/custom'\n      end\n    end\n\n    context 'env[\"api.format\"]' do\n      before do\n        ct = content_type\n        subject.post 'attachment' do\n          filename = params[:file][:filename]\n          content_type ct\n          env[Grape::Env::API_FORMAT] = :binary # there's no formatter for :binary, data will be returned \"as is\"\n          header 'Content-Disposition', \"attachment; filename*=UTF-8''#{CGI.escape(filename)}\"\n          params[:file][:tempfile].read\n        end\n      end\n\n      context 'when image/png' do\n        let(:content_type) { 'image/png' }\n\n        %w[/attachment.png attachment].each do |url|\n          it \"uploads and downloads a PNG file via #{url}\" do\n            image_filename = 'grape.png'\n            post url, file: Rack::Test::UploadedFile.new(image_filename, content_type, true)\n            expect(last_response).to be_created\n            expect(last_response.content_type).to eq(content_type)\n            expect(last_response.headers['Content-Disposition']).to eq(\"attachment; filename*=UTF-8''grape.png\")\n            File.open(image_filename, 'rb') do |io|\n              expect(last_response.body).to eq io.read\n            end\n          end\n        end\n      end\n\n      context 'when ruby file' do\n        let(:content_type) { 'application/x-ruby' }\n\n        it 'uploads and downloads a Ruby file' do\n          filename = __FILE__\n          post '/attachment.rb', file: Rack::Test::UploadedFile.new(filename, content_type, true)\n          expect(last_response).to be_created\n          expect(last_response.content_type).to eq(content_type)\n          expect(last_response.headers['Content-Disposition']).to eq(\"attachment; filename*=UTF-8''api_spec.rb\")\n          File.open(filename, 'rb') do |io|\n            expect(last_response.body).to eq io.read\n          end\n        end\n      end\n    end\n  end\n\n  context 'custom middleware' do\n    let(:phony_middleware) do\n      Class.new do\n        def initialize(app, *args)\n          @args = args\n          @app = app\n          @block = block_given? || nil\n        end\n\n        def call(env)\n          env['phony.args'] ||= []\n          env['phony.args'] << @args\n          env['phony.block'] = true if @block\n          @app.call(env)\n        end\n      end\n    end\n\n    describe '.middleware' do\n      it 'includes middleware arguments from settings' do\n        subject.use phony_middleware, 'abc', 123\n        expect(subject.middleware).to eql [[:use, phony_middleware, 'abc', 123]]\n      end\n\n      it 'includes all middleware from stacked settings' do\n        subject.use phony_middleware, 123\n        subject.use phony_middleware, 'abc'\n        subject.use phony_middleware, 'foo'\n\n        expect(subject.middleware).to eql [\n          [:use, phony_middleware, 123],\n          [:use, phony_middleware, 'abc'],\n          [:use, phony_middleware, 'foo']\n        ]\n      end\n    end\n\n    describe '.use' do\n      it 'adds middleware' do\n        subject.use phony_middleware, 123\n        expect(subject.middleware).to eql [[:use, phony_middleware, 123]]\n      end\n\n      it 'does not show up outside the namespace' do\n        example = self\n        inner_middleware = nil\n        subject.use phony_middleware, 123\n        subject.namespace :awesome do\n          use example.phony_middleware, 'abc'\n          inner_middleware = middleware\n        end\n\n        expect(subject.middleware).to eql [[:use, phony_middleware, 123]]\n        expect(inner_middleware).to eql [[:use, phony_middleware, 123], [:use, phony_middleware, 'abc']]\n      end\n\n      it 'calls the middleware' do\n        subject.use phony_middleware, 'hello'\n        subject.get '/' do\n          env['phony.args'].first.first\n        end\n\n        get '/'\n        expect(last_response.body).to eql 'hello'\n      end\n\n      it 'adds a block if one is given' do\n        block = -> {}\n        subject.use phony_middleware, &block\n        expect(subject.middleware).to eql [[:use, phony_middleware, block]]\n      end\n\n      it 'uses a block if one is given' do\n        block = -> {}\n        subject.use phony_middleware, &block\n        subject.get '/' do\n          env['phony.block'].inspect\n        end\n\n        get '/'\n        expect(last_response.body).to eq('true')\n      end\n\n      it 'does not destroy the middleware settings on multiple runs' do\n        block = -> {}\n        subject.use phony_middleware, &block\n        subject.get '/' do\n          env['phony.block'].inspect\n        end\n\n        2.times do\n          get '/'\n          expect(last_response.body).to eq('true')\n        end\n      end\n\n      it 'mounts behind error middleware' do\n        m = Class.new(Grape::Middleware::Base) do\n          def before\n            throw :error, message: 'Caught in the Net', status: 400\n          end\n        end\n        subject.use m\n        subject.get '/' do\n        end\n        get '/'\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('Caught in the Net')\n      end\n\n      context 'when middleware initialize as keywords' do\n        let(:middleware_with_keywords) do\n          Class.new do\n            def initialize(app, keyword:)\n              @app = app\n              @keyword = keyword\n            end\n\n            def call(env)\n              env['middleware_with_keywords'] = @keyword\n              @app.call(env)\n            end\n          end\n        end\n\n        before do\n          subject.use middleware_with_keywords, keyword: 'hello'\n          subject.get '/' do\n            env['middleware_with_keywords']\n          end\n          get '/'\n        end\n\n        it 'returns the middleware value' do\n          expect(last_response.body).to eq('hello')\n        end\n      end\n    end\n\n    describe '.insert_before' do\n      it 'runs before a given middleware' do\n        m = Class.new(Grape::Middleware::Base) do\n          def call(env)\n            env['phony.args'] ||= []\n            env['phony.args'] << @options[:message]\n            @app.call(env)\n          end\n        end\n\n        subject.use phony_middleware, 'hello'\n        subject.insert_before phony_middleware, m, message: 'bye'\n        subject.get '/' do\n          env['phony.args'].join(' ')\n        end\n\n        get '/'\n        expect(last_response.body).to eql 'bye hello'\n      end\n    end\n\n    describe '.insert_after' do\n      it 'runs after a given middleware' do\n        m = Class.new(Grape::Middleware::Base) do\n          def call(env)\n            env['phony.args'] ||= []\n            env['phony.args'] << @options[:message]\n            @app.call(env)\n          end\n        end\n\n        subject.use phony_middleware, 'hello'\n        subject.insert_after phony_middleware, m, message: 'bye'\n        subject.get '/' do\n          env['phony.args'].join(' ')\n        end\n\n        get '/'\n        expect(last_response.body).to eql 'hello bye'\n      end\n    end\n\n    describe '.insert' do\n      it 'inserts middleware in a specific location in the stack' do\n        m = Class.new(Grape::Middleware::Base) do\n          def call(env)\n            env['phony.args'] ||= []\n            env['phony.args'] << @options[:message]\n            @app.call(env)\n          end\n        end\n\n        subject.use phony_middleware, 'bye'\n        subject.insert 0, m, message: 'good'\n        subject.insert 0, m, message: 'hello'\n        subject.get '/' do\n          env['phony.args'].join(' ')\n        end\n\n        get '/'\n        expect(last_response.body).to eql 'hello good bye'\n      end\n    end\n  end\n\n  describe '.http_basic' do\n    it 'protects any resources on the same scope' do\n      subject.http_basic do |u, _p|\n        u == 'allow'\n      end\n      subject.get(:hello) { 'Hello, world.' }\n      get '/hello'\n      expect(last_response).to be_unauthorized\n      get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')\n      expect(last_response).to be_successful\n    end\n\n    it 'is scopable' do\n      subject.get(:hello) { 'Hello, world.' }\n      subject.namespace :admin do\n        http_basic do |u, _p|\n          u == 'allow'\n        end\n\n        get(:hello) { 'Hello, world.' }\n      end\n\n      get '/hello'\n      expect(last_response).to be_successful\n      get '/admin/hello'\n      expect(last_response).to be_unauthorized\n    end\n\n    it 'is callable via .auth as well' do\n      subject.auth :http_basic do |u, _p|\n        u == 'allow'\n      end\n\n      subject.get(:hello) { 'Hello, world.' }\n      get '/hello'\n      expect(last_response).to be_unauthorized\n      get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')\n      expect(last_response).to be_successful\n    end\n\n    it 'has access to the current endpoint' do\n      basic_auth_context = nil\n\n      subject.http_basic do |u, _p|\n        basic_auth_context = self\n\n        u == 'allow'\n      end\n\n      subject.get(:hello) { 'Hello, world.' }\n      get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')\n      expect(basic_auth_context).to be_a(Grape::Endpoint)\n    end\n\n    it 'has access to helper methods' do\n      subject.helpers do\n        def authorize?(user, password)\n          user == 'allow' && password == 'whatever'\n        end\n      end\n\n      subject.http_basic do |u, p|\n        authorize?(u, p)\n      end\n\n      subject.get(:hello) { 'Hello, world.' }\n      get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')\n      expect(last_response).to be_successful\n      get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('disallow', 'whatever')\n      expect(last_response).to be_unauthorized\n    end\n\n    it 'can set instance variables accessible to routes' do\n      subject.http_basic do |u, _p|\n        @hello = 'Hello, world.'\n\n        u == 'allow'\n      end\n\n      subject.get(:hello) { @hello }\n      get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')\n      expect(last_response).to be_successful\n      expect(last_response.body).to eql 'Hello, world.'\n    end\n  end\n\n  describe '.logger' do\n    it 'returns an instance of Logger class by default' do\n      expect(subject.logger.class).to eql Logger\n    end\n\n    context 'with a custom logger' do\n      subject do\n        Class.new(described_class) do\n          def self.io\n            @io ||= StringIO.new\n          end\n          logger Logger.new(io)\n        end\n      end\n\n      it 'exposes its interaface' do\n        message = 'this will be logged'\n        subject.logger.info message\n        expect(subject.io.string).to include(message)\n      end\n    end\n  end\n\n  describe '.helpers' do\n    it 'is accessible from the endpoint' do\n      subject.helpers do\n        def hello\n          'Hello, world.'\n        end\n      end\n\n      subject.get '/howdy' do\n        hello\n      end\n\n      get '/howdy'\n      expect(last_response.body).to eql 'Hello, world.'\n    end\n\n    it 'is scopable' do\n      subject.helpers do\n        def generic\n          'always there'\n        end\n      end\n\n      subject.namespace :admin do\n        helpers do\n          def secret\n            'only in admin'\n          end\n        end\n\n        get '/secret' do\n          [generic, secret].join ':'\n        end\n      end\n\n      subject.get '/generic' do\n        [generic, respond_to?(:secret)].join ':'\n      end\n\n      get '/generic'\n      expect(last_response.body).to eql 'always there:false'\n      get '/admin/secret'\n      expect(last_response.body).to eql 'always there:only in admin'\n    end\n\n    it 'is reopenable' do\n      subject.helpers do\n        def one\n          1\n        end\n      end\n\n      subject.helpers do\n        def two\n          2\n        end\n      end\n\n      subject.get 'howdy' do\n        [one, two]\n      end\n\n      expect { get '/howdy' }.not_to raise_error\n    end\n\n    it 'allows for modules' do\n      mod = Module.new do\n        def hello\n          'Hello, world.'\n        end\n      end\n      subject.helpers mod\n\n      subject.get '/howdy' do\n        hello\n      end\n\n      get '/howdy'\n      expect(last_response.body).to eql 'Hello, world.'\n    end\n\n    it 'allows multiple calls with modules and blocks' do\n      subject.helpers Module.new do\n        def one\n          1\n        end\n      end\n      subject.helpers Module.new do\n        def two\n          2\n        end\n      end\n      subject.helpers do\n        def three\n          3\n        end\n      end\n      subject.get 'howdy' do\n        [one, two, three]\n      end\n      expect { get '/howdy' }.not_to raise_error\n    end\n  end\n\n  describe '.scope' do\n    # TODO: refactor this to not be tied to versioning. How about a generic\n    # .setting macro?\n    it 'scopes the various settings' do\n      subject.prefix 'new'\n\n      subject.scope :legacy do\n        prefix 'legacy'\n        get '/abc' do\n          'abc'\n        end\n      end\n\n      subject.get '/def' do\n        'def'\n      end\n\n      get '/new/abc'\n      expect(last_response).to be_not_found\n      get '/legacy/abc'\n      expect(last_response).to be_successful\n      get '/legacy/def'\n      expect(last_response).to be_not_found\n      get '/new/def'\n      expect(last_response).to be_successful\n    end\n  end\n\n  describe 'lifecycle' do\n    let!(:lifecycle) { [] }\n    let!(:standard_cycle) do\n      %i[before before_validation after_validation api_call after finally]\n    end\n\n    let!(:validation_error) do\n      %i[before before_validation finally]\n    end\n\n    let!(:errored_cycle) do\n      %i[before before_validation after_validation api_call finally]\n    end\n\n    before do\n      current_cycle = lifecycle\n\n      subject.before do\n        current_cycle << :before\n      end\n\n      subject.before_validation do\n        current_cycle << :before_validation\n      end\n\n      subject.after_validation do\n        current_cycle << :after_validation\n      end\n\n      subject.after do\n        current_cycle << :after\n      end\n\n      subject.finally do\n        current_cycle << :finally\n      end\n    end\n\n    context 'when the api_call succeeds' do\n      before do\n        current_cycle = lifecycle\n\n        subject.get 'api_call' do\n          current_cycle << :api_call\n        end\n      end\n\n      it 'follows the standard life_cycle' do\n        get '/api_call'\n        expect(lifecycle).to eq standard_cycle\n      end\n    end\n\n    context 'when the api_call has a controlled error' do\n      before do\n        current_cycle = lifecycle\n\n        subject.get 'api_call' do\n          current_cycle << :api_call\n          error!(:some_error)\n        end\n      end\n\n      it 'follows the errored life_cycle (skips after)' do\n        get '/api_call'\n        expect(lifecycle).to eq errored_cycle\n      end\n    end\n\n    context 'when the api_call has an exception' do\n      before do\n        current_cycle = lifecycle\n\n        subject.get 'api_call' do\n          current_cycle << :api_call\n          raise StandardError\n        end\n      end\n\n      it 'follows the errored life_cycle (skips after)' do\n        expect { get '/api_call' }.to raise_error(StandardError)\n        expect(lifecycle).to eq errored_cycle\n      end\n    end\n\n    context 'when the api_call fails validation' do\n      before do\n        current_cycle = lifecycle\n\n        subject.params do\n          requires :some_param, type: String\n        end\n\n        subject.get 'api_call' do\n          current_cycle << :api_call\n        end\n      end\n\n      it 'follows the failed_validation cycle (skips after_validation, api_call & after)' do\n        get '/api_call'\n        expect(lifecycle).to eq validation_error\n      end\n    end\n  end\n\n  describe '.finally' do\n    let!(:code) { { has_executed: false } }\n    let(:block_to_run) do\n      code_to_execute = code\n      proc do\n        code_to_execute[:has_executed] = true\n      end\n    end\n\n    context 'when the ensure block has no exceptions' do\n      before { subject.finally(&block_to_run) }\n\n      context 'when no API call is made' do\n        it 'has not executed the ensure code' do\n          expect(code[:has_executed]).to be false\n        end\n      end\n\n      context 'when no errors occurs' do\n        before do\n          subject.get '/no_exceptions' do\n            'success'\n          end\n        end\n\n        it 'executes the ensure code' do\n          get '/no_exceptions'\n          expect(last_response.body).to eq 'success'\n          expect(code[:has_executed]).to be true\n        end\n\n        context 'with a helper' do\n          let(:block_to_run) do\n            code_to_execute = code\n            proc do\n              code_to_execute[:value] = some_helper\n            end\n          end\n\n          before do\n            subject.helpers do\n              def some_helper\n                'some_value'\n              end\n            end\n\n            subject.get '/with_helpers' do\n              'success'\n            end\n          end\n\n          it 'has access to the helper' do\n            get '/with_helpers'\n            expect(code[:value]).to eq 'some_value'\n          end\n        end\n      end\n\n      context 'when an unhandled occurs inside the API call' do\n        before do\n          subject.get '/unhandled_exception' do\n            raise StandardError\n          end\n        end\n\n        it 'executes the ensure code' do\n          expect { get '/unhandled_exception' }.to raise_error StandardError\n          expect(code[:has_executed]).to be true\n        end\n      end\n\n      context 'when a handled error occurs inside the API call' do\n        before do\n          subject.rescue_from(StandardError) { error! 'handled' }\n          subject.get '/handled_exception' do\n            raise StandardError\n          end\n        end\n\n        it 'executes the ensure code' do\n          get '/handled_exception'\n          expect(code[:has_executed]).to be true\n          expect(last_response.body).to eq 'handled'\n        end\n      end\n    end\n  end\n\n  describe '.rescue_from' do\n    it 'does not rescue errors when rescue_from is not set' do\n      subject.get '/exception' do\n        raise 'rain!'\n      end\n      expect { get '/exception' }.to raise_error(RuntimeError, 'rain!')\n    end\n\n    it 'uses custom helpers defined by using #helpers method' do\n      subject.helpers do\n        def custom_error!(name)\n          error! \"hello #{name}\"\n        end\n      end\n      subject.rescue_from(ArgumentError) { custom_error! :bob }\n      subject.get '/custom_error' do\n        raise ArgumentError\n      end\n      get '/custom_error'\n      expect(last_response.body).to eq 'hello bob'\n    end\n\n    context 'with multiple apis' do\n      let(:a) do\n        Class.new(described_class) do\n          namespace :a do\n            helpers do\n              def foo\n                error!('foo', 401)\n              end\n            end\n\n            rescue_from(:all) { foo }\n\n            get { raise 'boo' }\n          end\n        end\n      end\n      let(:b) do\n        Class.new(described_class) do\n          namespace :b do\n            helpers do\n              def foo\n                error!('bar', 401)\n              end\n            end\n\n            rescue_from(:all) { foo }\n\n            get { raise 'boo' }\n          end\n        end\n      end\n\n      before do\n        subject.mount a\n        subject.mount b\n      end\n\n      it 'avoids polluting global namespace' do\n        get '/a'\n        expect(last_response.body).to eq('foo')\n        get '/b'\n        expect(last_response.body).to eq('bar')\n        get '/a'\n        expect(last_response.body).to eq('foo')\n      end\n    end\n\n    it 'rescues all errors if rescue_from :all is called' do\n      subject.rescue_from :all\n      subject.get '/exception' do\n        raise 'rain!'\n      end\n      get '/exception'\n      expect(last_response).to be_server_error\n      expect(last_response.body).to eq 'rain!'\n    end\n\n    it 'rescues all errors with a json formatter' do\n      subject.format :json\n      subject.default_format :json\n      subject.rescue_from :all\n      subject.get '/exception' do\n        raise 'rain!'\n      end\n      get '/exception'\n      expect(last_response).to be_server_error\n      expect(last_response.body).to eq({ error: 'rain!' }.to_json)\n    end\n\n    it 'rescues only certain errors if rescue_from is called with specific errors' do\n      subject.rescue_from ArgumentError\n      subject.get('/rescued') { raise ArgumentError }\n      subject.get('/unrescued') { raise 'beefcake' }\n\n      get '/rescued'\n      expect(last_response).to be_server_error\n\n      expect { get '/unrescued' }.to raise_error(RuntimeError, 'beefcake')\n    end\n\n    it 'mimics default ruby \"rescue\" handler' do\n      # The exception is matched to the rescue starting at the top, and matches only once\n\n      subject.rescue_from ArgumentError do |e|\n        error!(e, 402)\n      end\n      subject.rescue_from StandardError do |e|\n        error!(e, 401)\n      end\n\n      subject.get('/child_of_standard_error') { raise ArgumentError }\n      subject.get('/standard_error') { raise StandardError }\n\n      get '/child_of_standard_error'\n      expect(last_response.status).to be 402\n\n      get '/standard_error'\n      expect(last_response).to be_unauthorized\n    end\n\n    context 'CustomError subclass of Grape::Exceptions::Base' do\n      before do\n        stub_const('ApiSpec::CustomError', Class.new(Grape::Exceptions::Base))\n      end\n\n      it 'does not re-raise exceptions of type Grape::Exceptions::Base' do\n        subject.get('/custom_exception') { raise ApiSpec::CustomError }\n\n        expect { get '/custom_exception' }.not_to raise_error\n      end\n\n      it 'rescues custom grape exceptions' do\n        subject.rescue_from ApiSpec::CustomError do |e|\n          error!('New Error', e.status)\n        end\n        subject.get '/custom_error' do\n          raise ApiSpec::CustomError.new(status: 400, message: 'Custom Error')\n        end\n\n        get '/custom_error'\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('New Error')\n      end\n    end\n\n    it 'can rescue exceptions raised in the formatter' do\n      formatter = double(:formatter)\n      allow(formatter).to receive(:call) { raise StandardError }\n      allow(Grape::Formatter).to receive(:formatter_for) { formatter }\n\n      subject.rescue_from :all do |_e|\n        error!('Formatter Error', 500)\n      end\n      subject.get('/formatter_exception') { 'Hello world' }\n\n      get '/formatter_exception'\n      expect(last_response).to be_server_error\n      expect(last_response.body).to eq('Formatter Error')\n    end\n\n    context 'when rescue_from block returns an invalid response' do\n      it 'returns a formatted response' do\n        subject.rescue_from(:all) { 'error' }\n        subject.get('/') { raise }\n        get '/'\n        expect(last_response).to be_server_error\n        expect(last_response.body).to eql 'Invalid response'\n      end\n    end\n  end\n\n  describe '.rescue_from klass, block' do\n    it 'rescues Exception' do\n      subject.rescue_from RuntimeError do |e|\n        error!(\"rescued from #{e.message}\", 202)\n      end\n      subject.get '/exception' do\n        raise 'rain!'\n      end\n      get '/exception'\n      expect(last_response).to be_accepted\n      expect(last_response.body).to eq('rescued from rain!')\n    end\n\n    context 'custom errors' do\n      before do\n        stub_const('ConnectionError', Class.new(RuntimeError))\n        stub_const('DatabaseError', Class.new(RuntimeError))\n        stub_const('CommunicationError', Class.new(StandardError))\n      end\n\n      it 'rescues an error via rescue_from :all' do\n        subject.rescue_from :all do |e|\n          error!(\"rescued from #{e.class.name}\", 500)\n        end\n        subject.get '/exception' do\n          raise ConnectionError\n        end\n        get '/exception'\n        expect(last_response).to be_server_error\n        expect(last_response.body).to eq('rescued from ConnectionError')\n      end\n\n      it 'rescues a specific error' do\n        subject.rescue_from ConnectionError do |e|\n          error!(\"rescued from #{e.class.name}\", 500)\n        end\n        subject.get '/exception' do\n          raise ConnectionError\n        end\n        get '/exception'\n        expect(last_response).to be_server_error\n        expect(last_response.body).to eq('rescued from ConnectionError')\n      end\n\n      it 'rescues a subclass of an error by default' do\n        subject.rescue_from RuntimeError do |e|\n          error!(\"rescued from #{e.class.name}\", 500)\n        end\n        subject.get '/exception' do\n          raise ConnectionError\n        end\n        get '/exception'\n        expect(last_response).to be_server_error\n        expect(last_response.body).to eq('rescued from ConnectionError')\n      end\n\n      it 'rescues multiple specific errors' do\n        subject.rescue_from ConnectionError do |e|\n          error!(\"rescued from #{e.class.name}\", 500)\n        end\n        subject.rescue_from DatabaseError do |e|\n          error!(\"rescued from #{e.class.name}\", 500)\n        end\n        subject.get '/connection' do\n          raise ConnectionError\n        end\n        subject.get '/database' do\n          raise DatabaseError\n        end\n        get '/connection'\n        expect(last_response).to be_server_error\n        expect(last_response.body).to eq('rescued from ConnectionError')\n        get '/database'\n        expect(last_response).to be_server_error\n        expect(last_response.body).to eq('rescued from DatabaseError')\n      end\n\n      it 'does not rescue a different error' do\n        subject.rescue_from RuntimeError do |e|\n          error!(\"rescued from #{e.class.name}\", 500)\n        end\n        subject.get '/uncaught' do\n          raise CommunicationError\n        end\n        expect { get '/uncaught' }.to raise_error(CommunicationError)\n      end\n    end\n  end\n\n  describe '.rescue_from klass, lambda' do\n    it 'rescues an error with the lambda' do\n      subject.rescue_from ArgumentError, lambda {\n        error!('rescued with a lambda', 400)\n      }\n      subject.get('/rescue_lambda') { raise ArgumentError }\n\n      get '/rescue_lambda'\n      expect(last_response).to be_bad_request\n      expect(last_response.body).to eq('rescued with a lambda')\n    end\n\n    it 'can execute the lambda with an argument' do\n      subject.rescue_from ArgumentError, lambda { |e|\n        error!(e.message, 400)\n      }\n      subject.get('/rescue_lambda') { raise ArgumentError, 'lambda takes an argument' }\n\n      get '/rescue_lambda'\n      expect(last_response).to be_bad_request\n      expect(last_response.body).to eq('lambda takes an argument')\n    end\n  end\n\n  describe '.rescue_from klass, with: :method_name' do\n    it 'rescues an error with the specified method name' do\n      subject.helpers do\n        def rescue_arg_error\n          error!('500 ArgumentError', 500)\n        end\n\n        def rescue_no_method_error\n          error!('500 NoMethodError', 500)\n        end\n      end\n      subject.rescue_from ArgumentError, with: :rescue_arg_error\n      subject.rescue_from NoMethodError, with: :rescue_no_method_error\n      subject.get('/rescue_arg_error') { raise ArgumentError }\n      subject.get('/rescue_no_method_error') { raise NoMethodError }\n\n      get '/rescue_arg_error'\n      expect(last_response).to be_server_error\n      expect(last_response.body).to eq('500 ArgumentError')\n\n      get '/rescue_no_method_error'\n      expect(last_response).to be_server_error\n      expect(last_response.body).to eq('500 NoMethodError')\n    end\n\n    it 'aborts if the specified method name does not exist' do\n      subject.rescue_from :all, with: :not_exist_method\n      subject.get('/rescue_method') { raise StandardError }\n\n      expect { get '/rescue_method' }.to raise_error(NameError, /^undefined method [`']not_exist_method'/)\n    end\n\n    it 'correctly chooses exception handler if :all handler is specified' do\n      subject.helpers do\n        def rescue_arg_error\n          error!('500 ArgumentError', 500)\n        end\n\n        def rescue_all_errors\n          error!('500 AnotherError', 500)\n        end\n      end\n\n      subject.rescue_from ArgumentError, with: :rescue_arg_error\n      subject.rescue_from :all, with: :rescue_all_errors\n      subject.get('/argument_error') { raise ArgumentError }\n      subject.get('/another_error') { raise NoMethodError }\n\n      get '/argument_error'\n      expect(last_response).to be_server_error\n      expect(last_response.body).to eq('500 ArgumentError')\n\n      get '/another_error'\n      expect(last_response).to be_server_error\n      expect(last_response.body).to eq('500 AnotherError')\n    end\n  end\n\n  describe '.rescue_from klass, rescue_subclasses: boolean' do\n    before do\n      parent_error = Class.new(StandardError)\n      stub_const('ApiSpec::APIErrors::ParentError', parent_error)\n      stub_const('ApiSpec::APIErrors::ChildError', Class.new(parent_error))\n    end\n\n    it 'rescues error as well as subclass errors with rescue_subclasses option set' do\n      subject.rescue_from ApiSpec::APIErrors::ParentError, rescue_subclasses: true do |e|\n        error!(\"rescued from #{e.class.name}\", 500)\n      end\n      subject.get '/caught_child' do\n        raise ApiSpec::APIErrors::ChildError\n      end\n      subject.get '/caught_parent' do\n        raise ApiSpec::APIErrors::ParentError\n      end\n      subject.get '/uncaught_parent' do\n        raise StandardError\n      end\n\n      get '/caught_child'\n      expect(last_response).to be_server_error\n      get '/caught_parent'\n      expect(last_response).to be_server_error\n      expect { get '/uncaught_parent' }.to raise_error(StandardError)\n    end\n\n    it 'sets rescue_subclasses to true by default' do\n      subject.rescue_from ApiSpec::APIErrors::ParentError do |e|\n        error!(\"rescued from #{e.class.name}\", 500)\n      end\n      subject.get '/caught_child' do\n        raise ApiSpec::APIErrors::ChildError\n      end\n\n      get '/caught_child'\n      expect(last_response).to be_server_error\n    end\n\n    it 'does not rescue child errors if rescue_subclasses is false' do\n      subject.rescue_from ApiSpec::APIErrors::ParentError, rescue_subclasses: false do |e|\n        error!(\"rescued from #{e.class.name}\", 500)\n      end\n      subject.get '/uncaught' do\n        raise ApiSpec::APIErrors::ChildError\n      end\n      expect { get '/uncaught' }.to raise_error(ApiSpec::APIErrors::ChildError)\n    end\n  end\n\n  describe '.rescue_from :grape_exceptions' do\n    before do\n      subject.rescue_from :grape_exceptions\n    end\n\n    let(:grape_exception) do\n      Grape::Exceptions::Base.new(status: 400, message: 'Grape Error')\n    end\n\n    it 'rescues grape exceptions' do\n      exception = grape_exception\n      subject.get('/grape_exception') { raise exception }\n\n      get '/grape_exception'\n\n      expect(last_response.status).to eq(exception.status)\n      expect(last_response.body).to eq(exception.message)\n    end\n\n    it 'rescues grape exceptions with a user-defined handler' do\n      subject.rescue_from grape_exception.class do |_error|\n        error!('Redefined Error', 403)\n      end\n\n      exception = grape_exception\n      subject.get('/grape_exception') { raise exception }\n\n      get '/grape_exception'\n\n      expect(last_response).to be_forbidden\n      expect(last_response.body).to eq('Redefined Error')\n    end\n  end\n\n  describe '.error_format' do\n    it 'rescues all errors and return :txt' do\n      subject.rescue_from :all\n      subject.format :txt\n      subject.get '/exception' do\n        raise 'rain!'\n      end\n      get '/exception'\n      expect(last_response.body).to eql 'rain!'\n    end\n\n    it 'rescues all errors and return :txt with backtrace' do\n      subject.rescue_from :all, backtrace: true\n      subject.format :txt\n      subject.get '/exception' do\n        raise 'rain!'\n      end\n      get '/exception'\n      expect(last_response.body.start_with?(\"rain!\\r\\n\")).to be true\n    end\n\n    it 'rescues all errors with a default formatter' do\n      subject.default_format :foo\n      subject.content_type :foo, 'text/foo'\n      subject.rescue_from :all\n      subject.get '/exception' do\n        raise 'rain!'\n      end\n      get '/exception.foo'\n      expect(last_response.body).to start_with 'rain!'\n    end\n\n    it 'defaults the error formatter to format' do\n      subject.format :json\n      subject.rescue_from :all\n      subject.content_type :json, 'application/json'\n      subject.content_type :foo, 'text/foo'\n      subject.get '/exception' do\n        raise 'rain!'\n      end\n      get '/exception.json'\n      expect(last_response.body).to eq('{\"error\":\"rain!\"}')\n      get '/exception.foo'\n      expect(last_response.body).to eq('{\"error\":\"rain!\"}')\n    end\n\n    context 'class' do\n      let(:custom_error_formatter) do\n        Class.new do\n          def self.call(message, _backtrace, _options, _env, _original_exception)\n            \"message: #{message} @backtrace\"\n          end\n        end\n      end\n\n      it 'returns a custom error format' do\n        subject.rescue_from :all, backtrace: true\n        subject.error_formatter :txt, custom_error_formatter\n        subject.get('/exception') { raise 'rain!' }\n\n        get '/exception'\n        expect(last_response.body).to eq('message: rain! @backtrace')\n      end\n\n      it 'returns a custom error format (using keyword :with)' do\n        subject.rescue_from :all, backtrace: true\n        subject.error_formatter :txt, with: custom_error_formatter\n        subject.get('/exception') { raise 'rain!' }\n\n        get '/exception'\n        expect(last_response.body).to eq('message: rain! @backtrace')\n      end\n\n      it 'returns a modified error with a custom error format' do\n        subject.rescue_from :all, backtrace: true do |e|\n          error!('raining dogs and cats', 418, {}, e.backtrace, e)\n        end\n        subject.error_formatter :txt, with: custom_error_formatter\n        subject.get '/exception' do\n          raise 'rain!'\n        end\n        get '/exception'\n        expect(last_response.body).to eq('message: raining dogs and cats @backtrace')\n      end\n    end\n\n    it 'rescues all errors and return :json' do\n      subject.rescue_from :all\n      subject.format :json\n      subject.get '/exception' do\n        raise 'rain!'\n      end\n      get '/exception'\n      expect(last_response.body).to eql '{\"error\":\"rain!\"}'\n    end\n\n    it 'rescues all errors and return :json with backtrace' do\n      subject.rescue_from :all, backtrace: true\n      subject.format :json\n      subject.get '/exception' do\n        raise 'rain!'\n      end\n      get '/exception'\n      json = Grape::Json.load(last_response.body)\n      expect(json['error']).to eql 'rain!'\n      expect(json['backtrace'].length).to be > 0\n    end\n\n    it 'rescues error! and return txt' do\n      subject.format :txt\n      subject.get '/error' do\n        error!('Access Denied', 401)\n      end\n      get '/error'\n      expect(last_response.body).to eql 'Access Denied'\n    end\n\n    context 'with json format' do\n      shared_examples_for 'a json format api' do |error_message|\n        subject { JSON.parse(last_response.body) }\n\n        before  { get '/error' }\n\n        let(:app) do\n          Class.new(Grape::API) do\n            format :json\n            get('/error') { error!(error_message, 401) }\n          end\n        end\n\n        context \"when error! called with #{error_message.class.name}\" do\n          it { is_expected.to eq('error' => 'failure') }\n        end\n      end\n\n      it_behaves_like 'a json format api', 'failure'\n      it_behaves_like 'a json format api', :failure\n      it_behaves_like 'a json format api', { error: :failure }\n    end\n\n    context 'when rescue_from enables backtrace without original exception' do\n      let(:app) do\n        response_type = response_format\n\n        Class.new(Grape::API) do\n          format response_type\n\n          rescue_from :all, backtrace: true, original_exception: false do |e|\n            error!('raining dogs and cats!', 418, {}, e.backtrace, e)\n          end\n\n          get '/exception' do\n            raise 'rain!'\n          end\n        end\n      end\n\n      before do\n        get '/exception'\n      end\n\n      context 'with json response type format' do\n        subject { JSON.parse(last_response.body) }\n\n        let(:response_format) { :json }\n\n        it { is_expected.to include('error' => a_kind_of(String), 'backtrace' => a_kind_of(Array)) }\n        it { is_expected.not_to include('original_exception') }\n      end\n\n      context 'with txt response type format' do\n        subject { last_response.body }\n\n        let(:response_format) { :txt }\n\n        it { is_expected.to include('backtrace') }\n        it { is_expected.not_to include('original_exception') }\n      end\n\n      context 'with xml response type format' do\n        subject { Grape::Xml.parse(last_response.body)['error'] }\n\n        let(:response_format) { :xml }\n\n        it { is_expected.to have_key('backtrace') }\n        it { is_expected.not_to have_key('original-exception') }\n      end\n    end\n\n    context 'when rescue_from enables original exception without backtrace' do\n      let(:app) do\n        response_type = response_format\n\n        Class.new(Grape::API) do\n          format response_type\n\n          rescue_from :all, backtrace: false, original_exception: true do |e|\n            error!('raining dogs and cats!', 418, {}, e.backtrace, e)\n          end\n\n          get '/exception' do\n            raise 'rain!'\n          end\n        end\n      end\n\n      before do\n        get '/exception'\n      end\n\n      context 'with json response type format' do\n        subject { JSON.parse(last_response.body) }\n\n        let(:response_format) { :json }\n\n        it { is_expected.to include('error' => a_kind_of(String), 'original_exception' => a_kind_of(String)) }\n        it { is_expected.not_to include('backtrace') }\n      end\n\n      context 'with txt response type format' do\n        subject { last_response.body }\n\n        let(:response_format) { :txt }\n\n        it { is_expected.to include('original exception') }\n        it { is_expected.not_to include('backtrace') }\n      end\n\n      context 'with xml response type format' do\n        subject { Grape::Xml.parse(last_response.body)['error'] }\n\n        let(:response_format) { :xml }\n\n        it { is_expected.to have_key('original-exception') }\n        it { is_expected.not_to have_key('backtrace') }\n      end\n    end\n\n    context 'when rescue_from include backtrace and original exception' do\n      let(:app) do\n        response_type = response_format\n\n        Class.new(Grape::API) do\n          format response_type\n\n          rescue_from :all, backtrace: true, original_exception: true do |e|\n            error!('raining dogs and cats!', 418, {}, e.backtrace, e)\n          end\n\n          get '/exception' do\n            raise 'rain!'\n          end\n        end\n      end\n\n      before do\n        get '/exception'\n      end\n\n      context 'with json response type format' do\n        subject { JSON.parse(last_response.body) }\n\n        let(:response_format) { :json }\n\n        it { is_expected.to include('error' => a_kind_of(String), 'backtrace' => a_kind_of(Array), 'original_exception' => a_kind_of(String)) }\n      end\n\n      context 'with txt response type format' do\n        subject { last_response.body }\n\n        let(:response_format) { :txt }\n\n        it { is_expected.to include('backtrace', 'original exception') }\n      end\n\n      context 'with xml response type format' do\n        subject { Grape::Xml.parse(last_response.body)['error'] }\n\n        let(:response_format) { :xml }\n\n        it { is_expected.to have_key('backtrace') & have_key('original-exception') }\n      end\n    end\n\n    context 'when rescue validation errors include backtrace and original exception' do\n      let(:app) do\n        response_type = response_format\n\n        Class.new(Grape::API) do\n          format response_type\n\n          rescue_from Grape::Exceptions::ValidationErrors, backtrace: true, original_exception: true do |e|\n            error!(e, 418, {}, e.backtrace, e)\n          end\n\n          params do\n            requires :weather\n          end\n          get '/forecast' do\n            'sunny'\n          end\n        end\n      end\n\n      before do\n        get '/forecast'\n      end\n\n      context 'with json response type format' do\n        subject { JSON.parse(last_response.body) }\n\n        let(:response_format) { :json }\n\n        it 'does not include backtrace or original exception' do\n          expect(subject).to match([{ 'messages' => ['is missing'], 'params' => ['weather'] }])\n        end\n      end\n\n      context 'with txt response type format' do\n        subject { last_response.body }\n\n        let(:response_format) { :txt }\n\n        it { is_expected.to include('backtrace', 'original exception') }\n      end\n\n      context 'with xml response type format' do\n        subject { Grape::Xml.parse(last_response.body)['error'] }\n\n        let(:response_format) { :xml }\n\n        it { is_expected.to have_key('backtrace') & have_key('original-exception') }\n      end\n    end\n  end\n\n  describe '.content_type' do\n    it 'sets additional content-type' do\n      subject.content_type :xls, 'application/vnd.ms-excel'\n      subject.get :excel do\n        'some binary content'\n      end\n      get '/excel.xls'\n      expect(last_response.content_type).to eq('application/vnd.ms-excel')\n    end\n\n    it 'allows to override content-type' do\n      subject.get :content do\n        content_type 'text/javascript'\n        'var x = 1;'\n      end\n      get '/content'\n      expect(last_response.content_type).to eq('text/javascript')\n    end\n\n    it 'removes existing content types' do\n      subject.content_type :xls, 'application/vnd.ms-excel'\n      subject.get :excel do\n        'some binary content'\n      end\n      get '/excel.json'\n      expect(last_response.status).to eq(406)\n      expect(last_response.body).to eq(Rack::Utils.escape_html(\"The requested format 'txt' is not supported.\"))\n    end\n  end\n\n  describe '.formatter' do\n    context 'multiple formatters' do\n      before do\n        subject.formatter :json, ->(object, _env) { \"{\\\"custom_formatter\\\":\\\"#{object[:some]}\\\"}\" }\n        subject.formatter :txt, ->(object, _env) { \"custom_formatter: #{object[:some]}\" }\n        subject.get :simple do\n          { some: 'hash' }\n        end\n      end\n\n      it 'sets one formatter' do\n        get '/simple.json'\n        expect(last_response.body).to eql '{\"custom_formatter\":\"hash\"}'\n      end\n\n      it 'sets another formatter' do\n        get '/simple.txt'\n        expect(last_response.body).to eql 'custom_formatter: hash'\n      end\n    end\n\n    context 'custom formatter' do\n      before do\n        subject.content_type :json, 'application/json'\n        subject.content_type :custom, 'application/custom'\n        subject.formatter :custom, ->(object, _env) { \"{\\\"custom_formatter\\\":\\\"#{object[:some]}\\\"}\" }\n        subject.get :simple do\n          { some: 'hash' }\n        end\n      end\n\n      it 'uses json' do\n        get '/simple.json'\n        expect(last_response.body).to eql '{\"some\":\"hash\"}'\n      end\n\n      it 'uses custom formatter' do\n        get '/simple.custom', 'HTTP_ACCEPT' => 'application/custom'\n        expect(last_response.body).to eql '{\"custom_formatter\":\"hash\"}'\n      end\n    end\n\n    context 'custom formatter class' do\n      let(:custom_formatter) do\n        Module.new do\n          def self.call(object, _env)\n            \"{\\\"custom_formatter\\\":\\\"#{object[:some]}\\\"}\"\n          end\n        end\n      end\n\n      before do\n        subject.content_type :json, 'application/json'\n        subject.content_type :custom, 'application/custom'\n        subject.formatter :custom, custom_formatter\n        subject.get :simple do\n          { some: 'hash' }\n        end\n      end\n\n      it 'uses json' do\n        get '/simple.json'\n        expect(last_response.body).to eql '{\"some\":\"hash\"}'\n      end\n\n      it 'uses custom formatter' do\n        get '/simple.custom', 'HTTP_ACCEPT' => 'application/custom'\n        expect(last_response.body).to eql '{\"custom_formatter\":\"hash\"}'\n      end\n    end\n  end\n\n  describe '.parser' do\n    it 'parses data in format requested by content-type' do\n      subject.format :json\n      subject.post '/data' do\n        { x: params[:x] }\n      end\n      post '/data', '{\"x\":42}', 'CONTENT_TYPE' => 'application/json'\n      expect(last_response).to be_created\n      expect(last_response.body).to eq('{\"x\":42}')\n    end\n\n    context 'lambda parser' do\n      before do\n        subject.content_type :txt, 'text/plain'\n        subject.content_type :custom, 'text/custom'\n        subject.parser :custom, ->(object, _env) { { object.to_sym => object.to_s.reverse } }\n        subject.put :simple do\n          params[:simple]\n        end\n      end\n\n      ['text/custom', 'text/custom; charset=UTF-8'].each do |content_type|\n        it \"uses parser for #{content_type}\" do\n          put '/simple', 'simple', 'CONTENT_TYPE' => content_type\n          expect(last_response).to be_successful\n          expect(last_response.body).to eql 'elpmis'\n        end\n      end\n    end\n\n    context 'custom parser class' do\n      let(:custom_parser) do\n        Module.new do\n          def self.call(object, _env)\n            { object.to_sym => object.to_s.reverse }\n          end\n        end\n      end\n\n      before do\n        subject.content_type :txt, 'text/plain'\n        subject.content_type :custom, 'text/custom'\n        subject.parser :custom, custom_parser\n        subject.put :simple do\n          params[:simple]\n        end\n      end\n\n      it 'uses custom parser' do\n        put '/simple', 'simple', 'CONTENT_TYPE' => 'text/custom'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eql 'elpmis'\n      end\n    end\n\n    if Object.const_defined? :MultiXml\n      context 'multi_xml' do\n        it \"doesn't parse yaml\" do\n          subject.put :yaml do\n            params[:tag]\n          end\n          put '/yaml', '<tag type=\"symbol\">a123</tag>', 'CONTENT_TYPE' => 'application/xml'\n          expect(last_response).to be_bad_request\n          expect(last_response.body).to eql 'Disallowed type attribute: \"symbol\"'\n        end\n      end\n    else\n      context 'default xml parser' do\n        it 'parses symbols' do\n          subject.put :yaml do\n            params[:tag]\n          end\n          body = '<tag type=\"symbol\">a123</tag>'\n          put '/yaml', body, 'CONTENT_TYPE' => 'application/xml'\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq(Grape::Xml.parse(body)['tag'].to_s)\n        end\n      end\n    end\n    context 'none parser class' do\n      before do\n        subject.parser :json, nil\n        subject.put 'data' do\n          \"body: #{env[Grape::Env::API_REQUEST_BODY]}\"\n        end\n      end\n\n      it 'does not parse data' do\n        put '/data', 'not valid json', 'CONTENT_TYPE' => 'application/json'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('body: not valid json')\n      end\n    end\n  end\n\n  describe '.default_format' do\n    before do\n      subject.format :json\n      subject.default_format :json\n    end\n\n    it 'returns data in default format' do\n      subject.get '/data' do\n        { x: 42 }\n      end\n      get '/data'\n      expect(last_response).to be_successful\n      expect(last_response.body).to eq('{\"x\":42}')\n    end\n\n    it 'parses data in default format' do\n      subject.post '/data' do\n        { x: params[:x] }\n      end\n      post '/data', '{\"x\":42}', 'CONTENT_TYPE' => ''\n      expect(last_response).to be_created\n      expect(last_response.body).to eq('{\"x\":42}')\n    end\n  end\n\n  describe '.default_error_status' do\n    it 'allows setting default_error_status' do\n      subject.rescue_from :all\n      subject.default_error_status 200\n      subject.get '/exception' do\n        raise 'rain!'\n      end\n      get '/exception'\n      expect(last_response).to be_successful\n    end\n\n    it 'has a default error status' do\n      subject.rescue_from :all\n      subject.get '/exception' do\n        raise 'rain!'\n      end\n      get '/exception'\n      expect(last_response).to be_server_error\n    end\n\n    it 'uses the default error status in error!' do\n      subject.rescue_from :all\n      subject.default_error_status 400\n      subject.get '/exception' do\n        error! 'rain!'\n      end\n      get '/exception'\n      expect(last_response).to be_bad_request\n    end\n  end\n\n  context 'routes' do\n    describe 'empty api structure' do\n      it 'returns an empty array of routes' do\n        expect(subject.routes).to eq([])\n      end\n    end\n\n    describe 'single method api structure' do\n      before do\n        subject.get :ping do\n          'pong'\n        end\n      end\n\n      it 'returns one route' do\n        expect(subject.routes.size).to eq(1)\n        route = subject.routes[0]\n        expect(route.version).to be_nil\n        expect(route.path).to eq('/ping(.:format)')\n        expect(route.request_method).to eq(Rack::GET)\n      end\n    end\n\n    describe 'api structure with two versions and a namespace' do\n      before do\n        subject.version 'v1', using: :path\n        subject.get 'version' do\n          api.version\n        end\n        # version v2\n        subject.version 'v2', using: :path\n        subject.prefix 'p'\n        subject.namespace 'n1' do\n          namespace 'n2' do\n            get 'version' do\n              api.version\n            end\n          end\n        end\n      end\n\n      it 'returns the latest version set' do\n        expect(subject.version).to eq('v2')\n      end\n\n      it 'returns versions' do\n        expect(subject.versions).to eq(%w[v1 v2])\n      end\n\n      it 'sets route paths' do\n        expect(subject.routes.size).to be >= 2\n        expect(subject.routes[0].path).to eq('/:version/version(.:format)')\n        expect(subject.routes[1].path).to eq('/p/:version/n1/n2/version(.:format)')\n      end\n\n      it 'sets route versions' do\n        expect(subject.routes[0].version).to eq('v1')\n        expect(subject.routes[1].version).to eq('v2')\n      end\n\n      it 'sets a nested namespace' do\n        expect(subject.routes[1].namespace).to eq('/n1/n2')\n      end\n\n      it 'sets prefix' do\n        expect(subject.routes[1].prefix).to eq('p')\n      end\n    end\n\n    describe 'api structure with additional parameters' do\n      before do\n        subject.params do\n          requires :token, desc: 'a token'\n          optional :limit, desc: 'the limit'\n        end\n        subject.get 'split/:string' do\n          params[:string].split(params[:token], (params[:limit] || 0).to_i)\n        end\n      end\n\n      it 'splits a string' do\n        get '/split/a,b,c.json', token: ','\n        expect(last_response.body).to eq('[\"a\",\"b\",\"c\"]')\n      end\n\n      it 'splits a string with limit' do\n        get '/split/a,b,c.json', token: ',', limit: '2'\n        expect(last_response.body).to eq('[\"a\",\"b,c\"]')\n      end\n\n      it 'sets params' do\n        expect(subject.routes.map do |route|\n          { params: route.params }\n        end).to eq [\n          {\n            params: {\n              'string' => '',\n              'token' => { required: true, desc: 'a token' },\n              'limit' => { required: false, desc: 'the limit' }\n            }\n          }\n        ]\n      end\n    end\n\n    describe 'api structure with multiple apis' do\n      before do\n        subject.params do\n          requires :one, desc: 'a token'\n          optional :two, desc: 'the limit'\n        end\n        subject.get 'one' do\n        end\n\n        subject.params do\n          requires :three, desc: 'a token'\n          optional :four, desc: 'the limit'\n        end\n        subject.get 'two' do\n        end\n      end\n\n      it 'sets params' do\n        expect(subject.routes.map do |route|\n          { params: route.params }\n        end).to eq [\n          {\n            params: {\n              'one' => { required: true, desc: 'a token' },\n              'two' => { required: false, desc: 'the limit' }\n            }\n          },\n          {\n            params: {\n              'three' => { required: true, desc: 'a token' },\n              'four' => { required: false, desc: 'the limit' }\n            }\n          }\n        ]\n      end\n    end\n\n    describe 'api structure with an api without params' do\n      before do\n        subject.params do\n          requires :one, desc: 'a token'\n          optional :two, desc: 'the limit'\n        end\n        subject.get 'one' do\n        end\n\n        subject.get 'two' do\n        end\n      end\n\n      it 'sets params' do\n        expect(subject.routes.map do |route|\n          { params: route.params }\n        end).to eq [\n          {\n            params: {\n              'one' => { required: true, desc: 'a token' },\n              'two' => { required: false, desc: 'the limit' }\n            }\n          },\n          {\n            params: {}\n          }\n        ]\n      end\n    end\n\n    describe 'api with a custom route setting' do\n      before do\n        subject.route_setting :custom, key: 'value'\n        subject.get 'one'\n      end\n\n      it 'exposed' do\n        expect(subject.routes.count).to eq 1\n        route = subject.routes.first\n        expect(route.settings[:custom]).to eq(key: 'value')\n      end\n    end\n\n    describe 'status' do\n      it 'can be set to arbitrary Integer value' do\n        subject.get '/foo' do\n          status 210\n        end\n        get '/foo'\n        expect(last_response.status).to eq 210\n      end\n\n      it 'can be set with a status code symbol' do\n        subject.get '/foo' do\n          status :see_other\n        end\n        get '/foo'\n        expect(last_response.status).to eq 303\n      end\n    end\n  end\n\n  context 'desc' do\n    it 'empty array of routes' do\n      expect(subject.routes).to eq([])\n    end\n\n    it 'empty array of routes' do\n      subject.desc 'grape api'\n      expect(subject.routes).to eq([])\n    end\n\n    it 'describes a method' do\n      subject.desc 'first method'\n      subject.get :first\n      expect(subject.routes.length).to eq(1)\n      route = subject.routes.first\n      expect(route.description).to eq('first method')\n      expect(route.params).to eq({})\n      expect(route.options).to be_a(Hash)\n    end\n\n    it 'has params which does not include format and version as named captures' do\n      subject.version :v1, using: :path\n      subject.get :first\n      param_keys = subject.routes.first.params.keys\n      expect(param_keys).not_to include('format')\n      expect(param_keys).not_to include('version')\n    end\n\n    it 'describes methods separately' do\n      subject.desc 'first method'\n      subject.get :first\n      subject.desc 'second method'\n      subject.get :second\n      expect(subject.routes.count).to eq(2)\n      expect(subject.routes.map do |route|\n        { description: route.description, params: route.params }\n      end).to eq [\n        { description: 'first method', params: {} },\n        { description: 'second method', params: {} }\n      ]\n    end\n\n    it 'resets desc' do\n      subject.desc 'first method'\n      subject.get :first\n      subject.get :second\n      expect(subject.routes.map do |route|\n        { description: route.description, params: route.params }\n      end).to eq [\n        { description: 'first method', params: {} },\n        { description: nil, params: {} }\n      ]\n    end\n\n    it 'namespaces and describe arbitrary parameters' do\n      subject.namespace 'ns' do\n        desc 'ns second', foo: 'bar'\n        get 'second'\n      end\n      expect(subject.routes.map do |route|\n        { description: route.description, foo: route.options[:foo], params: route.params }\n      end).to eq [\n        { description: 'ns second', foo: 'bar', params: {} }\n      ]\n    end\n\n    it 'includes detail' do\n      subject.desc 'method', detail: 'method details'\n      subject.get 'method'\n      expect(subject.routes.map do |route|\n        { description: route.description, detail: route.detail, params: route.params }\n      end).to eq [\n        { description: 'method', detail: 'method details', params: {} }\n      ]\n    end\n\n    it 'describes a method with parameters' do\n      subject.desc 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } }\n      subject.get 'reverse' do\n        params[:s].reverse\n      end\n      expect(subject.routes.map do |route|\n        { description: route.description, params: route.params }\n      end).to eq [\n        { description: 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } } }\n      ]\n    end\n\n    it 'does not inherit param descriptions in consequent namespaces' do\n      subject.desc 'global description'\n      subject.params do\n        requires :param1\n        optional :param2\n      end\n      subject.namespace 'ns1' do\n        get {}\n      end\n      subject.params do\n        optional :param2\n      end\n      subject.namespace 'ns2' do\n        get {}\n      end\n      routes_doc = subject.routes.map do |route|\n        { description: route.description, params: route.params }\n      end\n      expect(routes_doc).to eq [\n        { description: 'global description',\n          params: {\n            'param1' => { required: true },\n            'param2' => { required: false }\n          } },\n        { description: 'global description',\n          params: {\n            'param2' => { required: false }\n          } }\n      ]\n    end\n\n    it 'merges the parameters of the namespace with the parameters of the method' do\n      subject.desc 'namespace'\n      subject.params do\n        requires :ns_param, desc: 'namespace parameter'\n      end\n      subject.namespace 'ns' do\n        desc 'method'\n        params do\n          optional :method_param, desc: 'method parameter'\n        end\n        get 'method'\n      end\n\n      routes_doc = subject.routes.map do |route|\n        { description: route.description, params: route.params }\n      end\n      expect(routes_doc).to eq [\n        { description: 'method',\n          params: {\n            'ns_param' => { required: true, desc: 'namespace parameter' },\n            'method_param' => { required: false, desc: 'method parameter' }\n          } }\n      ]\n    end\n\n    it 'merges the parameters of nested namespaces' do\n      subject.desc 'ns1'\n      subject.params do\n        optional :ns_param, desc: 'ns param 1'\n        requires :ns1_param, desc: 'ns1 param'\n      end\n      subject.namespace 'ns1' do\n        desc 'ns2'\n        params do\n          requires :ns_param, desc: 'ns param 2'\n          requires :ns2_param, desc: 'ns2 param'\n        end\n        namespace 'ns2' do\n          desc 'method'\n          params do\n            optional :method_param, desc: 'method param'\n          end\n          get 'method'\n        end\n      end\n      expect(subject.routes.map do |route|\n        { description: route.description, params: route.params }\n      end).to eq [\n        { description: 'method',\n          params: {\n            'ns_param' => { required: true, desc: 'ns param 2' },\n            'ns1_param' => { required: true, desc: 'ns1 param' },\n            'ns2_param' => { required: true, desc: 'ns2 param' },\n            'method_param' => { required: false, desc: 'method param' }\n          } }\n      ]\n    end\n\n    it 'groups nested params and prevents overwriting of params with same name in different groups' do\n      subject.desc 'method'\n      subject.params do\n        group :group1, type: Array do\n          optional :param1, desc: 'group1 param1 desc'\n          requires :param2, desc: 'group1 param2 desc'\n        end\n        group :group2, type: Array do\n          optional :param1, desc: 'group2 param1 desc'\n          requires :param2, desc: 'group2 param2 desc'\n        end\n      end\n      subject.get 'method'\n\n      expect(subject.routes.map(&:params)).to eq [{\n        'group1' => { required: true, type: 'Array' },\n        'group1[param1]' => { required: false, desc: 'group1 param1 desc' },\n        'group1[param2]' => { required: true, desc: 'group1 param2 desc' },\n        'group2' => { required: true, type: 'Array' },\n        'group2[param1]' => { required: false, desc: 'group2 param1 desc' },\n        'group2[param2]' => { required: true, desc: 'group2 param2 desc' }\n      }]\n    end\n\n    it 'uses full name of parameters in nested groups' do\n      subject.desc 'nesting'\n      subject.params do\n        requires :root_param, desc: 'root param'\n        group :nested, type: Array do\n          requires :nested_param, desc: 'nested param'\n        end\n      end\n      subject.get 'method'\n      expect(subject.routes.map do |route|\n        { description: route.description, params: route.params }\n      end).to eq [\n        { description: 'nesting',\n          params: {\n            'root_param' => { required: true, desc: 'root param' },\n            'nested' => { required: true, type: 'Array' },\n            'nested[nested_param]' => { required: true, desc: 'nested param' }\n          } }\n      ]\n    end\n\n    it 'allows to set the type attribute on :group element' do\n      subject.params do\n        group :foo, type: Array do\n          optional :bar\n        end\n      end\n      subject.get 'method'\n      expect(subject.routes.map do |route|\n        { description: route.description, params: route.params }\n      end).to eq [\n        { description: nil, params: { 'foo' => { required: true, type: 'Array' }, 'foo[bar]' => { required: false } } }\n      ]\n    end\n\n    it 'parses parameters when no description is given' do\n      subject.params do\n        requires :one_param, desc: 'one param'\n      end\n      subject.get 'method'\n      expect(subject.routes.map do |route|\n        { description: route.description, params: route.params }\n      end).to eq [\n        { description: nil, params: { 'one_param' => { required: true, desc: 'one param' } } }\n      ]\n    end\n\n    it 'does not symbolize params' do\n      subject.desc 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } }\n      subject.get 'reverse/:s' do\n        params[:s].reverse\n      end\n      expect(subject.routes.map do |route|\n        { description: route.description, params: route.params }\n      end).to eq [\n        { description: 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } } }\n      ]\n    end\n  end\n\n  describe '.mount' do\n    let(:mounted_app) { ->(_env) { [200, {}, ['MOUNTED']] } }\n\n    context 'with a bare rack app' do\n      before do\n        subject.mount mounted_app => '/mounty'\n      end\n\n      it 'makes a bare Rack app available at the endpoint' do\n        get '/mounty'\n        expect(last_response.body).to eq('MOUNTED')\n      end\n\n      it 'anchors the routes, passing all subroutes to it' do\n        get '/mounty/awesome'\n        expect(last_response.body).to eq('MOUNTED')\n      end\n\n      it 'is able to cascade' do\n        subject.mount lambda { |env|\n          headers = {}\n          headers['X-Cascade'] == 'pass' unless env[Rack::PATH_INFO].include?('boo')\n          [200, headers, ['Farfegnugen']]\n        } => '/'\n\n        get '/boo'\n        expect(last_response.body).to eq('Farfegnugen')\n        get '/mounty'\n        expect(last_response.body).to eq('MOUNTED')\n      end\n    end\n\n    context 'without a hash' do\n      it 'calls through setting the route to \"/\"' do\n        subject.mount mounted_app\n        get '/'\n        expect(last_response.body).to eq('MOUNTED')\n      end\n    end\n\n    context 'mounting an API' do\n      it 'applies the settings of the mounting api' do\n        subject.version 'v1', using: :path\n\n        subject.namespace :cool do\n          app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass\n          app.get('/awesome') do\n            'yo'\n          end\n\n          mount app\n        end\n\n        get '/v1/cool/awesome'\n        expect(last_response.body).to eq('yo')\n      end\n\n      it 'applies the settings to nested mounted apis' do\n        subject.version 'v1', using: :path\n\n        subject.namespace :cool do\n          inner_app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass\n          inner_app.get('/awesome') do\n            'yo'\n          end\n\n          app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass\n          app.mount inner_app\n          mount app\n        end\n\n        get '/v1/cool/awesome'\n        expect(last_response.body).to eq('yo')\n      end\n\n      context 'when some rescues are defined by mounted' do\n        it 'inherits parent rescues' do\n          subject.rescue_from :all do |e|\n            error!(\"rescued from #{e.message}\", 202)\n          end\n\n          app = Class.new(described_class)\n\n          subject.namespace :mounted do\n            app.rescue_from ArgumentError\n            app.get('/fail') { raise 'doh!' }\n            mount app\n          end\n\n          get '/mounted/fail'\n          expect(last_response).to be_accepted\n          expect(last_response.body).to eq('rescued from doh!')\n        end\n\n        it 'prefers rescues defined by mounted if they rescue similar error class' do\n          subject.rescue_from StandardError do\n            error!('outer rescue')\n          end\n\n          app = Class.new(described_class)\n\n          subject.namespace :mounted do\n            rescue_from StandardError do\n              error!('inner rescue')\n            end\n            app.get('/fail') { raise 'doh!' }\n            mount app\n          end\n\n          get '/mounted/fail'\n          expect(last_response.body).to eq('inner rescue')\n        end\n\n        it 'prefers rescues defined by mounted even if outer is more specific' do\n          subject.rescue_from ArgumentError do\n            error!('outer rescue')\n          end\n\n          app = Class.new(described_class)\n\n          subject.namespace :mounted do\n            rescue_from StandardError do\n              error!('inner rescue')\n            end\n            app.get('/fail') { raise ArgumentError.new }\n            mount app\n          end\n\n          get '/mounted/fail'\n          expect(last_response.body).to eq('inner rescue')\n        end\n\n        it 'prefers more specific rescues defined by mounted' do\n          subject.rescue_from StandardError do\n            error!('outer rescue')\n          end\n\n          app = Class.new(described_class)\n\n          subject.namespace :mounted do\n            rescue_from ArgumentError do\n              error!('inner rescue')\n            end\n            app.get('/fail') { raise ArgumentError.new }\n            mount app\n          end\n\n          get '/mounted/fail'\n          expect(last_response.body).to eq('inner rescue')\n        end\n      end\n\n      it 'collects the routes of the mounted api' do\n        subject.namespace :cool do\n          app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass\n          app.get('/awesome') {}\n          app.post('/sauce') {}\n          mount app\n        end\n        expect(subject.routes.size).to eq(2)\n        expect(subject.routes.first.path).to match(%r{/cool/awesome})\n        expect(subject.routes.last.path).to match(%r{/cool/sauce})\n      end\n\n      it 'mounts on a path' do\n        subject.namespace :cool do\n          app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass\n          app.get '/awesome' do\n            'sauce'\n          end\n          mount app => '/mounted'\n        end\n        get '/mounted/cool/awesome'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('sauce')\n      end\n\n      it 'mounts on a nested path' do\n        app1 = Class.new(described_class)\n        app2 = Class.new(described_class)\n        app2.get '/nice' do\n          'play'\n        end\n        # NOTE: that the reverse won't work, mount from outside-in\n        app3 = subject\n        app3.mount app1 => '/app1'\n        app1.mount app2 => '/app2'\n        get '/app1/app2/nice'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('play')\n        options '/app1/app2/nice'\n        expect(last_response).to be_no_content\n      end\n\n      it 'responds to options' do\n        app = Class.new(described_class)\n        app.get '/colour' do\n          'red'\n        end\n        app.namespace :pears do\n          get '/colour' do\n            'green'\n          end\n        end\n        subject.namespace :apples do\n          mount app\n        end\n\n        get '/apples/colour'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('red')\n        options '/apples/colour'\n        expect(last_response).to be_no_content\n        get '/apples/pears/colour'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('green')\n        options '/apples/pears/colour'\n        expect(last_response).to be_no_content\n      end\n\n      it 'responds to options with path versioning' do\n        subject.version 'v1', using: :path\n        subject.namespace :apples do\n          app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass\n          app.get('/colour') do\n            'red'\n          end\n          mount app\n        end\n\n        get '/v1/apples/colour'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('red')\n        options '/v1/apples/colour'\n        expect(last_response).to be_no_content\n      end\n\n      it 'mounts a versioned API with nested resources' do\n        api = Class.new(described_class) do\n          version 'v1'\n          resources :users do\n            get :hello do\n              'hello users'\n            end\n          end\n        end\n        subject.mount api\n\n        get '/v1/users/hello'\n        expect(last_response.body).to eq('hello users')\n      end\n\n      it 'mounts a prefixed API with nested resources' do\n        api = Class.new(described_class) do\n          prefix 'api'\n          resources :users do\n            get :hello do\n              'hello users'\n            end\n          end\n        end\n        subject.mount api\n\n        get '/api/users/hello'\n        expect(last_response.body).to eq('hello users')\n      end\n\n      it 'applies format to a mounted API with nested resources' do\n        api = Class.new(described_class) do\n          format :json\n          resources :users do\n            get do\n              { users: true }\n            end\n          end\n        end\n        subject.mount api\n\n        get '/users'\n        expect(last_response.body).to eq({ users: true }.to_json)\n      end\n\n      it 'applies auth to a mounted API with nested resources' do\n        api = Class.new(described_class) do\n          format :json\n          http_basic do |username, password|\n            username == 'username' && password == 'password'\n          end\n          resources :users do\n            get do\n              { users: true }\n            end\n          end\n        end\n        subject.mount api\n\n        get '/users'\n        expect(last_response).to be_unauthorized\n\n        get '/users', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('username', 'password')\n        expect(last_response.body).to eq({ users: true }.to_json)\n      end\n\n      it 'mounts multiple versioned APIs with nested resources' do\n        api1 = Class.new(described_class) do\n          version 'one', using: :header, vendor: 'test'\n          resources :users do\n            get :hello do\n              'one'\n            end\n          end\n        end\n\n        api2 = Class.new(described_class) do\n          version 'two', using: :header, vendor: 'test'\n          resources :users do\n            get :hello do\n              'two'\n            end\n          end\n        end\n\n        subject.mount api1\n        subject.mount api2\n\n        versioned_get '/users/hello', 'one', using: :header, vendor: 'test'\n        expect(last_response.body).to eq('one')\n        versioned_get '/users/hello', 'two', using: :header, vendor: 'test'\n        expect(last_response.body).to eq('two')\n      end\n\n      it 'recognizes potential versions with mounted path' do\n        a = Class.new(described_class) do\n          version :v1, using: :path\n\n          get '/hello' do\n            'hello'\n          end\n        end\n\n        b = Class.new(described_class) do\n          version :v1, using: :path\n\n          get '/world' do\n            'world'\n          end\n        end\n\n        subject.mount a => '/one'\n        subject.mount b => '/two'\n\n        get '/one/v1/hello'\n        expect(last_response).to be_successful\n\n        get '/two/v1/world'\n        expect(last_response).to be_successful\n      end\n\n      context 'when mounting class extends a subclass of Grape::API' do\n        it 'mounts APIs with the same superclass' do\n          base_api = Class.new(described_class)\n          a = Class.new(base_api)\n          b = Class.new(base_api)\n\n          expect { a.mount b }.not_to raise_error\n        end\n      end\n\n      context 'when including a module' do\n        let(:included_module) do\n          Module.new do\n            def self.included(base)\n              base.extend(ClassMethods)\n            end\n          end\n        end\n\n        before do\n          stub_const(\n            'ClassMethods',\n            Module.new do\n              def my_method\n                @test = true\n              end\n            end\n          )\n        end\n\n        it 'correctlies include module in nested mount' do\n          module_to_include = included_module\n          v1 = Class.new(described_class) do\n            version :v1, using: :path\n            include module_to_include\n\n            my_method\n          end\n          v2 = Class.new(described_class) do\n            version :v2, using: :path\n          end\n          segment_base = Class.new(described_class) do\n            mount v1\n            mount v2\n          end\n\n          Class.new(described_class) do\n            mount segment_base\n          end\n\n          expect(v1.my_method).to be_truthy\n        end\n      end\n    end\n  end\n\n  describe '.endpoints' do\n    it 'adds one for each route created' do\n      subject.get '/'\n      subject.post '/'\n      expect(subject.endpoints.size).to eq(2)\n    end\n  end\n\n  describe '.change!' do\n    it 'invalidates any compiled instance' do\n      expect(Grape::API::Instance.singleton_class::LOCK).to receive(:synchronize).and_call_original.twice\n      subject.compile!\n      subject.change!\n      subject.compile!\n    end\n  end\n\n  describe '.endpoint' do\n    before do\n      subject.format :json\n      subject.get '/endpoint/options' do\n        {\n          path: options[:path],\n          source_location: source.source_location\n        }\n      end\n    end\n\n    it 'path' do\n      get '/endpoint/options'\n      options = Grape::Json.load(last_response.body)\n      expect(options['path']).to eq(['/endpoint/options'])\n      expect(options['source_location'][0]).to include 'api_spec.rb'\n      expect(options['source_location'][1].to_i).to be > 0\n    end\n  end\n\n  describe '.route' do\n    context 'plain' do\n      before do\n        subject.get '/' do\n          route.path\n        end\n        subject.get '/path' do\n          route.path\n        end\n      end\n\n      it 'provides access to route info' do\n        get '/'\n        expect(last_response.body).to eq('/(.:format)')\n        get '/path'\n        expect(last_response.body).to eq('/path(.:format)')\n      end\n    end\n\n    context 'with desc' do\n      before do\n        subject.desc 'returns description'\n        subject.get '/description' do\n          route.description\n        end\n        subject.desc 'returns parameters', params: { 'x' => 'y' }\n        subject.get '/params/:id' do\n          route.params[params[:id]]\n        end\n      end\n\n      it 'returns route description' do\n        get '/description'\n        expect(last_response.body).to eq('returns description')\n      end\n\n      it 'returns route parameters' do\n        get '/params/x'\n        expect(last_response.body).to eq('y')\n      end\n    end\n  end\n\n  describe '.format' do\n    context ':txt' do\n      before do\n        subject.format :txt\n        subject.content_type :json, 'application/json'\n        subject.get '/meaning_of_life' do\n          { meaning_of_life: 42 }\n        end\n      end\n\n      it 'forces txt without an extension' do\n        get '/meaning_of_life'\n        expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)\n      end\n\n      it 'does not force txt with an extension' do\n        get '/meaning_of_life.json'\n        expect(last_response.body).to eq({ meaning_of_life: 42 }.to_json)\n      end\n\n      it 'forces txt from a non-accepting header' do\n        get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'application/json'\n        expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)\n      end\n    end\n\n    context ':txt only' do\n      before do\n        subject.format :txt\n        subject.get '/meaning_of_life' do\n          { meaning_of_life: 42 }\n        end\n      end\n\n      it 'forces txt without an extension' do\n        get '/meaning_of_life'\n        expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)\n      end\n\n      it 'accepts specified extension' do\n        get '/meaning_of_life.txt'\n        expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)\n      end\n\n      it 'does not accept extensions other than specified' do\n        get '/meaning_of_life.json'\n        expect(last_response).to be_not_found\n      end\n\n      it 'forces txt from a non-accepting header' do\n        get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'application/json'\n        expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)\n      end\n    end\n\n    context ':json' do\n      before do\n        subject.format :json\n        subject.content_type :txt, 'text/plain'\n        subject.get '/meaning_of_life' do\n          { meaning_of_life: 42 }\n        end\n      end\n\n      it 'forces json without an extension' do\n        get '/meaning_of_life'\n        expect(last_response.body).to eq({ meaning_of_life: 42 }.to_json)\n      end\n\n      it 'does not force json with an extension' do\n        get '/meaning_of_life.txt'\n        expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)\n      end\n\n      it 'forces json from a non-accepting header' do\n        get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'text/html'\n        expect(last_response.body).to eq({ meaning_of_life: 42 }.to_json)\n      end\n\n      it 'can be overwritten with an explicit api_format' do\n        subject.get '/meaning_of_life_with_content_type' do\n          api_format :txt\n          { meaning_of_life: 42 }.to_s\n        end\n        get '/meaning_of_life_with_content_type'\n        expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)\n      end\n\n      it 'raised :error from middleware' do\n        middleware = Class.new(Grape::Middleware::Base) do\n          def before\n            throw :error, message: 'Unauthorized', status: 500\n          end\n        end\n        subject.use middleware\n        subject.get do\n        end\n        get '/'\n        expect(last_response).to be_server_error\n        expect(last_response.body).to eq({ error: 'Unauthorized' }.to_json)\n      end\n    end\n\n    context ':serializable_hash' do\n      before do\n        stub_const(\n          'SerializableHashExample',\n          Class.new do\n            def serializable_hash\n              { abc: 'def' }\n            end\n          end\n        )\n\n        subject.format :serializable_hash\n      end\n\n      it 'instance' do\n        subject.get '/example' do\n          SerializableHashExample.new\n        end\n        get '/example'\n        expect(last_response.body).to eq('{\"abc\":\"def\"}')\n      end\n\n      it 'root' do\n        subject.get '/example' do\n          { 'root' => SerializableHashExample.new }\n        end\n        get '/example'\n        expect(last_response.body).to eq('{\"root\":{\"abc\":\"def\"}}')\n      end\n\n      it 'array' do\n        subject.get '/examples' do\n          [SerializableHashExample.new, SerializableHashExample.new]\n        end\n        get '/examples'\n        expect(last_response.body).to eq('[{\"abc\":\"def\"},{\"abc\":\"def\"}]')\n      end\n    end\n\n    context ':xml' do\n      before do\n        subject.format :xml\n      end\n\n      it 'string' do\n        subject.get '/example' do\n          'example'\n        end\n        get '/example'\n        expect(last_response).to be_server_error\n        expect(last_response.body).to eq <<~XML\n          <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n          <error>\n            <message>cannot convert String to xml</message>\n          </error>\n        XML\n      end\n\n      it 'hash' do\n        subject.get '/example' do\n          {\n            example1: 'example1',\n            example2: 'example2'\n          }\n        end\n        get '/example'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq <<~XML\n          <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n          <hash>\n            <example1>example1</example1>\n            <example2>example2</example2>\n          </hash>\n        XML\n      end\n\n      it 'array' do\n        subject.get '/example' do\n          %w[example1 example2]\n        end\n        get '/example'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq <<~XML\n          <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n          <strings type=\"array\">\n            <string>example1</string>\n            <string>example2</string>\n          </strings>\n        XML\n      end\n\n      it 'raised :error from middleware' do\n        middleware = Class.new(Grape::Middleware::Base) do\n          def before\n            throw :error, message: 'Unauthorized', status: 500\n          end\n        end\n        subject.use middleware\n        subject.get do\n        end\n        get '/'\n        expect(last_response.status).to eq(500)\n        expect(last_response.body).to eq <<~XML\n          <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n          <error>\n            <message>Unauthorized</message>\n          </error>\n        XML\n      end\n    end\n  end\n\n  describe '.configure' do\n    context 'when given a block' do\n      it 'returns self' do\n        expect(subject.configure {}).to be subject\n      end\n\n      it 'calls the block passing the config' do\n        call = [false, nil]\n        subject.configure do |config|\n          call = [true, config]\n        end\n\n        expect(call[0]).to be true\n        expect(call[1]).not_to be_nil\n      end\n    end\n\n    context 'when not given a block' do\n      it 'returns a configuration object' do\n        expect(subject.configure).to respond_to(:[], :[]=)\n      end\n    end\n\n    it 'allows configuring the api' do\n      subject.configure do |config|\n        config[:hello] = 'hello'\n        config[:bread] = 'bread'\n      end\n\n      subject.get '/hello-bread' do\n        \"#{configuration[:hello]} #{configuration[:bread]}\"\n      end\n\n      get '/hello-bread'\n      expect(last_response.body).to eq 'hello bread'\n    end\n  end\n\n  context 'catch-all' do\n    before do\n      api1 = Class.new(described_class)\n      api1.version 'v1', using: :path\n      api1.get 'hello' do\n        'v1'\n      end\n      api2 = Class.new(described_class)\n      api2.version 'v2', using: :path\n      api2.get 'hello' do\n        'v2'\n      end\n      subject.mount api1\n      subject.mount api2\n    end\n\n    [true, false].each do |anchor|\n      it \"anchor=#{anchor}\" do\n        subject.route :any, '*path', anchor: anchor do\n          error!(\"Unrecognized request path: #{params[:path]} - #{env[Rack::PATH_INFO]}#{env[Rack::SCRIPT_NAME]}\", 404)\n        end\n        get '/v1/hello'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('v1')\n        get '/v2/hello'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('v2')\n        options '/v2/hello'\n        expect(last_response).to be_no_content\n        expect(last_response.body).to be_blank\n        head '/v2/hello'\n        expect(last_response).to be_successful\n        expect(last_response.body).to be_blank\n        get '/foobar'\n        expect(last_response).to be_not_found\n        expect(last_response.body).to eq('Unrecognized request path: foobar - /foobar')\n      end\n    end\n  end\n\n  context 'cascading' do\n    context 'via version' do\n      it 'cascades' do\n        subject.version 'v1', using: :path, cascade: true\n        get '/v1/hello'\n        expect(last_response).to be_not_found\n        expect(last_response.headers['X-Cascade']).to eq('pass')\n      end\n\n      it 'does not cascade' do\n        subject.version 'v2', using: :path, cascade: false\n        get '/v2/hello'\n        expect(last_response).to be_not_found\n        expect(last_response.headers.keys).not_to include 'X-Cascade'\n      end\n    end\n\n    context 'via endpoint' do\n      it 'cascades' do\n        subject.cascade true\n        get '/hello'\n        expect(last_response).to be_not_found\n        expect(last_response.headers['X-Cascade']).to eq('pass')\n      end\n\n      it 'does not cascade' do\n        subject.cascade false\n        get '/hello'\n        expect(last_response).to be_not_found\n        expect(last_response.headers.keys).not_to include 'X-Cascade'\n      end\n    end\n  end\n\n  context 'with json default_error_formatter' do\n    it 'returns json error' do\n      subject.content_type :json, 'application/json'\n      subject.default_error_formatter :json\n      subject.get '/something' do\n        'foo'\n      end\n      get '/something'\n      expect(last_response.status).to eq(406)\n      expect(last_response.body).to eq(Rack::Utils.escape_html({ error: \"The requested format 'txt' is not supported.\" }.to_json))\n    end\n  end\n\n  context 'with unsafe HTML format specified' do\n    it 'escapes the HTML' do\n      subject.content_type :json, 'application/json'\n      subject.get '/something' do\n        'foo'\n      end\n      get '/something?format=<script>blah</script>'\n      expect(last_response.status).to eq(406)\n      expect(last_response.body).to eq(Rack::Utils.escape_html(\"The requested format '<script>blah</script>' is not supported.\"))\n    end\n  end\n\n  context 'with non-UTF-8 characters in specified format' do\n    it 'converts the characters' do\n      subject.format :json\n      subject.content_type :json, 'application/json'\n      subject.get '/something' do\n        'foo'\n      end\n      get '/something?format=%0A%0B%BF'\n      expect(last_response.status).to eq(406)\n      message = \"The requested format '\\n\\u000b\\357\\277\\275' is not supported.\"\n      expect(last_response.body).to eq({ error: message }.to_json)\n    end\n  end\n\n  context 'body' do\n    context 'false' do\n      before do\n        subject.get '/blank' do\n          body false\n        end\n      end\n\n      it 'returns blank body' do\n        get '/blank'\n        expect(last_response).to be_no_content\n        expect(last_response.body).to be_blank\n      end\n    end\n\n    context 'plain text' do\n      before do\n        subject.get '/text' do\n          content_type 'text/plain'\n          body 'Hello World'\n          'ignored'\n        end\n      end\n\n      it 'returns blank body' do\n        get '/text'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq 'Hello World'\n      end\n    end\n  end\n\n  describe 'normal class methods' do\n    subject(:grape_api) { Class.new(described_class) }\n\n    before do\n      stub_const('MyAPI', grape_api)\n    end\n\n    it 'can find the appropiate name' do\n      expect(grape_api.name).to eq 'MyAPI'\n    end\n\n    it 'is equal to itself' do\n      expect(grape_api.itself).to eq grape_api\n      expect(grape_api).to eq MyAPI\n      expect(grape_api.eql?(MyAPI))\n    end\n  end\n\n  describe '.inherited' do\n    context 'overriding within class' do\n      let(:root_api) do\n        Class.new(described_class) do\n          @bar = 'Hello, world'\n\n          def self.inherited(child_api)\n            super\n            child_api.instance_variable_set(:@foo, @bar.dup)\n          end\n        end\n      end\n\n      let(:child_api) { Class.new(root_api) }\n\n      it 'allows overriding the hook' do\n        expect(child_api.instance_variable_get(:@foo)).to eq('Hello, world')\n      end\n    end\n\n    it 'does not override methods inherited from Class' do\n      Class.define_method(:test_method) {}\n      subclass = Class.new(described_class)\n      expect(subclass).not_to receive(:add_setup)\n      subclass.test_method\n    ensure\n      Class.remove_method(:test_method)\n    end\n\n    context 'overriding via composition' do\n      let(:inherited) do\n        Module.new do\n          def inherited(api)\n            super\n            api.instance_variable_set(:@foo, @bar.dup)\n          end\n        end\n      end\n\n      let(:root_api) do\n        context = self\n\n        Class.new(described_class) do\n          @bar = 'Hello, world'\n          extend context.inherited\n        end\n      end\n\n      let(:child_api) { Class.new(root_api) }\n\n      it 'allows overriding the hook' do\n        expect(child_api.instance_variable_get(:@foo)).to eq('Hello, world')\n      end\n    end\n  end\n\n  describe 'const_missing' do\n    subject(:grape_api) { Class.new(described_class) }\n\n    let(:mounted) do\n      Class.new(described_class) do\n        get '/missing' do\n          SomeRandomConstant\n        end\n      end\n    end\n\n    before { subject.mount mounted => '/const' }\n\n    it 'raises an error' do\n      expect { get '/const/missing' }.to raise_error(NameError).with_message(/SomeRandomConstant/)\n    end\n  end\n\n  describe 'custom route helpers on nested APIs' do\n    subject(:grape_api) do\n      Class.new(described_class) do\n        version 'v1', using: :path\n      end\n    end\n\n    let(:shared_api_module) do\n      Module.new do\n        # rubocop:disable Style/ExplicitBlockArgument -- because\n        # this causes the underlying issue in this form\n        def uniqe_id_route\n          params do\n            use :unique_id\n          end\n          route_param(:id) do\n            yield\n          end\n        end\n        # rubocop:enable Style/ExplicitBlockArgument\n      end\n    end\n    let(:shared_api_definitions) do\n      Module.new do\n        extend ActiveSupport::Concern\n\n        included do\n          helpers do\n            params :unique_id do\n              requires :id, type: String,\n                            allow_blank: false,\n                            regexp: /\\d+-\\d+/\n            end\n          end\n        end\n      end\n    end\n    let(:orders_root) do\n      shared = shared_api_definitions\n      find = orders_find_endpoint\n      Class.new(described_class) do\n        include shared\n\n        namespace(:orders) do\n          mount find\n        end\n      end\n    end\n    let(:orders_find_endpoint) do\n      shared = shared_api_definitions\n      Class.new(described_class) do\n        include shared\n\n        uniqe_id_route do\n          desc 'Fetch a single order' do\n            detail 'While specifying the order id on the route'\n          end\n          get { params[:id] }\n        end\n      end\n    end\n\n    before do\n      Grape::API::Instance.extend(shared_api_module)\n      subject.mount orders_root\n    end\n\n    it 'returns an error when the id is bad' do\n      get '/v1/orders/abc'\n      expect(last_response.body).to eq('id is invalid')\n    end\n\n    it 'returns the given id when it is valid' do\n      get '/v1/orders/1-2'\n      expect(last_response.body).to eq('1-2')\n    end\n  end\n\n  context 'instance variables' do\n    context 'when setting instance variables in a before validation' do\n      it 'is accessible inside the endpoint' do\n        expected_instance_variable_value = 'wadus'\n\n        subject.before do\n          @my_var = expected_instance_variable_value\n        end\n\n        subject.get('/') do\n          { my_var: @my_var }.to_json\n        end\n\n        get '/'\n        expect(last_response.body).to eq({ my_var: expected_instance_variable_value }.to_json)\n      end\n    end\n\n    context 'when setting instance variables inside the endpoint code' do\n      it 'is accessible inside the rescue_from handler' do\n        expected_instance_variable_value = 'wadus'\n\n        subject.rescue_from(:all) do\n          body = { my_var: @my_var }\n          error!(body, 400)\n        end\n\n        subject.get('/') do\n          @my_var = expected_instance_variable_value\n          raise\n        end\n\n        get '/'\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq({ my_var: expected_instance_variable_value }.to_json)\n      end\n\n      it 'is NOT available in other endpoints of the same api' do\n        expected_instance_variable_value = 'wadus'\n\n        subject.get('/first') do\n          @my_var = expected_instance_variable_value\n          { my_var: @my_var }.to_json\n        end\n\n        subject.get('/second') do\n          { my_var: @my_var }.to_json\n        end\n\n        get '/first'\n        expect(last_response.body).to eq({ my_var: expected_instance_variable_value }.to_json)\n        get '/second'\n        expect(last_response.body).to eq({ my_var: nil }.to_json)\n      end\n    end\n\n    context 'when set type to a route_param' do\n      context 'and the param does not match' do\n        it 'returns a 404 response' do\n          subject.namespace :books do\n            route_param :id, type: Integer do\n              get do\n                params[:id]\n              end\n            end\n          end\n\n          get '/books/other'\n          expect(last_response).to be_not_found\n        end\n      end\n    end\n  end\n\n  context 'rescue_from context' do\n    subject { last_response }\n\n    let(:api) do\n      Class.new(described_class) do\n        rescue_from :all do\n          error!(context.env, 400)\n        end\n        get { raise ArgumentError, 'Oops!' }\n      end\n    end\n\n    let(:app) { api }\n\n    before { get '/' }\n\n    it { is_expected.to be_bad_request }\n  end\n\n  describe '.build_with' do\n    let(:app) do\n      Class.new(described_class) do\n        build_with :unknown\n        params do\n          requires :a_param, type: Integer\n        end\n        get\n      end\n    end\n\n    before do\n      get '/'\n    end\n\n    it 'raises an UnknownParamsBuilder error' do\n      expect(last_response).to be_server_error\n      expect(last_response.body).to eq('unknown params_builder: unknown')\n    end\n  end\n\n  describe '.lint!' do\n    let(:app) do\n      Class.new(described_class) do\n        lint!\n        get '/' do\n          status 42\n        end\n      end\n    end\n\n    around do |example|\n      @lint = Grape.config.lint\n      Grape.config.lint = false\n      example.run\n    ensure\n      Grape.config.lint = @lint\n    end\n\n    it 'raises a Rack::Lint error' do\n      # Status must be an Integer >= 100\n      expect { get '/' }.to raise_error(Rack::Lint::LintError)\n    end\n  end\n\n  describe '.cascade' do\n    subject { api.cascade }\n\n    let(:api) do\n      Class.new(Grape::API) do\n        cascade true\n      end\n    end\n\n    it { is_expected.to be(true) }\n  end\nend\n"
  },
  {
    "path": "spec/grape/content_types_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::ContentTypes do\n  describe 'DEFAULTS' do\n    subject { described_class::DEFAULTS }\n\n    let(:expected_value) do\n      {\n        xml: 'application/xml',\n        serializable_hash: 'application/json',\n        json: 'application/json',\n        binary: 'application/octet-stream',\n        txt: 'text/plain'\n      }.freeze\n    end\n\n    it { is_expected.to eq(expected_value) }\n  end\n\n  describe 'MIME_TYPES' do\n    subject { described_class::MIME_TYPES }\n\n    let(:expected_value) do\n      {\n        'application/xml' => :xml,\n        'application/json' => :json,\n        'application/octet-stream' => :binary,\n        'text/plain' => :txt\n      }.freeze\n    end\n\n    it { is_expected.to eq(expected_value) }\n  end\n\n  describe '.content_types_for' do\n    subject { described_class.content_types_for(from_settings) }\n\n    context 'when from_settings is present' do\n      let(:from_settings) { { a: :b } }\n\n      it { is_expected.to eq(from_settings) }\n    end\n\n    context 'when from_settings is not present' do\n      let(:from_settings) { nil }\n\n      it { is_expected.to be(described_class::DEFAULTS) }\n    end\n  end\n\n  describe '.mime_types_for' do\n    subject { described_class.mime_types_for(from_settings) }\n\n    context 'when from_settings is equal to Grape::ContentTypes::DEFAULTS' do\n      let(:from_settings) do\n        {\n          xml: 'application/xml',\n          serializable_hash: 'application/json',\n          json: 'application/json',\n          binary: 'application/octet-stream',\n          txt: 'text/plain'\n        }.freeze\n      end\n\n      it { is_expected.to be(described_class::MIME_TYPES) }\n    end\n\n    context 'when from_settings is not equal to Grape::ContentTypes::DEFAULTS' do\n      let(:from_settings) do\n        {\n          xml: 'application/xml;charset=utf-8'\n        }\n      end\n\n      it { is_expected.to eq('application/xml' => :xml) }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/dsl/callbacks_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::DSL::Callbacks do\n  subject { dummy_class }\n\n  let(:dummy_class) do\n    Class.new do\n      extend Grape::DSL::Settings\n      extend Grape::DSL::Callbacks\n    end\n  end\n\n  let(:proc) { -> {} }\n\n  describe '.before' do\n    it 'adds a block to \"before\"' do\n      subject.before(&proc)\n      expect(subject.inheritable_setting.namespace_stackable[:befores]).to eq([proc])\n    end\n  end\n\n  describe '.before_validation' do\n    it 'adds a block to \"before_validation\"' do\n      subject.before_validation(&proc)\n      expect(subject.inheritable_setting.namespace_stackable[:before_validations]).to eq([proc])\n    end\n  end\n\n  describe '.after_validation' do\n    it 'adds a block to \"after_validation\"' do\n      subject.after_validation(&proc)\n      expect(subject.inheritable_setting.namespace_stackable[:after_validations]).to eq([proc])\n    end\n  end\n\n  describe '.after' do\n    it 'adds a block to \"after\"' do\n      subject.after(&proc)\n      expect(subject.inheritable_setting.namespace_stackable[:afters]).to eq([proc])\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/dsl/desc_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::DSL::Desc do\n  subject { dummy_class }\n\n  let(:dummy_class) do\n    Class.new do\n      extend Grape::DSL::Desc\n      extend Grape::DSL::Settings\n    end\n  end\n\n  describe '.desc' do\n    it 'sets a description' do\n      desc_text = 'The description'\n      options = { message: 'none' }\n      subject.desc desc_text, options\n      expect(subject.namespace_setting(:description)).to eq(options.merge(description: desc_text))\n      expect(subject.route_setting(:description)).to eq(options.merge(description: desc_text))\n    end\n\n    it 'can be set with a block' do\n      expected_options = {\n        summary: 'summary',\n        description: 'The description',\n        detail: 'more details',\n        params: { first: :param },\n        entity: Object,\n        default: { code: 400, message: 'Invalid' },\n        http_codes: [[401, 'Unauthorized', 'Entities::Error']],\n        named: 'My named route',\n        body_name: 'My body name',\n        headers: [\n          XAuthToken: {\n            description: 'Valdates your identity',\n            required: true\n          },\n          XOptionalHeader: {\n            description: 'Not really needed',\n            required: false\n          }\n        ],\n        hidden: false,\n        deprecated: false,\n        is_array: true,\n        nickname: 'nickname',\n        produces: %w[array of mime_types],\n        consumes: %w[array of mime_types],\n        tags: %w[tag1 tag2],\n        security: %w[array of security schemes]\n      }\n\n      subject.desc 'The description' do\n        summary 'summary'\n        detail 'more details'\n        params(first: :param)\n        success Object\n        default code: 400, message: 'Invalid'\n        failure [[401, 'Unauthorized', 'Entities::Error']]\n        named 'My named route'\n        body_name 'My body name'\n        headers [\n          XAuthToken: {\n            description: 'Valdates your identity',\n            required: true\n          },\n          XOptionalHeader: {\n            description: 'Not really needed',\n            required: false\n          }\n        ]\n        hidden false\n        deprecated false\n        is_array true\n        nickname 'nickname'\n        produces %w[array of mime_types]\n        consumes %w[array of mime_types]\n        tags %w[tag1 tag2]\n        security %w[array of security schemes]\n      end\n\n      expect(subject.namespace_setting(:description)).to eq(expected_options)\n      expect(subject.route_setting(:description)).to eq(expected_options)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/dsl/headers_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::DSL::Headers do\n  subject { dummy_class.new }\n\n  let(:dummy_class) do\n    Class.new do\n      include Grape::DSL::Headers\n    end\n  end\n\n  let(:header_data) do\n    { 'first key' => 'First Value',\n      'second key' => 'Second Value' }\n  end\n\n  context 'when headers are set' do\n    describe '#header' do\n      before do\n        header_data.each { |k, v| subject.header(k, v) }\n      end\n\n      describe 'get' do\n        it 'returns a specifc value' do\n          expect(subject.header['first key']).to eq 'First Value'\n          expect(subject.header['second key']).to eq 'Second Value'\n        end\n\n        it 'returns all set headers' do\n          expect(subject.header).to eq header_data\n          expect(subject.headers).to eq header_data\n        end\n      end\n\n      describe 'set' do\n        it 'returns value' do\n          expect(subject.header('third key', 'Third Value'))\n          expect(subject.header['third key']).to eq 'Third Value'\n        end\n      end\n\n      describe 'delete' do\n        it 'deletes a header key-value pair' do\n          expect(subject.header('first key')).to eq header_data['first key']\n          expect(subject.header).not_to have_key('first key')\n        end\n      end\n    end\n  end\n\n  context 'when no headers are set' do\n    describe '#header' do\n      it 'returns nil' do\n        expect(subject.header['first key']).to be_nil\n        expect(subject.header('first key')).to be_nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/dsl/helpers_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::DSL::Helpers do\n  subject { dummy_class }\n\n  let(:dummy_class) do\n    Class.new do\n      extend Grape::DSL::Helpers\n      extend Grape::DSL::Settings\n\n      def self.mods\n        inheritable_setting.namespace_stackable[:helpers]\n      end\n\n      def self.first_mod\n        mods.first\n      end\n    end\n  end\n\n  let(:proc) do\n    lambda do |*|\n      def test\n        :test\n      end\n    end\n  end\n\n  describe '.helpers' do\n    it 'adds a module with the given block' do\n      subject.helpers(&proc)\n      expect(subject.first_mod.instance_methods).to include(:test)\n    end\n\n    it 'uses provided modules' do\n      mod = Module.new\n      subject.helpers(mod, &proc)\n      expect(subject.first_mod).to eq mod\n    end\n\n    it 'uses many provided modules' do\n      mod  = Module.new\n      mod2 = Module.new\n      mod3 = Module.new\n\n      subject.helpers(mod, mod2, mod3, &proc)\n      expect(subject.mods).to include(mod, mod2, mod3)\n    end\n\n    context 'with an external file' do\n      let(:boolean_helper) do\n        Module.new do\n          extend Grape::API::Helpers\n\n          params :requires_toggle_prm do\n            requires :toggle_prm, type: Boolean\n          end\n        end\n      end\n\n      it 'sets Boolean as a Grape::API::Boolean' do\n        subject.helpers boolean_helper\n        expect(subject.first_mod::Boolean).to eq Grape::API::Boolean\n      end\n    end\n\n    context 'in child classes' do\n      let(:base_class) do\n        Class.new(Grape::API) do\n          helpers do\n            params :requires_toggle_prm do\n              requires :toggle_prm, type: Integer\n            end\n          end\n        end\n      end\n\n      let(:api_class) do\n        Class.new(base_class) do\n          params do\n            use :requires_toggle_prm\n          end\n        end\n      end\n\n      it 'is available' do\n        expect { api_class }.not_to raise_exception\n      end\n    end\n\n    context 'public scope' do\n      it 'returns helpers only' do\n        expect(Class.new { extend Grape::DSL::Helpers }.singleton_methods - Class.methods).to contain_exactly(:helpers)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/dsl/inside_route_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::DSL::InsideRoute do\n  subject { dummy_class.new }\n\n  let(:dummy_class) do\n    Class.new do\n      include Grape::DSL::InsideRoute\n      include Grape::DSL::Settings\n\n      attr_reader :env, :request, :new_settings\n\n      def initialize\n        @env = {}\n        @header = {}\n        @new_settings = { namespace_inheritable: inheritable_setting.namespace_inheritable, namespace_stackable: inheritable_setting.namespace_stackable }\n      end\n\n      def header(key = nil, val = nil)\n        if key\n          val ? header[key] = val : header.delete(key)\n        else\n          @header ||= Grape::Util::Header.new\n        end\n      end\n    end\n  end\n\n  describe '#version' do\n    it 'defaults to nil' do\n      expect(subject.version).to be_nil\n    end\n\n    it 'returns env[api.version]' do\n      subject.env[Grape::Env::API_VERSION] = 'dummy'\n      expect(subject.version).to eq 'dummy'\n    end\n  end\n\n  describe '#error!' do\n    it 'throws :error' do\n      expect { subject.error! 'Not Found', 404 }.to throw_symbol(:error)\n    end\n\n    describe 'thrown' do\n      before do\n        catch(:error) { subject.error! 'Not Found', 404 }\n      end\n\n      it 'sets status' do\n        expect(subject.status).to eq 404\n      end\n    end\n\n    describe 'default_error_status' do\n      before do\n        subject.inheritable_setting.namespace_inheritable[:default_error_status] = 500\n        catch(:error) { subject.error! 'Unknown' }\n      end\n\n      it 'sets status to default_error_status' do\n        expect(subject.status).to eq 500\n      end\n    end\n\n    # self.status(status || settings[:default_error_status])\n    # throw :error, message: message, status: self.status, headers: headers\n  end\n\n  describe '#redirect' do\n    describe 'default' do\n      before do\n        subject.redirect '/'\n      end\n\n      it 'sets status to 302' do\n        expect(subject.status).to eq 302\n      end\n\n      it 'sets location header' do\n        expect(subject.header['Location']).to eq '/'\n      end\n    end\n\n    describe 'permanent' do\n      before do\n        subject.redirect '/', permanent: true\n      end\n\n      it 'sets status to 301' do\n        expect(subject.status).to eq 301\n      end\n\n      it 'sets location header' do\n        expect(subject.header['Location']).to eq '/'\n      end\n    end\n  end\n\n  describe '#status' do\n    %w[GET PUT OPTIONS].each do |method|\n      it 'defaults to 200 on GET' do\n        request = Grape::Request.new(Rack::MockRequest.env_for('/', method: method))\n        expect(subject).to receive(:request).and_return(request).twice\n        expect(subject.status).to eq 200\n      end\n    end\n\n    it 'defaults to 201 on POST' do\n      request = Grape::Request.new(Rack::MockRequest.env_for('/', method: Rack::POST))\n      expect(subject).to receive(:request).and_return(request)\n      expect(subject.status).to eq 201\n    end\n\n    it 'defaults to 204 on DELETE' do\n      request = Grape::Request.new(Rack::MockRequest.env_for('/', method: Rack::DELETE))\n      expect(subject).to receive(:request).and_return(request).twice\n      expect(subject.status).to eq 204\n    end\n\n    it 'defaults to 200 on DELETE with a body present' do\n      request = Grape::Request.new(Rack::MockRequest.env_for('/', method: Rack::DELETE))\n      subject.body 'content here'\n      expect(subject).to receive(:request).and_return(request).twice\n      expect(subject.status).to eq 200\n    end\n\n    it 'returns status set' do\n      subject.status 501\n      expect(subject.status).to eq 501\n    end\n\n    it 'accepts symbol for status' do\n      subject.status :see_other\n      expect(subject.status).to eq 303\n    end\n\n    it 'raises error if unknow symbol is passed' do\n      expect { subject.status :foo_bar }\n        .to raise_error(ArgumentError, 'Status code :foo_bar is invalid.')\n    end\n\n    it 'accepts unknown Integer status codes' do\n      expect { subject.status 210 }.not_to raise_error\n    end\n\n    it 'raises error if status is not a integer or symbol' do\n      expect { subject.status Object.new }\n        .to raise_error(ArgumentError, 'Status code must be Integer or Symbol.')\n    end\n  end\n\n  describe '#return_no_content' do\n    it 'sets the status code and body' do\n      subject.return_no_content\n      expect(subject.status).to eq 204\n      expect(subject.body).to eq ''\n    end\n  end\n\n  describe '#content_type' do\n    describe 'set' do\n      before do\n        subject.content_type 'text/plain'\n      end\n\n      it 'returns value' do\n        expect(subject.content_type).to eq 'text/plain'\n      end\n    end\n\n    it 'returns default' do\n      expect(subject.content_type).to be_nil\n    end\n  end\n\n  describe '#body' do\n    describe 'set' do\n      before do\n        subject.body 'body'\n      end\n\n      it 'returns value' do\n        expect(subject.body).to eq 'body'\n      end\n    end\n\n    describe 'false' do\n      before do\n        subject.body false\n      end\n\n      it 'sets status to 204' do\n        expect(subject.body).to eq ''\n        expect(subject.status).to eq 204\n      end\n    end\n\n    it 'returns default' do\n      expect(subject.body).to be_nil\n    end\n  end\n\n  describe '#sendfile' do\n    describe 'set' do\n      context 'as file path' do\n        let(:file_path) { '/some/file/path' }\n\n        let(:file_response) do\n          file_body = Grape::ServeStream::FileBody.new(file_path)\n          Grape::ServeStream::StreamResponse.new(file_body)\n        end\n\n        before do\n          subject.header Rack::CACHE_CONTROL, 'cache'\n          subject.header Rack::CONTENT_LENGTH, 123\n          subject.header 'Transfer-Encoding', 'base64'\n          subject.sendfile file_path\n        end\n\n        it 'returns value wrapped in StreamResponse' do\n          expect(subject.sendfile).to eq file_response\n        end\n\n        it 'set the correct headers' do\n          expect(subject.header).to match(\n            Rack::CACHE_CONTROL => 'cache',\n            Rack::CONTENT_LENGTH => 123,\n            'Transfer-Encoding' => 'base64'\n          )\n        end\n      end\n\n      context 'as object' do\n        let(:file_object) { double('StreamerObject', each: nil) }\n\n        it 'raises an error that only a file path is supported' do\n          expect { subject.sendfile file_object }.to raise_error(ArgumentError, /Argument must be a file path/)\n        end\n      end\n    end\n\n    it 'returns default' do\n      expect(subject.sendfile).to be_nil\n    end\n  end\n\n  describe '#stream' do\n    describe 'set' do\n      context 'as a file path' do\n        let(:file_path) { '/some/file/path' }\n\n        let(:file_response) do\n          file_body = Grape::ServeStream::FileBody.new(file_path)\n          Grape::ServeStream::StreamResponse.new(file_body)\n        end\n\n        before do\n          subject.header Rack::CACHE_CONTROL, 'cache'\n          subject.header Rack::CONTENT_LENGTH, 123\n          subject.header 'Transfer-Encoding', 'base64'\n          subject.stream file_path\n        end\n\n        it 'returns file body wrapped in StreamResponse' do\n          expect(subject.stream).to eq file_response\n        end\n\n        it 'sets only the cache-control header' do\n          expect(subject.header).to match(Rack::CACHE_CONTROL => 'no-cache')\n        end\n      end\n\n      context 'as a stream object' do\n        let(:stream_object) { double('StreamerObject', each: nil) }\n\n        let(:stream_response) do\n          Grape::ServeStream::StreamResponse.new(stream_object)\n        end\n\n        before do\n          subject.header Rack::CACHE_CONTROL, 'cache'\n          subject.header Rack::CONTENT_LENGTH, 123\n          subject.header 'Transfer-Encoding', 'base64'\n          subject.stream stream_object\n        end\n\n        it 'returns value wrapped in StreamResponse' do\n          expect(subject.stream).to eq stream_response\n        end\n\n        it 'set only the cache-control header' do\n          expect(subject.header).to match(Rack::CACHE_CONTROL => 'no-cache')\n        end\n      end\n\n      context 'as a non-stream object' do\n        let(:non_stream_object) { double('NonStreamerObject') }\n\n        it 'raises an error that the object must implement :each' do\n          expect { subject.stream non_stream_object }.to raise_error(ArgumentError, /:each/)\n        end\n      end\n    end\n\n    it 'returns default' do\n      expect(subject.stream).to be_nil\n      expect(subject.header).to be_empty\n    end\n  end\n\n  describe '#route' do\n    before do\n      subject.env[Grape::Env::GRAPE_ROUTING_ARGS] = {}\n      subject.env[Grape::Env::GRAPE_ROUTING_ARGS][:route_info] = 'dummy'\n    end\n\n    it 'returns route_info' do\n      expect(subject.route).to eq 'dummy'\n    end\n  end\n\n  describe '#present' do\n    # see entity_spec.rb for entity representation spec coverage\n\n    describe 'dummy' do\n      before do\n        subject.present 'dummy'\n      end\n\n      it 'presents dummy object' do\n        expect(subject.body).to eq 'dummy'\n      end\n    end\n\n    describe 'with' do\n      describe 'entity' do\n        let(:entity_mock) do\n          entity_mock = Object.new\n          allow(entity_mock).to receive(:represent).and_return('dummy')\n          entity_mock\n        end\n\n        describe 'instance' do\n          before do\n            subject.present 'dummy', with: entity_mock\n          end\n\n          it 'presents dummy object' do\n            expect(subject.body).to eq 'dummy'\n          end\n        end\n      end\n    end\n\n    describe 'multiple entities' do\n      let(:entity_mock_one) do\n        entity_mock_one = Object.new\n        allow(entity_mock_one).to receive(:represent).and_return(dummy1: 'dummy1')\n        entity_mock_one\n      end\n\n      let(:entity_mock_two) do\n        entity_mock_two = Object.new\n        allow(entity_mock_two).to receive(:represent).and_return(dummy2: 'dummy2')\n        entity_mock_two\n      end\n\n      describe 'instance' do\n        before do\n          subject.present 'dummy1', with: entity_mock_one\n          subject.present 'dummy2', with: entity_mock_two\n        end\n\n        it 'presents both dummy objects' do\n          expect(subject.body[:dummy1]).to eq 'dummy1'\n          expect(subject.body[:dummy2]).to eq 'dummy2'\n        end\n      end\n    end\n\n    describe 'non mergeable entity' do\n      let(:entity_mock_one) do\n        entity_mock_one = Object.new\n        allow(entity_mock_one).to receive(:represent).and_return(dummy1: 'dummy1')\n        entity_mock_one\n      end\n\n      let(:entity_mock_two) do\n        entity_mock_two = Object.new\n        allow(entity_mock_two).to receive(:represent).and_return('not a hash')\n        entity_mock_two\n      end\n\n      describe 'instance' do\n        it 'fails' do\n          subject.present 'dummy1', with: entity_mock_one\n          expect do\n            subject.present 'dummy2', with: entity_mock_two\n          end.to raise_error ArgumentError, 'Representation of type String cannot be merged.'\n        end\n      end\n    end\n  end\n\n  describe '#declared' do\n    let(:dummy_class) do\n      Class.new do\n        include Grape::DSL::Declared\n\n        attr_reader :before_filter_passed\n\n        def initialize\n          @before_filter_passed = false\n        end\n      end\n    end\n\n    it 'is not available by default' do\n      expect { subject.declared({}) }.to raise_error(\n        Grape::DSL::InsideRoute::MethodNotYetAvailable\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/dsl/logger_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::DSL::Logger do\n  let(:dummy_logger) do\n    Class.new do\n      extend Grape::DSL::Logger\n      extend Grape::DSL::Settings\n    end\n  end\n\n  describe '.logger' do\n    context 'when setting a logger' do\n      subject { dummy_logger.logger :my_logger }\n\n      it { is_expected.to eq(:my_logger) }\n    end\n\n    context 'when retrieving logger' do\n      context 'when never been set' do\n        subject { dummy_logger.logger }\n\n        before { allow(Logger).to receive(:new).with($stdout).and_return(:stdout_logger) }\n\n        it { is_expected.to eq(:stdout_logger) }\n      end\n\n      context 'when already set' do\n        subject { dummy_logger.logger }\n\n        before { dummy_logger.logger :my_logger }\n\n        it { is_expected.to eq(:my_logger) }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/dsl/middleware_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::DSL::Middleware do\n  subject { dummy_class }\n\n  let(:dummy_class) do\n    Class.new do\n      extend Grape::DSL::Middleware\n      extend Grape::DSL::Settings\n    end\n  end\n\n  let(:proc) { -> {} }\n  let(:foo_middleware) { Class.new }\n  let(:bar_middleware) { Class.new }\n\n  describe '.use' do\n    it 'adds a middleware with the right operation' do\n      subject.use foo_middleware, :arg1, &proc\n      expect(subject.inheritable_setting.namespace_stackable[:middleware]).to eq([[:use, foo_middleware, :arg1, proc]])\n    end\n  end\n\n  describe '.insert' do\n    it 'adds a middleware with the right operation' do\n      subject.insert 0, :arg1, &proc\n      expect(subject.inheritable_setting.namespace_stackable[:middleware]).to eq([[:insert, 0, :arg1, proc]])\n    end\n  end\n\n  describe '.insert_before' do\n    it 'adds a middleware with the right operation' do\n      subject.insert_before foo_middleware, :arg1, &proc\n      expect(subject.inheritable_setting.namespace_stackable[:middleware]).to eq([[:insert_before, foo_middleware, :arg1, proc]])\n    end\n  end\n\n  describe '.insert_after' do\n    it 'adds a middleware with the right operation' do\n      subject.insert_after foo_middleware, :arg1, &proc\n      expect(subject.inheritable_setting.namespace_stackable[:middleware]).to eq([[:insert_after, foo_middleware, :arg1, proc]])\n    end\n  end\n\n  describe '.middleware' do\n    it 'returns the middleware stack' do\n      subject.use foo_middleware, :arg1, &proc\n      subject.insert_before bar_middleware, :arg1, :arg2\n\n      expect(subject.middleware).to eq [[:use, foo_middleware, :arg1, proc], [:insert_before, bar_middleware, :arg1, :arg2]]\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/dsl/parameters_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::DSL::Parameters do\n  subject { dummy_class.new }\n\n  let(:dummy_class) do\n    Class.new do\n      include Grape::DSL::Parameters\n\n      attr_accessor :api, :element, :parent\n\n      def initialize\n        @validate_attributes = []\n      end\n\n      def validate_attributes(*args)\n        @validate_attributes.push(*args)\n      end\n\n      def validate_attributes_reader\n        @validate_attributes\n      end\n\n      def push_declared_params(args, _opts)\n        @push_declared_params = args\n      end\n\n      def push_declared_params_reader\n        @push_declared_params\n      end\n\n      def validates(*args)\n        @validates = *args\n      end\n\n      def validates_reader\n        @validates\n      end\n\n      def new_scope(element, **_opts, &block)\n        nested_scope = self.class.new\n        nested_scope.new_group_scope(element, &block)\n        nested_scope\n      end\n\n      def new_group_scope(group)\n        prev_group = @group\n        @group = group\n        yield\n        @group = prev_group\n      end\n\n      def extract_message_option(attrs)\n        return nil unless attrs.is_a?(Array)\n\n        opts = attrs.last.is_a?(Hash) ? attrs.pop : {}\n        opts.key?(:message) && !opts[:message].nil? ? opts.delete(:message) : nil\n      end\n    end\n  end\n\n  describe '#use' do\n    before do\n      allow_message_expectations_on_nil\n      allow(subject.api).to receive(:namespace_stackable).with(:named_params)\n    end\n\n    let(:options) { { option: 'value' } }\n    let(:named_params) { { params_group: proc {} } }\n\n    it 'calls processes associated with named params' do\n      subject.api = Class.new { include Grape::DSL::Settings }.new\n      subject.api.inheritable_setting.namespace_stackable[:named_params] = named_params\n      expect(subject).to receive(:instance_exec).with(options).and_yield\n      subject.use :params_group, **options\n    end\n\n    it 'raises error when non-existent named param is called' do\n      subject.api = Class.new { include Grape::DSL::Settings }.new\n      expect { subject.use :params_group }.to raise_error('Params :params_group not found!')\n    end\n  end\n\n  describe '#use_scope' do\n    it 'is alias to #use' do\n      expect(subject.method(:use_scope)).to eq subject.method(:use)\n    end\n  end\n\n  describe '#includes' do\n    it 'is alias to #use' do\n      expect(subject.method(:includes)).to eq subject.method(:use)\n    end\n  end\n\n  describe '#requires' do\n    it 'adds a required parameter' do\n      subject.requires :id, type: Integer, desc: 'Identity.'\n\n      expect(subject.validate_attributes_reader).to eq([[:id], { type: Integer, desc: 'Identity.', presence: { value: true, message: nil } }])\n      expect(subject.push_declared_params_reader).to eq([:id])\n    end\n  end\n\n  describe '#optional' do\n    it 'adds an optional parameter' do\n      subject.optional :id, type: Integer, desc: 'Identity.'\n\n      expect(subject.validate_attributes_reader).to eq([[:id], { type: Integer, desc: 'Identity.' }])\n      expect(subject.push_declared_params_reader).to eq([:id])\n    end\n  end\n\n  describe '#with' do\n    it 'creates a scope with group attributes' do\n      subject.with(type: Integer) { subject.optional :id, desc: 'Identity.' }\n\n      expect(subject.validate_attributes_reader).to eq([[:id], { type: Integer, desc: 'Identity.' }])\n      expect(subject.push_declared_params_reader).to eq([:id])\n    end\n\n    it 'merges the group attributes' do\n      subject.with(documentation: { in: 'body' }) { subject.optional :vault, documentation: { default: 33 } }\n\n      expect(subject.validate_attributes_reader).to eq([[:vault], { documentation: { in: 'body', default: 33 } }])\n      expect(subject.push_declared_params_reader).to eq([:vault])\n    end\n\n    it 'overrides the group attribute when values not mergable' do\n      subject.with(type: Integer, documentation: { in: 'body', default: 33 }) do\n        subject.optional :vault\n        subject.optional :allowed_vaults, type: [Integer], documentation: { default: [31, 32, 33], is_array: true }\n      end\n\n      expect(subject.validate_attributes_reader).to eq(\n        [\n          [:vault], { type: Integer, documentation: { in: 'body', default: 33 } },\n          [:allowed_vaults], { type: [Integer], documentation: { in: 'body', default: [31, 32, 33], is_array: true } }\n        ]\n      )\n    end\n\n    it 'allows a primitive type attribite to overwrite a complex type group attribute' do\n      subject.with(documentation: { x: { nullable: true } }) do\n        subject.optional :vault, type: Integer, documentation: { x: nil }\n      end\n\n      expect(subject.validate_attributes_reader).to eq(\n        [\n          [:vault], { type: Integer, documentation: { x: nil } }\n        ]\n      )\n    end\n\n    it 'does not nest primitives inside existing complex types erroneously' do\n      subject.with(type: Hash, documentation: { default: { vault: '33' } }) do\n        subject.optional :info\n        subject.optional :role, type: String, documentation: { default: 'resident' }\n      end\n\n      expect(subject.validate_attributes_reader).to eq(\n        [\n          [:info], { type: Hash, documentation: { default: { vault: '33' } } },\n          [:role], { type: String, documentation: { default: 'resident' } }\n        ]\n      )\n    end\n\n    it 'merges deeply nested attributes' do\n      subject.with(documentation: { details: { in: 'body', hidden: false } }) do\n        subject.optional :vault, documentation: { details: { desc: 'The vault number' } }\n      end\n\n      expect(subject.validate_attributes_reader).to eq(\n        [\n          [:vault], { documentation: { details: { in: 'body', hidden: false, desc: 'The vault number' } } }\n        ]\n      )\n    end\n\n    it \"supports nested 'with' calls\" do\n      subject.with(type: Integer, documentation: { in: 'body' }) do\n        subject.optional :pipboy_id\n        subject.with(documentation: { default: 33 }) do\n          subject.optional :vault\n          subject.with(type: String) do\n            subject.with(documentation: { default: 'resident' }) do\n              subject.optional :role\n            end\n          end\n          subject.optional :age, documentation: { default: 42 }\n        end\n      end\n\n      expect(subject.validate_attributes_reader).to eq(\n        [\n          [:pipboy_id], { type: Integer, documentation: { in: 'body' } },\n          [:vault], { type: Integer, documentation: { in: 'body', default: 33 } },\n          [:role], { type: String, documentation: { in: 'body', default: 'resident' } },\n          [:age], { type: Integer, documentation: { in: 'body', default: 42 } }\n        ]\n      )\n    end\n\n    it \"supports Hash parameter inside the 'with' calls\" do\n      subject.with(documentation: { in: 'body' }) do\n        subject.optional :info, type: Hash, documentation: { x: { nullable: true }, desc: 'The info' } do\n          subject.optional :vault, type: Integer, documentation: { default: 33, desc: 'The vault number' }\n        end\n      end\n\n      expect(subject.validate_attributes_reader).to eq(\n        [\n          [:info], { type: Hash, documentation: { in: 'body', desc: 'The info', x: { nullable: true } } },\n          [:vault], { type: Integer, documentation: { in: 'body', default: 33, desc: 'The vault number' } }\n        ]\n      )\n    end\n  end\n\n  describe '#mutually_exclusive' do\n    it 'adds an mutally exclusive parameter validation' do\n      subject.mutually_exclusive :media, :audio\n\n      expect(subject.validates_reader).to eq([%i[media audio], { mutually_exclusive: { value: true, message: nil } }])\n    end\n  end\n\n  describe '#exactly_one_of' do\n    it 'adds an exactly of one parameter validation' do\n      subject.exactly_one_of :media, :audio\n\n      expect(subject.validates_reader).to eq([%i[media audio], { exactly_one_of: { value: true, message: nil } }])\n    end\n  end\n\n  describe '#at_least_one_of' do\n    it 'adds an at least one of parameter validation' do\n      subject.at_least_one_of :media, :audio\n\n      expect(subject.validates_reader).to eq([%i[media audio], { at_least_one_of: { value: true, message: nil } }])\n    end\n  end\n\n  describe '#all_or_none_of' do\n    it 'adds an all or none of parameter validation' do\n      subject.all_or_none_of :media, :audio\n\n      expect(subject.validates_reader).to eq([%i[media audio], { all_or_none_of: { value: true, message: nil } }])\n    end\n  end\n\n  describe '#group' do\n    it 'is alias to #requires' do\n      expect(subject.method(:group)).to eq subject.method(:requires)\n    end\n  end\n\n  describe '#params' do\n    it 'inherits params from parent' do\n      parent_params = { foo: 'bar' }\n      subject.parent = Object.new\n      allow(subject.parent).to receive_messages(params: parent_params, qualifying_params: nil)\n      expect(subject.params({})).to eq parent_params\n    end\n\n    describe 'when params argument is an array of hashes' do\n      it 'returns values of each hash for @element key' do\n        subject.element = :foo\n        expect(subject.params([{ foo: 'bar' }, { foo: 'baz' }])).to eq(%w[bar baz])\n      end\n    end\n\n    describe 'when params argument is a hash' do\n      it 'returns value for @element key' do\n        subject.element = :foo\n        expect(subject.params(foo: 'bar')).to eq('bar')\n      end\n    end\n\n    describe 'when params argument is not a array or a hash' do\n      it 'returns empty hash' do\n        subject.element = Object.new\n        expect(subject.params(Object.new)).to eq({})\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/dsl/request_response_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::DSL::RequestResponse do\n  subject { dummy_class }\n\n  let(:dummy_class) do\n    Class.new do\n      extend Grape::DSL::RequestResponse\n      extend Grape::DSL::Settings\n    end\n  end\n\n  let(:c_type) { 'application/json' }\n  let(:format) { 'txt' }\n\n  describe '.default_format' do\n    it 'sets the default format' do\n      subject.default_format :format\n      expect(subject.inheritable_setting.namespace_inheritable[:default_format]).to eq(:format)\n    end\n\n    it 'returns the format without paramter' do\n      subject.default_format :format\n      expect(subject.default_format).to eq :format\n    end\n  end\n\n  describe '.format' do\n    it 'sets a new format' do\n      subject.format format\n      expect(subject.inheritable_setting.namespace_inheritable[:format]).to eq(format.to_sym)\n      expect(subject.inheritable_setting.namespace_inheritable[:default_error_formatter]).to eq(Grape::ErrorFormatter::Txt)\n    end\n  end\n\n  describe '.formatter' do\n    it 'sets the formatter for a content type' do\n      subject.formatter c_type, :formatter\n      expect(subject.inheritable_setting.namespace_stackable[:formatters]).to eq([c_type.to_sym => :formatter])\n    end\n  end\n\n  describe '.parser' do\n    it 'sets a parser for a content type' do\n      subject.parser c_type, :parser\n      expect(subject.inheritable_setting.namespace_stackable[:parsers]).to eq([c_type.to_sym => :parser])\n    end\n  end\n\n  describe '.default_error_formatter' do\n    it 'sets a new error formatter' do\n      subject.default_error_formatter :json\n      expect(subject.inheritable_setting.namespace_inheritable[:default_error_formatter]).to eq(Grape::ErrorFormatter::Json)\n    end\n  end\n\n  describe '.error_formatter' do\n    it 'sets a error_formatter' do\n      format = 'txt'\n      subject.error_formatter format, :error_formatter\n      expect(subject.inheritable_setting.namespace_stackable[:error_formatters]).to eq([{ format.to_sym => :error_formatter }])\n    end\n\n    it 'understands syntactic sugar' do\n      subject.error_formatter format, with: :error_formatter\n      expect(subject.inheritable_setting.namespace_stackable[:error_formatters]).to eq([{ format.to_sym => :error_formatter }])\n    end\n  end\n\n  describe '.content_type' do\n    it 'sets a content type for a format' do\n      subject.content_type format, c_type\n      expect(subject.inheritable_setting.namespace_stackable[:content_types]).to eq([format.to_sym => c_type])\n    end\n  end\n\n  describe '.content_types' do\n    it 'returns all content types' do\n      expect(subject.content_types).to eq(xml: 'application/xml',\n                                          serializable_hash: 'application/json',\n                                          json: 'application/json',\n                                          txt: 'text/plain',\n                                          binary: 'application/octet-stream')\n    end\n  end\n\n  describe '.default_error_status' do\n    it 'sets a default error status' do\n      subject.default_error_status 500\n      expect(subject.inheritable_setting.namespace_inheritable[:default_error_status]).to eq(500)\n    end\n  end\n\n  describe '.rescue_from' do\n    describe ':all' do\n      it 'sets rescue all to true' do\n        subject.rescue_from :all\n        expect(subject.inheritable_setting.namespace_inheritable.to_hash).to eq(\n          {\n            rescue_all: true,\n            all_rescue_handler: nil\n          }\n        )\n      end\n\n      it 'sets given proc as rescue handler' do\n        rescue_handler_proc = proc {}\n        subject.rescue_from :all, rescue_handler_proc\n        expect(subject.inheritable_setting.namespace_inheritable.to_hash).to eq(\n          {\n            rescue_all: true,\n            all_rescue_handler: rescue_handler_proc\n          }\n        )\n      end\n\n      it 'sets given block as rescue handler' do\n        rescue_handler_proc = proc {}\n        subject.rescue_from :all, &rescue_handler_proc\n        expect(subject.inheritable_setting.namespace_inheritable.to_hash).to eq(\n          {\n            rescue_all: true,\n            all_rescue_handler: rescue_handler_proc\n          }\n        )\n      end\n\n      it 'sets a rescue handler declared through :with option' do\n        with_block = -> { 'hello' }\n        subject.rescue_from :all, with: with_block\n        expect(subject.inheritable_setting.namespace_inheritable.to_hash).to eq(\n          {\n            rescue_all: true,\n            all_rescue_handler: with_block\n          }\n        )\n      end\n\n      it 'abort if :with option value is not Symbol, String or Proc' do\n        expect { subject.rescue_from :all, with: 1234 }.to raise_error(ArgumentError, \"with: #{integer_class_name}, expected Symbol, String or Proc\")\n      end\n\n      it 'abort if both :with option and block are passed' do\n        expect do\n          subject.rescue_from :all, with: -> { 'hello' } do\n            error!('bye')\n          end\n        end.to raise_error(ArgumentError, 'both :with option and block cannot be passed')\n      end\n    end\n\n    describe ':grape_exceptions' do\n      it 'sets rescue all to true' do\n        subject.rescue_from :grape_exceptions\n        expect(subject.inheritable_setting.namespace_inheritable.to_hash).to eq(\n          {\n            rescue_all: true,\n            rescue_grape_exceptions: true,\n            grape_exceptions_rescue_handler: nil\n          }\n        )\n      end\n\n      it 'sets given proc as rescue handler' do\n        rescue_handler_proc = proc {}\n        subject.rescue_from :grape_exceptions, rescue_handler_proc\n        expect(subject.inheritable_setting.namespace_inheritable.to_hash).to eq(\n          {\n            rescue_all: true,\n            rescue_grape_exceptions: true,\n            grape_exceptions_rescue_handler: rescue_handler_proc\n          }\n        )\n      end\n\n      it 'sets given block as rescue handler' do\n        rescue_handler_proc = proc {}\n        subject.rescue_from :grape_exceptions, &rescue_handler_proc\n        expect(subject.inheritable_setting.namespace_inheritable.to_hash).to eq(\n          {\n            rescue_all: true,\n            rescue_grape_exceptions: true,\n            grape_exceptions_rescue_handler: rescue_handler_proc\n          }\n        )\n      end\n\n      it 'sets a rescue handler declared through :with option' do\n        with_block = -> { 'hello' }\n        subject.rescue_from :grape_exceptions, with: with_block\n        expect(subject.inheritable_setting.namespace_inheritable.to_hash).to eq(\n          {\n            rescue_all: true,\n            rescue_grape_exceptions: true,\n            grape_exceptions_rescue_handler: with_block\n          }\n        )\n      end\n    end\n\n    describe 'list of exceptions is passed' do\n      it 'sets hash of exceptions as rescue handlers' do\n        subject.rescue_from StandardError\n        expect(subject.inheritable_setting.namespace_reverse_stackable[:rescue_handlers]).to eq([StandardError => nil])\n        expect(subject.inheritable_setting.namespace_stackable[:rescue_options]).to eq([{}])\n      end\n\n      it 'rescues only base handlers if rescue_subclasses: false option is passed' do\n        subject.rescue_from StandardError, rescue_subclasses: false\n        expect(subject.inheritable_setting.namespace_reverse_stackable[:base_only_rescue_handlers]).to eq([StandardError => nil])\n        expect(subject.inheritable_setting.namespace_stackable[:rescue_options]).to eq([rescue_subclasses: false])\n      end\n\n      it 'sets given proc as rescue handler for each key in hash' do\n        rescue_handler_proc = proc {}\n        subject.rescue_from StandardError, rescue_handler_proc\n        expect(subject.inheritable_setting.namespace_reverse_stackable[:rescue_handlers]).to eq([StandardError => rescue_handler_proc])\n        expect(subject.inheritable_setting.namespace_stackable[:rescue_options]).to eq([{}])\n      end\n\n      it 'sets given block as rescue handler for each key in hash' do\n        rescue_handler_proc = proc {}\n        subject.rescue_from StandardError, &rescue_handler_proc\n        expect(subject.inheritable_setting.namespace_reverse_stackable[:rescue_handlers]).to eq([StandardError => rescue_handler_proc])\n        expect(subject.inheritable_setting.namespace_stackable[:rescue_options]).to eq([{}])\n      end\n\n      it 'sets a rescue handler declared through :with option for each key in hash' do\n        with_block = -> { 'hello' }\n        subject.rescue_from StandardError, with: with_block\n        expect(subject.inheritable_setting.namespace_reverse_stackable[:rescue_handlers]).to eq([StandardError => with_block])\n        expect(subject.inheritable_setting.namespace_stackable[:rescue_options]).to eq([{}])\n      end\n    end\n  end\n\n  describe '.represent' do\n    it 'sets a presenter for a class' do\n      presenter = Class.new\n      subject.represent :ThisClass, with: presenter\n      expect(subject.inheritable_setting.namespace_stackable[:representations]).to eq([ThisClass: presenter])\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/dsl/routing_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::DSL::Routing do\n  subject { dummy_class }\n\n  let(:dummy_class) do\n    Class.new do\n      extend Grape::DSL::Routing\n      extend Grape::DSL::Settings\n      extend Grape::DSL::Validations\n\n      class << self\n        attr_reader :instance, :base\n        attr_accessor :configuration\n      end\n    end\n  end\n\n  let(:proc) { -> {} }\n  let(:options) { { a: :b } }\n  let(:path) { '/dummy' }\n\n  describe '.version' do\n    it 'sets a version for route' do\n      version = 'v1'\n      expect(subject.version(version)).to eq(version)\n      expect(subject.inheritable_setting.namespace_inheritable[:version]).to eq([version])\n      expect(subject.inheritable_setting.namespace_inheritable[:version_options]).to eq(using: :path)\n    end\n  end\n\n  describe '.prefix' do\n    it 'sets a prefix for route' do\n      prefix = '/api'\n      subject.prefix prefix\n      expect(subject.inheritable_setting.namespace_inheritable[:root_prefix]).to eq(prefix)\n    end\n  end\n\n  describe '.scope' do\n    let(:root_app) do\n      Class.new(Grape::API) do\n        scope :my_scope do\n          get :my_endpoint do\n            return_no_content\n          end\n        end\n      end\n    end\n\n    it 'create a scope without affecting the URL' do\n      env = Rack::MockRequest.env_for('/my_endpoint', method: Rack::GET)\n      response = Rack::MockResponse[*root_app.call(env)]\n      expect(response).to be_no_content\n    end\n  end\n\n  describe '.do_not_route_head!' do\n    it 'sets do not route head option' do\n      subject.do_not_route_head!\n      expect(subject.inheritable_setting.namespace_inheritable[:do_not_route_head]).to be(true)\n    end\n  end\n\n  describe '.do_not_route_options!' do\n    it 'sets do not route options option' do\n      subject.do_not_route_options!\n      expect(subject.inheritable_setting.namespace_inheritable[:do_not_route_options]).to be(true)\n    end\n  end\n\n  describe '.mount' do\n    it 'mounts on a nested path' do\n      subject = Class.new(Grape::API)\n      app1 = Class.new(Grape::API) do\n        get '/' do\n          return_no_content\n        end\n      end\n      app2 = Class.new(Grape::API) do\n        get '/' do\n          return_no_content\n        end\n      end\n      subject.mount app1 => '/app1'\n      app1.mount app2 => '/app2'\n\n      env = Rack::MockRequest.env_for('/app1', method: Rack::GET)\n      response = Rack::MockResponse[*subject.call(env)]\n\n      expect(response).to be_no_content\n\n      env = Rack::MockRequest.env_for('/app1/app2', method: Rack::GET)\n      response = Rack::MockResponse[*subject.call(env)]\n\n      expect(response).to be_no_content\n    end\n\n    it 'mounts multiple routes at once' do\n      base_app = Class.new(Grape::API)\n      app1 = Class.new(Grape::API) do\n        get '/' do\n          return_no_content\n        end\n      end\n      app2 = Class.new(Grape::API) do\n        get '/' do\n          return_no_content\n        end\n      end\n      base_app.mount(app1 => '/app1', app2 => '/app2')\n\n      env = Rack::MockRequest.env_for('/app1', method: Rack::GET)\n      response = Rack::MockResponse[*base_app.call(env)]\n\n      expect(response).to be_no_content\n\n      env = Rack::MockRequest.env_for('/app2', method: Rack::GET)\n      response = Rack::MockResponse[*base_app.call(env)]\n\n      expect(response).to be_no_content\n    end\n  end\n\n  describe '.route' do\n    before do\n      allow(subject).to receive(:endpoints).and_return([])\n      allow(subject.inheritable_setting).to receive(:route_end)\n      allow(subject).to receive(:reset_validations!)\n    end\n\n    it 'marks end of the route' do\n      expect(subject.inheritable_setting).to receive(:route_end)\n      subject.route(:any)\n    end\n\n    it 'resets validations' do\n      expect(subject).to receive(:reset_validations!)\n      subject.route(:any)\n    end\n\n    it 'defines a new endpoint' do\n      expect { subject.route(:any) }\n        .to change { subject.endpoints.count }.from(0).to(1)\n    end\n\n    it 'does not duplicate identical endpoints' do\n      subject.route(:any)\n      expect { subject.route(:any) }\n        .not_to change(subject.endpoints, :count)\n    end\n\n    it 'generates correct endpoint options' do\n      subject.inheritable_setting.route[:description] = { fiz: 'baz' }\n      subject.inheritable_setting.namespace_stackable[:params] = { nuz: 'naz' }\n\n      expect(Grape::Endpoint).to receive(:new) do |_inheritable_setting, endpoint_options|\n        expect(endpoint_options[:method]).to eq :get\n        expect(endpoint_options[:path]).to eq '/foo'\n        expect(endpoint_options[:for]).to eq subject\n        expect(endpoint_options[:route_options]).to eq(foo: 'bar', fiz: 'baz', params: { nuz: 'naz' })\n      end.and_yield\n\n      subject.route(:get, '/foo', { foo: 'bar' }, &proc {})\n    end\n  end\n\n  describe '.get' do\n    it 'delegates to .route' do\n      expect(subject).to receive(:route).with(Rack::GET, path, options)\n      subject.get path, **options, &proc\n    end\n  end\n\n  describe '.post' do\n    it 'delegates to .route' do\n      expect(subject).to receive(:route).with(Rack::POST, path, options)\n      subject.post path, **options, &proc\n    end\n  end\n\n  describe '.put' do\n    it 'delegates to .route' do\n      expect(subject).to receive(:route).with(Rack::PUT, path, options)\n      subject.put path, **options, &proc\n    end\n  end\n\n  describe '.head' do\n    it 'delegates to .route' do\n      expect(subject).to receive(:route).with(Rack::HEAD, path, options)\n      subject.head path, **options, &proc\n    end\n  end\n\n  describe '.delete' do\n    it 'delegates to .route' do\n      expect(subject).to receive(:route).with(Rack::DELETE, path, options)\n      subject.delete path, **options, &proc\n    end\n  end\n\n  describe '.options' do\n    it 'delegates to .route' do\n      expect(subject).to receive(:route).with(Rack::OPTIONS, path, options)\n      subject.options path, **options, &proc\n    end\n  end\n\n  describe '.patch' do\n    it 'delegates to .route' do\n      expect(subject).to receive(:route).with(Rack::PATCH, path, options)\n      subject.patch path, **options, &proc\n    end\n  end\n\n  describe '.namespace' do\n    it 'creates a new namespace with given name and options' do\n      subject.namespace(:foo, foo: 'bar') {}\n      expect(subject.namespace(:foo, foo: 'bar')).to eq(Grape::Namespace.new(:foo, foo: 'bar'))\n    end\n\n    it 'calls #joined_space_path on Namespace' do\n      inside_namespace = nil\n      subject.namespace(:foo, foo: 'bar') do\n        inside_namespace = namespace\n      end\n      expect(inside_namespace).to eq('/foo')\n    end\n  end\n\n  describe '.group' do\n    it 'is alias to #namespace' do\n      expect(subject.method(:group)).to eq subject.method(:namespace)\n    end\n  end\n\n  describe '.resource' do\n    it 'is alias to #namespace' do\n      expect(subject.method(:resource)).to eq subject.method(:namespace)\n    end\n  end\n\n  describe '.resources' do\n    it 'is alias to #namespace' do\n      expect(subject.method(:resources)).to eq subject.method(:namespace)\n    end\n  end\n\n  describe '.segment' do\n    it 'is alias to #namespace' do\n      expect(subject.method(:segment)).to eq subject.method(:namespace)\n    end\n  end\n\n  describe '.routes' do\n    let(:main_app) { Class.new(Grape::API) }\n    let(:first_app) { Class.new(Grape::API) }\n    let(:second_app) { Class.new(Grape::API) }\n\n    before do\n      main_app.mount(first_app => '/first_app', second_app => '/second_app')\n    end\n\n    it 'returns flatten endpoints routes' do\n      expect(main_app.endpoints).not_to be_empty\n      expect(main_app.routes).to eq(main_app.endpoints.map(&:routes).flatten)\n    end\n\n    context 'when #routes was already called once' do\n      it 'memoizes' do\n        object_id = main_app.routes.object_id\n        expect(main_app.routes.object_id).to eq(object_id)\n      end\n    end\n  end\n\n  describe '.route_param' do\n    let!(:options) { { requirements: regex } }\n    let(:regex) { /(.*)/ }\n\n    it 'calls #namespace with given params' do\n      expect(subject).to receive(:namespace).with(':foo', requirements: nil).and_yield\n      subject.route_param('foo', &proc {})\n    end\n\n    it 'nests requirements option under param name' do\n      expect(subject).to receive(:namespace) do |_param, options|\n        expect(options[:requirements][:foo]).to eq regex\n      end\n      subject.route_param('foo', **options, &proc {})\n    end\n\n    it 'does not modify options parameter' do\n      allow(subject).to receive(:namespace)\n      expect { subject.route_param('foo', **options, &proc {}) }\n        .not_to(change { options })\n    end\n  end\n\n  describe '.versions' do\n    it 'returns last defined version' do\n      subject.version 'v1'\n      subject.version 'v2'\n      expect(subject.version).to eq('v2')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/dsl/settings_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::DSL::Settings do\n  subject { dummy_class.new }\n\n  let(:dummy_class) do\n    Class.new do\n      include Grape::DSL::Settings\n\n      def with_namespace(&block)\n        within_namespace(&block)\n      end\n\n      def reset_validations!; end\n    end\n  end\n\n  describe '#global_setting' do\n    it 'sets a value globally' do\n      subject.global_setting :some_thing, :foo_bar\n      expect(subject.global_setting(:some_thing)).to eq :foo_bar\n      subject.with_namespace do\n        subject.global_setting :some_thing, :foo_bar_baz\n        expect(subject.global_setting(:some_thing)).to eq :foo_bar_baz\n      end\n      expect(subject.global_setting(:some_thing)).to eq(:foo_bar_baz)\n    end\n  end\n\n  describe '#route_setting' do\n    it 'sets a value until the end of a namespace' do\n      subject.with_namespace do\n        subject.route_setting :some_thing, :foo_bar\n        expect(subject.route_setting(:some_thing)).to eq :foo_bar\n      end\n      expect(subject.route_setting(:some_thing)).to be_nil\n    end\n  end\n\n  describe '#namespace_setting' do\n    it 'sets a value until the end of a namespace' do\n      subject.with_namespace do\n        subject.namespace_setting :some_thing, :foo_bar\n        expect(subject.namespace_setting(:some_thing)).to eq :foo_bar\n      end\n      expect(subject.namespace_setting(:some_thing)).to be_nil\n    end\n\n    it 'resets values after leaving nested namespaces' do\n      subject.with_namespace do\n        subject.namespace_setting :some_thing, :foo_bar\n        expect(subject.namespace_setting(:some_thing)).to eq :foo_bar\n        subject.with_namespace do\n          expect(subject.namespace_setting(:some_thing)).to be_nil\n        end\n        expect(subject.namespace_setting(:some_thing)).to eq :foo_bar\n      end\n      expect(subject.namespace_setting(:some_thing)).to be_nil\n    end\n  end\n\n  describe '#namespace_inheritable' do\n    it 'inherits values from surrounding namespace' do\n      subject.with_namespace do\n        subject.inheritable_setting.namespace_inheritable[:some_thing] = :foo_bar\n        expect(subject.inheritable_setting.namespace_inheritable[:some_thing]).to eq :foo_bar\n        subject.with_namespace do\n          expect(subject.inheritable_setting.namespace_inheritable[:some_thing]).to eq :foo_bar\n          subject.inheritable_setting.namespace_inheritable[:some_thing] = :foo_bar_2\n          expect(subject.inheritable_setting.namespace_inheritable[:some_thing]).to eq :foo_bar_2\n        end\n        expect(subject.inheritable_setting.namespace_inheritable[:some_thing]).to eq :foo_bar\n      end\n    end\n  end\n\n  describe '#namespace_stackable' do\n    it 'stacks values from surrounding namespace' do\n      subject.with_namespace do\n        subject.inheritable_setting.namespace_stackable[:some_thing] = :foo_bar\n        expect(subject.inheritable_setting.namespace_stackable[:some_thing]).to eq [:foo_bar]\n        subject.with_namespace do\n          subject.inheritable_setting.namespace_stackable[:some_thing] = :foo_bar_2\n          expect(subject.inheritable_setting.namespace_stackable[:some_thing]).to eq %i[foo_bar foo_bar_2]\n        end\n        expect(subject.inheritable_setting.namespace_stackable[:some_thing]).to eq [:foo_bar]\n      end\n    end\n  end\n\n  describe 'complex scenario' do\n    it 'plays well' do\n      obj1 = dummy_class.new\n      obj2 = dummy_class.new\n      obj3 = dummy_class.new\n\n      obj1_copy = nil\n      obj2_copy = nil\n      obj3_copy = nil\n\n      obj1.with_namespace do\n        obj1.inheritable_setting.namespace_stackable[:some_thing] = :obj1\n        expect(obj1.inheritable_setting.namespace_stackable[:some_thing]).to eq [:obj1]\n        obj1_copy = obj1.inheritable_setting.point_in_time_copy\n      end\n\n      expect(obj1.inheritable_setting.namespace_stackable[:some_thing]).to eq []\n      expect(obj1_copy.namespace_stackable[:some_thing]).to eq [:obj1]\n\n      obj2.with_namespace do\n        obj2.inheritable_setting.namespace_stackable[:some_thing] = :obj2\n        expect(obj2.inheritable_setting.namespace_stackable[:some_thing]).to eq [:obj2]\n        obj2_copy = obj2.inheritable_setting.point_in_time_copy\n      end\n\n      expect(obj2.inheritable_setting.namespace_stackable[:some_thing]).to eq []\n      expect(obj2_copy.namespace_stackable[:some_thing]).to eq [:obj2]\n\n      obj3.with_namespace do\n        obj3.inheritable_setting.namespace_stackable[:some_thing] = :obj3\n        expect(obj3.inheritable_setting.namespace_stackable[:some_thing]).to eq [:obj3]\n        obj3_copy = obj3.inheritable_setting.point_in_time_copy\n      end\n\n      expect(obj3.inheritable_setting.namespace_stackable[:some_thing]).to eq []\n      expect(obj3_copy.namespace_stackable[:some_thing]).to eq [:obj3]\n\n      # obj1.top_level_setting.inherit_from obj2_copy.point_in_time_copy\n      # obj2.top_level_setting.inherit_from obj3_copy.point_in_time_copy\n\n      # expect(obj1_copy.namespace_stackable[:some_thing]).to eq %i[obj3 obj2 obj1]\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/dsl/validations_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::DSL::Validations do\n  subject { dummy_class }\n\n  let(:dummy_class) do\n    Class.new do\n      extend Grape::DSL::Settings\n      extend Grape::DSL::Validations\n    end\n  end\n\n  describe '.params' do\n    subject { dummy_class.params { :my_block } }\n\n    it 'creates a proper Grape::Validations::ParamsScope' do\n      expect(Grape::Validations::ParamsScope).to receive(:new).with(api: dummy_class, type: Hash) do |_func, &block|\n        expect(block.call).to eq(:my_block)\n      end.and_return(:param_scope)\n      expect(subject).to eq(:param_scope)\n    end\n  end\n\n  describe '.contract' do\n    context 'when contract is nil and blockless' do\n      it 'raises an ArgumentError' do\n        expect { dummy_class.contract }.to raise_error(ArgumentError, 'Either contract or block must be provided')\n      end\n    end\n\n    context 'when contract is nil and but a block is provided' do\n      it 'returns a proper rape::Validations::ContractScope' do\n        expect(Grape::Validations::ContractScope).to receive(:new).with(dummy_class, nil) do |_func, &block|\n          expect(block.call).to eq(:my_block)\n        end.and_return(:my_contract_scope)\n\n        expect(dummy_class.contract { :my_block }).to eq(:my_contract_scope)\n      end\n    end\n\n    context 'when contract is present and blockless' do\n      subject { dummy_class.contract(:my_contract) }\n\n      before do\n        allow(Grape::Validations::ContractScope).to receive(:new).with(dummy_class, :my_contract).and_return(:my_contract_scope)\n      end\n\n      it { is_expected.to eq(:my_contract_scope) }\n    end\n\n    context 'when contract and block are provided' do\n      context 'when contract does not respond to schema' do\n        let(:my_contract) { Class.new }\n\n        it 'returns a proper rape::Validations::ContractScope' do\n          expect(Grape::Validations::ContractScope).to receive(:new).with(dummy_class, my_contract) do |_func, &block|\n            expect(block.call).to eq(:my_block)\n          end.and_return(:my_contract_scope)\n\n          expect(dummy_class.contract(my_contract.new) { :my_block }).to eq(:my_contract_scope)\n        end\n      end\n\n      context 'when contract responds to schema' do\n        let(:my_contract) do\n          Class.new do\n            def schema; end\n          end\n        end\n\n        it 'raises an ArgumentError' do\n          expect { dummy_class.contract(my_contract.new) { :my_block } }.to raise_error(ArgumentError, 'Cannot inherit from contract, only schema')\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/endpoint/declared_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Endpoint do\n  subject { Class.new(Grape::API) }\n\n  let(:app) { subject }\n\n  describe '#declared' do\n    before do\n      subject.format :json\n      subject.params do\n        requires :first\n        optional :second\n        optional :third, default: 'third-default'\n        optional :multiple_types, types: [Integer, String]\n        optional :nested, type: Hash do\n          optional :fourth\n          optional :fifth\n          optional :nested_two, type: Hash do\n            optional :sixth\n            optional :nested_three, type: Hash do\n              optional :seventh\n            end\n          end\n          optional :nested_arr, type: Array do\n            optional :eighth\n          end\n          optional :empty_arr, type: Array\n          optional :empty_typed_arr, type: Array[String]\n          optional :empty_hash, type: Hash\n          optional :empty_set, type: Set\n          optional :empty_typed_set, type: Set[String]\n        end\n        optional :arr, type: Array do\n          optional :nineth\n        end\n        optional :empty_arr, type: Array\n        optional :empty_typed_arr, type: Array[String]\n        optional :empty_hash, type: Hash\n        optional :empty_hash_two, type: Hash\n        optional :empty_set, type: Set\n        optional :empty_typed_set, type: Set[String]\n      end\n    end\n\n    context 'when params are not built with default class' do\n      it 'returns an object that corresponds with the params class - hash with indifferent access' do\n        subject.params do\n          build_with :hash_with_indifferent_access\n        end\n        subject.get '/declared' do\n          d = declared(params, include_missing: true)\n          { declared_class: d.class.to_s }\n        end\n\n        get '/declared?first=present'\n        expect(JSON.parse(last_response.body)['declared_class']).to eq('ActiveSupport::HashWithIndifferentAccess')\n      end\n\n      it 'returns an object that corresponds with the params class - hash' do\n        subject.params do\n          build_with :hash\n        end\n        subject.get '/declared' do\n          d = declared(params, include_missing: true)\n          { declared_class: d.class.to_s }\n        end\n\n        get '/declared?first=present'\n        expect(JSON.parse(last_response.body)['declared_class']).to eq('Hash')\n      end\n    end\n\n    it 'shows nil for nested params if include_missing is true' do\n      subject.get '/declared' do\n        declared(params, include_missing: true)\n      end\n\n      get '/declared?first=present'\n      expect(last_response).to be_successful\n      expect(JSON.parse(last_response.body)['nested']['fourth']).to be_nil\n    end\n\n    it 'shows nil for multiple allowed types if include_missing is true' do\n      subject.get '/declared' do\n        declared(params, include_missing: true)\n      end\n\n      get '/declared?first=present'\n      expect(last_response).to be_successful\n      expect(JSON.parse(last_response.body)['multiple_types']).to be_nil\n    end\n\n    it 'does not work in a before filter' do\n      subject.before do\n        declared(params)\n      end\n      subject.get('/declared') { declared(params) }\n\n      expect { get('/declared') }.to raise_error(\n        Grape::DSL::InsideRoute::MethodNotYetAvailable\n      )\n    end\n\n    it 'has as many keys as there are declared params' do\n      subject.get '/declared' do\n        declared(params)\n      end\n      get '/declared?first=present'\n      expect(last_response).to be_successful\n      expect(JSON.parse(last_response.body).keys.size).to eq(12)\n    end\n\n    it 'has a optional param with default value all the time' do\n      subject.get '/declared' do\n        declared(params)\n      end\n      get '/declared?first=one'\n      expect(last_response).to be_successful\n      expect(JSON.parse(last_response.body)['third']).to eql('third-default')\n    end\n\n    it 'builds nested params' do\n      subject.get '/declared' do\n        declared(params)\n      end\n\n      get '/declared?first=present&nested[fourth]=1'\n      expect(last_response).to be_successful\n      expect(JSON.parse(last_response.body)['nested'].keys.size).to eq 9\n    end\n\n    it 'builds arrays correctly' do\n      subject.params do\n        requires :first\n        optional :second, type: Array\n      end\n      subject.post('/declared') { declared(params) }\n\n      post '/declared', first: 'present', second: ['present']\n      expect(last_response).to be_created\n\n      body = JSON.parse(last_response.body)\n      expect(body['second']).to eq(['present'])\n    end\n\n    it 'builds nested params when given array' do\n      subject.get '/dummy' do\n      end\n      subject.params do\n        requires :first\n        optional :second\n        optional :third, default: 'third-default'\n        optional :nested, type: Array do\n          optional :fourth\n        end\n      end\n      subject.get '/declared' do\n        declared(params)\n      end\n\n      get '/declared?first=present&nested[][fourth]=1&nested[][fourth]=2'\n      expect(last_response).to be_successful\n      expect(JSON.parse(last_response.body)['nested'].size).to eq 2\n    end\n\n    context 'when the param is missing and include_missing=false' do\n      before do\n        subject.get('/declared') { declared(params, include_missing: false) }\n      end\n\n      it 'sets nested objects to be nil' do\n        get '/declared?first=present'\n        expect(last_response).to be_successful\n        expect(JSON.parse(last_response.body)['nested']).to be_nil\n      end\n    end\n\n    context 'when the param is missing and include_missing=true' do\n      before do\n        subject.get('/declared') { declared(params, include_missing: true) }\n      end\n\n      it 'sets objects with type=Hash to be a hash' do\n        get '/declared?first=present'\n        expect(last_response).to be_successful\n\n        body = JSON.parse(last_response.body)\n        expect(body['empty_hash']).to eq({})\n        expect(body['empty_hash_two']).to eq({})\n        expect(body['nested']).to be_a(Hash)\n        expect(body['nested']['empty_hash']).to eq({})\n        expect(body['nested']['nested_two']).to be_a(Hash)\n      end\n\n      it 'sets objects with type=Set to be a set' do\n        get '/declared?first=present'\n        expect(last_response).to be_successful\n        json_empty_set = JSON.parse(Set.new.to_json)\n\n        body = JSON.parse(last_response.body)\n        expect(body['empty_set']).to eq(json_empty_set)\n        expect(body['empty_typed_set']).to eq(json_empty_set)\n        expect(body['nested']['empty_set']).to eq(json_empty_set)\n        expect(body['nested']['empty_typed_set']).to eq(json_empty_set)\n      end\n\n      it 'sets objects with type=Array to be an array' do\n        get '/declared?first=present'\n        expect(last_response).to be_successful\n\n        body = JSON.parse(last_response.body)\n        expect(body['empty_arr']).to eq([])\n        expect(body['empty_typed_arr']).to eq([])\n        expect(body['arr']).to eq([])\n        expect(body['nested']['empty_arr']).to eq([])\n        expect(body['nested']['empty_typed_arr']).to eq([])\n        expect(body['nested']['nested_arr']).to eq([])\n      end\n\n      it 'includes all declared children when type=Hash' do\n        get '/declared?first=present'\n        expect(last_response).to be_successful\n\n        body = JSON.parse(last_response.body)\n        expect(body['nested'].keys).to eq(%w[fourth fifth nested_two nested_arr empty_arr empty_typed_arr empty_hash empty_set empty_typed_set])\n        expect(body['nested']['nested_two'].keys).to eq(%w[sixth nested_three])\n        expect(body['nested']['nested_two']['nested_three'].keys).to eq(%w[seventh])\n      end\n    end\n\n    it 'filters out any additional params that are given' do\n      subject.get '/declared' do\n        declared(params)\n      end\n      get '/declared?first=one&other=two'\n      expect(last_response).to be_successful\n      expect(JSON.parse(last_response.body).key?(:other)).to be false\n    end\n\n    it 'stringifies if that option is passed' do\n      subject.get '/declared' do\n        declared(params, stringify: true)\n      end\n\n      get '/declared?first=one&other=two'\n      expect(last_response).to be_successful\n      expect(JSON.parse(last_response.body)['first']).to eq 'one'\n    end\n\n    it 'does not include missing attributes if that option is passed' do\n      subject.get '/declared' do\n        error! 'expected nil', 400 if declared(params, include_missing: false).key?(:second)\n        ''\n      end\n\n      get '/declared?first=one&other=two'\n      expect(last_response).to be_successful\n    end\n\n    it 'does not include renamed missing attributes if that option is passed' do\n      subject.params do\n        optional :renamed_original, as: :renamed\n      end\n      subject.get '/declared' do\n        error! 'expected nil', 400 if declared(params, include_missing: false).key?(:renamed)\n        ''\n      end\n\n      get '/declared?first=one&other=two'\n      expect(last_response).to be_successful\n    end\n\n    it 'includes attributes with value that evaluates to false' do\n      subject.params do\n        requires :first\n        optional :boolean\n      end\n\n      subject.post '/declared' do\n        error!('expected false', 400) if declared(params, include_missing: false)[:boolean] != false\n        ''\n      end\n\n      post '/declared', Grape::Json.dump(first: 'one', boolean: false), 'CONTENT_TYPE' => 'application/json'\n      expect(last_response).to be_created\n    end\n\n    it 'includes attributes with value that evaluates to nil' do\n      subject.params do\n        requires :first\n        optional :second\n      end\n\n      subject.post '/declared' do\n        error!('expected nil', 400) unless declared(params, include_missing: false)[:second].nil?\n        ''\n      end\n\n      post '/declared', Grape::Json.dump(first: 'one', second: nil), 'CONTENT_TYPE' => 'application/json'\n      expect(last_response).to be_created\n    end\n\n    it 'includes missing attributes with defaults when there are nested hashes' do\n      subject.get '/dummy' do\n      end\n\n      subject.params do\n        requires :first\n        optional :second\n        optional :third, default: nil\n        optional :nested, type: Hash do\n          optional :fourth, default: nil\n          optional :fifth, default: nil\n          requires :nested_nested, type: Hash do\n            optional :sixth, default: 'sixth-default'\n            optional :seven, default: nil\n          end\n        end\n      end\n\n      subject.get '/declared' do\n        declared(params, include_missing: false)\n      end\n\n      get '/declared?first=present&nested[fourth]=&nested[nested_nested][sixth]=sixth'\n      json = JSON.parse(last_response.body)\n      expect(last_response).to be_successful\n      expect(json['first']).to eq 'present'\n      expect(json['nested'].keys).to eq %w[fourth fifth nested_nested]\n      expect(json['nested']['fourth']).to eq ''\n      expect(json['nested']['nested_nested'].keys).to eq %w[sixth seven]\n      expect(json['nested']['nested_nested']['sixth']).to eq 'sixth'\n    end\n\n    it 'does not include missing attributes when there are nested hashes' do\n      subject.get '/dummy' do\n      end\n\n      subject.params do\n        requires :first\n        optional :second\n        optional :third\n        optional :nested, type: Hash do\n          optional :fourth\n          optional :fifth\n        end\n      end\n\n      subject.get '/declared' do\n        declared(params, include_missing: false)\n      end\n\n      get '/declared?first=present&nested[fourth]=4'\n      json = JSON.parse(last_response.body)\n      expect(last_response).to be_successful\n      expect(json['first']).to eq 'present'\n      expect(json['nested'].keys).to eq %w[fourth]\n      expect(json['nested']['fourth']).to eq '4'\n    end\n  end\n\n  describe '#declared; call from child namespace' do\n    before do\n      subject.format :json\n      subject.namespace :parent do\n        params do\n          requires :parent_name, type: String\n        end\n\n        namespace ':parent_name' do\n          params do\n            requires :child_name, type: String\n            requires :child_age, type: Integer\n          end\n\n          namespace ':child_name' do\n            params do\n              requires :grandchild_name, type: String\n            end\n\n            get ':grandchild_name' do\n              {\n                'params' => params,\n                'without_parent_namespaces' => declared(params, include_parent_namespaces: false),\n                'with_parent_namespaces' => declared(params, include_parent_namespaces: true)\n              }\n            end\n          end\n        end\n      end\n\n      get '/parent/foo/bar/baz', child_age: 5, extra: 'hello'\n    end\n\n    let(:parsed_response) { JSON.parse(last_response.body, symbolize_names: true) }\n\n    it { expect(last_response).to be_successful }\n\n    context 'with include_parent_namespaces: false' do\n      it 'returns declared parameters only from current namespace' do\n        expect(parsed_response[:without_parent_namespaces]).to eq(\n          grandchild_name: 'baz'\n        )\n      end\n    end\n\n    context 'with include_parent_namespaces: true' do\n      it 'returns declared parameters from every parent namespace' do\n        expect(parsed_response[:with_parent_namespaces]).to eq(\n          parent_name: 'foo',\n          child_name: 'bar',\n          grandchild_name: 'baz',\n          child_age: 5\n        )\n      end\n    end\n\n    context 'without declaration' do\n      it 'returns all requested parameters' do\n        expect(parsed_response[:params]).to eq(\n          parent_name: 'foo',\n          child_name: 'bar',\n          grandchild_name: 'baz',\n          child_age: 5,\n          extra: 'hello'\n        )\n      end\n    end\n  end\n\n  describe '#declared; from a nested mounted endpoint' do\n    before do\n      doubly_mounted = Class.new(Grape::API)\n      doubly_mounted.namespace :more do\n        params do\n          requires :y, type: Integer\n        end\n        route_param :y do\n          get do\n            {\n              params: params,\n              declared_params: declared(params)\n            }\n          end\n        end\n      end\n\n      mounted = Class.new(Grape::API)\n      mounted.namespace :another do\n        params do\n          requires :mount_space, type: Integer\n        end\n        route_param :mount_space do\n          mount doubly_mounted\n        end\n      end\n\n      subject.format :json\n      subject.namespace :something do\n        params do\n          requires :id, type: Integer\n        end\n        resource ':id' do\n          mount mounted\n        end\n      end\n    end\n\n    it 'can access parent attributes' do\n      get '/something/123/another/456/more/789'\n      expect(last_response).to be_successful\n      json = JSON.parse(last_response.body, symbolize_names: true)\n\n      # test all three levels of params\n      expect(json[:declared_params][:y]).to eq 789\n      expect(json[:declared_params][:mount_space]).to eq 456\n      expect(json[:declared_params][:id]).to eq 123\n    end\n  end\n\n  describe '#declared; mixed nesting' do\n    before do\n      subject.format :json\n      subject.resource :users do\n        route_param :id, type: Integer, desc: 'ID desc' do\n          # Adding this causes route_setting(:declared_params) to be nil for the\n          # get block in namespace 'foo' below\n          get do\n          end\n\n          namespace 'foo' do\n            get do\n              {\n                params: params,\n                declared_params: declared(params),\n                declared_params_no_parent: declared(params, include_parent_namespaces: false)\n              }\n            end\n          end\n        end\n      end\n    end\n\n    it 'can access parent route_param' do\n      get '/users/123/foo', bar: 'bar'\n      expect(last_response).to be_successful\n      json = JSON.parse(last_response.body, symbolize_names: true)\n\n      expect(json[:declared_params][:id]).to eq 123\n      expect(json[:declared_params_no_parent][:id]).to be_nil\n    end\n  end\n\n  describe '#declared; with multiple route_param' do\n    before do\n      mounted = Class.new(Grape::API)\n      mounted.namespace :albums do\n        get do\n          declared(params)\n        end\n      end\n\n      subject.format :json\n      subject.namespace :artists do\n        route_param :id, type: Integer do\n          get do\n            declared(params)\n          end\n\n          params do\n            requires :filter, type: String\n          end\n          get :some_route do\n            declared(params)\n          end\n        end\n\n        route_param :artist_id, type: Integer do\n          namespace :compositions do\n            get do\n              declared(params)\n            end\n          end\n        end\n\n        route_param :compositor_id, type: Integer do\n          mount mounted\n        end\n      end\n    end\n\n    it 'return only :id without :artist_id' do\n      get '/artists/1'\n      json = JSON.parse(last_response.body, symbolize_names: true)\n\n      expect(json).to be_key(:id)\n      expect(json).not_to be_key(:artist_id)\n    end\n\n    it 'return only :artist_id without :id' do\n      get '/artists/1/compositions'\n      json = JSON.parse(last_response.body, symbolize_names: true)\n\n      expect(json).to be_key(:artist_id)\n      expect(json).not_to be_key(:id)\n    end\n\n    it 'return :filter and :id parameters in declared for second enpoint inside route_param' do\n      get '/artists/1/some_route', filter: 'some_filter'\n      json = JSON.parse(last_response.body, symbolize_names: true)\n\n      expect(json).to be_key(:filter)\n      expect(json).to be_key(:id)\n      expect(json).not_to be_key(:artist_id)\n    end\n\n    it 'return :compositor_id for mounter in route_param' do\n      get '/artists/1/albums'\n      json = JSON.parse(last_response.body, symbolize_names: true)\n\n      expect(json).to be_key(:compositor_id)\n      expect(json).not_to be_key(:id)\n      expect(json).not_to be_key(:artist_id)\n    end\n  end\n\n  describe 'parameter renaming' do\n    context 'with a deeply nested parameter structure' do\n      let(:params) do\n        {\n          i_a: 'a',\n          i_b: {\n            i_c: 'c',\n            i_d: {\n              i_e: {\n                i_f: 'f',\n                i_g: 'g',\n                i_h: [\n                  {\n                    i_ha: 'ha1',\n                    i_hb: {\n                      i_hc: 'c'\n                    }\n                  },\n                  {\n                    i_ha: 'ha2',\n                    i_hb: {\n                      i_hc: 'c'\n                    }\n                  }\n                ]\n              }\n            }\n          }\n        }\n      end\n      let(:declared) do\n        {\n          o_a: 'a',\n          o_b: {\n            o_c: 'c',\n            o_d: {\n              o_e: {\n                o_f: 'f',\n                o_g: 'g',\n                o_h: [\n                  {\n                    o_ha: 'ha1',\n                    o_hb: {\n                      o_hc: 'c'\n                    }\n                  },\n                  {\n                    o_ha: 'ha2',\n                    o_hb: {\n                      o_hc: 'c'\n                    }\n                  }\n                ]\n              }\n            }\n          }\n        }\n      end\n      let(:params_keys) do\n        [\n          'i_a',\n          'i_b',\n          'i_b[i_c]',\n          'i_b[i_d]',\n          'i_b[i_d][i_e]',\n          'i_b[i_d][i_e][i_f]',\n          'i_b[i_d][i_e][i_g]',\n          'i_b[i_d][i_e][i_h]',\n          'i_b[i_d][i_e][i_h][i_ha]',\n          'i_b[i_d][i_e][i_h][i_hb]',\n          'i_b[i_d][i_e][i_h][i_hb][i_hc]'\n        ]\n      end\n\n      before do\n        subject.format :json\n        subject.params do\n          optional :i_a, type: String, as: :o_a\n          optional :i_b, type: Hash, as: :o_b do\n            optional :i_c, type: String, as: :o_c\n            optional :i_d, type: Hash, as: :o_d do\n              optional :i_e, type: Hash, as: :o_e do\n                optional :i_f, type: String, as: :o_f\n                optional :i_g, type: String, as: :o_g\n                optional :i_h, type: Array, as: :o_h do\n                  optional :i_ha, type: String, as: :o_ha\n                  optional :i_hb, type: Hash, as: :o_hb do\n                    optional :i_hc, type: String, as: :o_hc\n                  end\n                end\n              end\n            end\n          end\n        end\n        subject.post '/test' do\n          declared(params, include_missing: false)\n        end\n        subject.post '/test/no-mod' do\n          before = params.to_h\n          declared(params, include_missing: false)\n          after = params.to_h\n          { before: before, after: after }\n        end\n      end\n\n      it 'generates the correct parameter names for documentation' do\n        expect(subject.routes.first.params.keys).to match(params_keys)\n      end\n\n      it 'maps the renamed parameter correctly' do\n        post '/test', **params\n        expect(JSON.parse(last_response.body, symbolize_names: true)).to \\\n          match(declared)\n      end\n\n      it 'maps no parameters when none are given' do\n        post '/test'\n        expect(JSON.parse(last_response.body)).to match({})\n      end\n\n      it 'does not modify the request params' do\n        post '/test/no-mod', **params\n        result = JSON.parse(last_response.body, symbolize_names: true)\n        expect(result[:before]).to match(result[:after])\n      end\n    end\n\n    context 'with a renamed root parameter' do\n      before do\n        subject.format :json\n        subject.params do\n          optional :email_address, type: String, regexp: /.+@.+/, as: :email\n        end\n        subject.post '/test' do\n          declared(params, include_missing: false)\n        end\n      end\n\n      it 'generates the correct parameter names for documentation' do\n        expect(subject.routes.first.params.keys).to match(%w[email_address])\n      end\n\n      it 'maps the renamed parameter correctly (original name)' do\n        post '/test', email_address: 'test@example.com'\n        expect(JSON.parse(last_response.body)).to \\\n          match('email' => 'test@example.com')\n      end\n\n      it 'validates the renamed parameter correctly (original name)' do\n        post '/test', email_address: 'bad[at]example.com'\n        expect(JSON.parse(last_response.body)).to \\\n          match('error' => 'email_address is invalid')\n      end\n\n      it 'ignores the renamed parameter (as name)' do\n        post '/test', email: 'test@example.com'\n        expect(JSON.parse(last_response.body)).to match({})\n      end\n    end\n\n    context 'with a renamed hash with nested parameters' do\n      before do\n        subject.format :json\n        subject.params do\n          optional :address, type: Hash, as: :address_attributes do\n            optional :street, type: String, values: ['Street 1', 'Street 2'],\n                              default: 'Street 1'\n            optional :city, type: String\n          end\n        end\n        subject.post '/test' do\n          declared(params, include_missing: false)\n        end\n      end\n\n      it 'generates the correct parameter names for documentation' do\n        expect(subject.routes.first.params.keys).to \\\n          match(%w[address address[street] address[city]])\n      end\n\n      it 'maps the renamed parameter correctly (original name)' do\n        post '/test', address: { city: 'Berlin', street: 'Street 2', t: 't' }\n        expect(JSON.parse(last_response.body)).to \\\n          match('address_attributes' => { 'city' => 'Berlin',\n                                          'street' => 'Street 2' })\n      end\n\n      it 'validates the renamed parameter correctly (original name)' do\n        post '/test', address: { street: 'unknown' }\n        expect(JSON.parse(last_response.body)).to \\\n          match('error' => 'address[street] does not have a valid value')\n      end\n\n      it 'ignores the renamed parameter (as name)' do\n        post '/test', address_attributes: { city: 'Berlin', unknown: '1' }\n        expect(JSON.parse(last_response.body)).to match({})\n      end\n    end\n\n    context 'with a renamed hash with nested renamed parameter' do\n      before do\n        subject.format :json\n        subject.params do\n          optional :user, type: Hash, as: :user_attributes do\n            optional :email_address, type: String, regexp: /.+@.+/, as: :email\n          end\n        end\n        subject.post '/test' do\n          declared(params, include_missing: false)\n        end\n      end\n\n      it 'generates the correct parameter names for documentation' do\n        expect(subject.routes.first.params.keys).to \\\n          match(%w[user user[email_address]])\n      end\n\n      it 'maps the renamed parameter correctly (original name)' do\n        post '/test', user: { email_address: 'test@example.com' }\n        expect(JSON.parse(last_response.body)).to \\\n          match('user_attributes' => { 'email' => 'test@example.com' })\n      end\n\n      it 'validates the renamed parameter correctly (original name)' do\n        post '/test', user: { email_address: 'bad[at]example.com' }\n        expect(JSON.parse(last_response.body)).to \\\n          match('error' => 'user[email_address] is invalid')\n      end\n\n      it 'ignores the renamed parameter (as name, 1)' do\n        post '/test', user: { email: 'test@example.com' }\n        expect(JSON.parse(last_response.body)).to \\\n          match({ 'user_attributes' => {} })\n      end\n\n      it 'ignores the renamed parameter (as name, 2)' do\n        post '/test', user_attributes: { email_address: 'test@example.com' }\n        expect(JSON.parse(last_response.body)).to match({})\n      end\n\n      it 'ignores the renamed parameter (as name, 3)' do\n        post '/test', user_attributes: { email: 'test@example.com' }\n        expect(JSON.parse(last_response.body)).to match({})\n      end\n    end\n\n    context 'with a renamed field inside `given` block nested in hashes' do\n      before do\n        subject.format :json\n        subject.params do\n          requires :a, type: Hash do\n            optional :c, type: String\n            given :c do\n              requires :b, type: Hash do\n                requires :input_field, as: :output_field\n              end\n            end\n          end\n        end\n        subject.post '/test' do\n          declared(params)\n        end\n      end\n\n      it 'renames parameter input_field to output_field' do\n        post '/test', { a: { b: { input_field: 'value' }, c: 'value2' } }\n\n        expect(JSON.parse(last_response.body)).to \\\n          match('a' => { 'b' => { 'output_field' => 'value' }, 'c' => 'value2' })\n      end\n    end\n  end\n\n  describe 'optional_array' do\n    subject { last_response }\n\n    let(:app) do\n      Class.new(Grape::API) do\n        params do\n          requires :z, type: Array do\n            optional :a, type: Integer\n          end\n        end\n\n        post do\n          declared(params, include_missing: false)\n        end\n      end\n    end\n\n    before { post '/', { z: [] } }\n\n    it { is_expected.to be_successful }\n  end\nend\n"
  },
  {
    "path": "spec/grape/endpoint_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Endpoint do\n  subject { Class.new(Grape::API) }\n\n  def app\n    subject\n  end\n\n  describe '.before_each' do\n    after { described_class.before_each.clear }\n\n    it 'is settable via block' do\n      block = ->(_endpoint) { 'noop' }\n      described_class.before_each(&block)\n      expect(described_class.before_each.first).to eq(block)\n    end\n\n    it 'is settable via reference' do\n      block = ->(_endpoint) { 'noop' }\n      described_class.before_each block\n      expect(described_class.before_each.first).to eq(block)\n    end\n\n    it 'is able to override a helper' do\n      subject.get('/') { current_user }\n      expect { get '/' }.to raise_error(NameError, /undefined local variable or method [`']current_user'/)\n\n      described_class.before_each do |endpoint|\n        allow(endpoint).to receive(:current_user).and_return('Bob')\n      end\n\n      get '/'\n      expect(last_response.body).to eq('Bob')\n\n      described_class.before_each(nil)\n      expect { get '/' }.to raise_error(NameError, /undefined local variable or method [`']current_user'/)\n    end\n\n    it 'is able to stack helper' do\n      subject.get('/') do\n        authenticate_user!\n        current_user\n      end\n      expect { get '/' }.to raise_error(NoMethodError, /undefined method [`']authenticate_user!' for/)\n\n      described_class.before_each do |endpoint|\n        allow(endpoint).to receive(:current_user).and_return('Bob')\n      end\n\n      described_class.before_each do |endpoint|\n        allow(endpoint).to receive(:authenticate_user!).and_return(true)\n      end\n\n      get '/'\n      expect(last_response.body).to eq('Bob')\n\n      described_class.before_each(nil)\n      expect { get '/' }.to raise_error(NoMethodError, /undefined method [`']authenticate_user!' for/)\n    end\n  end\n\n  it 'sets itself in the env upon call' do\n    subject.get('/') { 'Hello world.' }\n    get '/'\n    expect(last_request.env[Grape::Env::API_ENDPOINT]).to be_a(described_class)\n  end\n\n  describe '#status' do\n    it 'is callable from within a block' do\n      subject.get('/home') do\n        status 206\n        'Hello'\n      end\n\n      get '/home'\n      expect(last_response.status).to eq(206)\n      expect(last_response.body).to eq('Hello')\n    end\n\n    it 'is set as default to 200 for get' do\n      memoized_status = nil\n      subject.get('/home') do\n        memoized_status = status\n        'Hello'\n      end\n\n      get '/home'\n      expect(last_response.status).to eq(200)\n      expect(memoized_status).to eq(200)\n      expect(last_response.body).to eq('Hello')\n    end\n\n    it 'is set as default to 201 for post' do\n      memoized_status = nil\n      subject.post('/home') do\n        memoized_status = status\n        'Hello'\n      end\n\n      post '/home'\n      expect(last_response.status).to eq(201)\n      expect(memoized_status).to eq(201)\n      expect(last_response.body).to eq('Hello')\n    end\n\n    context 'when rescue_from' do\n      subject { last_request.env[Grape::Env::API_ENDPOINT].status }\n\n      before do\n        post '/'\n      end\n\n      context 'when :all blockless' do\n        context 'when default_error_status is not set' do\n          let(:app) do\n            Class.new(Grape::API) do\n              rescue_from :all\n\n              post { raise StandardError }\n            end\n          end\n\n          it { is_expected.to eq(last_response.status) }\n        end\n\n        context 'when default_error_status is set' do\n          let(:app) do\n            Class.new(Grape::API) do\n              default_error_status 418\n              rescue_from :all\n\n              post { raise StandardError }\n            end\n          end\n\n          it { is_expected.to eq(last_response.status) }\n        end\n      end\n\n      context 'when :with' do\n        let(:app) do\n          Class.new(Grape::API) do\n            helpers do\n              def handle_argument_error\n                error!(\"I'm a teapot!\", 418)\n              end\n            end\n            rescue_from ArgumentError, with: :handle_argument_error\n\n            post { raise ArgumentError }\n          end\n        end\n\n        it { is_expected.to eq(last_response.status) }\n      end\n    end\n  end\n\n  describe '#header' do\n    it 'is callable from within a block' do\n      subject.get('/hey') do\n        header 'X-Awesome', 'true'\n        'Awesome'\n      end\n\n      get '/hey'\n      expect(last_response.headers['X-Awesome']).to eq('true')\n    end\n  end\n\n  describe '#headers' do\n    before do\n      subject.get('/headers') do\n        headers.to_json\n      end\n    end\n\n    let(:headers) do\n      Grape::Util::Header.new.tap do |h|\n        h['Cookie'] = ''\n        h['Host'] = 'example.org'\n      end\n    end\n\n    it 'includes request headers' do\n      get '/headers'\n      expect(JSON.parse(last_response.body)).to include(headers.to_h)\n    end\n\n    it 'includes additional request headers' do\n      get '/headers', nil, 'HTTP_X_GRAPE_CLIENT' => '1'\n      x_grape_client_header = 'x-grape-client'\n      expect(JSON.parse(last_response.body)[x_grape_client_header]).to eq('1')\n    end\n  end\n\n  describe '#cookies' do\n    it 'is callable from within a block' do\n      subject.get('/get/cookies') do\n        cookies['my-awesome-cookie1'] = 'is cool'\n        cookies['my-awesome-cookie2'] = {\n          value: 'is cool too',\n          domain: 'my.example.com',\n          path: '/',\n          secure: true\n        }\n        cookies[:cookie3] = 'symbol'\n        cookies['cookie4'] = 'secret code here'\n      end\n\n      get('/get/cookies')\n\n      expect(last_response.cookie_jar).to contain_exactly(\n        { 'name' => 'cookie3', 'value' => 'symbol' },\n        { 'name' => 'cookie4', 'value' => 'secret code here' },\n        { 'name' => 'my-awesome-cookie1', 'value' => 'is cool' },\n        { 'name' => 'my-awesome-cookie2', 'value' => 'is cool too', 'domain' => 'my.example.com', 'path' => '/', 'secure' => true }\n      )\n    end\n\n    it 'sets browser cookies and does not set response cookies' do\n      set_cookie %w[username=mrplum sandbox=true]\n      subject.get('/username') do\n        cookies[:username]\n      end\n\n      get '/username'\n      expect(last_response.body).to eq('mrplum')\n      expect(last_response.cookie_jar).to be_empty\n    end\n\n    it 'sets and update browser cookies' do\n      set_cookie %w[username=user sandbox=false]\n      subject.get('/username') do\n        cookies[:sandbox] = true if cookies[:sandbox] == 'false'\n        cookies[:username] += '_test'\n      end\n\n      get '/username'\n      expect(last_response.body).to eq('user_test')\n      expect(last_response.cookie_jar).to contain_exactly(\n        { 'name' => 'sandbox', 'value' => 'true' },\n        { 'name' => 'username', 'value' => 'user_test' }\n      )\n    end\n\n    it 'deletes cookie' do\n      set_cookie %w[delete_this_cookie=1 and_this=2]\n      subject.get('/test') do\n        sum = 0\n        cookies.each do |name, val|\n          sum += val.to_i\n          cookies.delete name\n        end\n        sum\n      end\n      get '/test'\n      expect(last_response.body).to eq('3')\n      expect(last_response.cookie_jar).to contain_exactly(\n        { 'name' => 'and_this', 'value' => '', 'max-age' => 0, 'expires' => Time.at(0) },\n        { 'name' => 'delete_this_cookie', 'value' => '', 'max-age' => 0, 'expires' => Time.at(0) }\n      )\n    end\n\n    it 'deletes cookies with path' do\n      set_cookie %w[delete_this_cookie=1 and_this=2]\n      subject.get('/test') do\n        sum = 0\n        cookies.each do |name, val|\n          sum += val.to_i\n          cookies.delete name, path: '/test'\n        end\n        sum\n      end\n      get '/test'\n      expect(last_response.body).to eq('3')\n      expect(last_response.cookie_jar).to contain_exactly(\n        { 'name' => 'and_this', 'path' => '/test', 'value' => '', 'max-age' => 0, 'expires' => Time.at(0) },\n        { 'name' => 'delete_this_cookie', 'path' => '/test', 'value' => '', 'max-age' => 0, 'expires' => Time.at(0) }\n      )\n    end\n  end\n\n  describe '#params' do\n    context 'default class' do\n      it 'is a ActiveSupport::HashWithIndifferentAccess' do\n        subject.get '/foo' do\n          params.class\n        end\n\n        get '/foo'\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('ActiveSupport::HashWithIndifferentAccess')\n      end\n    end\n\n    context 'sets a value to params' do\n      it 'params' do\n        subject.params do\n          requires :a, type: String\n        end\n        subject.get '/foo' do\n          params[:a] = 'bar'\n        end\n\n        get '/foo', a: 'foo'\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('bar')\n      end\n    end\n  end\n\n  describe '#params' do\n    it 'is available to the caller' do\n      subject.get('/hey') do\n        params[:howdy]\n      end\n\n      get '/hey?howdy=hey'\n      expect(last_response.body).to eq('hey')\n    end\n\n    it 'parses from path segments' do\n      subject.get('/hey/:id') do\n        params[:id]\n      end\n\n      get '/hey/12'\n      expect(last_response.body).to eq('12')\n    end\n\n    it 'deeply converts nested params' do\n      subject.get '/location' do\n        params[:location][:city]\n      end\n      get '/location?location[city]=Dallas'\n      expect(last_response.body).to eq('Dallas')\n    end\n\n    context 'with special requirements' do\n      it 'parses email param with provided requirements for params' do\n        subject.get('/:person_email', requirements: { person_email: /.*/ }) do\n          params[:person_email]\n        end\n\n        get '/someone@example.com'\n        expect(last_response.body).to eq('someone@example.com')\n\n        get 'someone@example.com.pl'\n        expect(last_response.body).to eq('someone@example.com.pl')\n      end\n\n      it 'parses many params with provided regexps' do\n        subject.get('/:person_email/test/:number', requirements: { person_email: /someone@(.*).com/, number: /[0-9]/ }) do\n          params[:person_email] << params[:number]\n        end\n\n        get '/someone@example.com/test/1'\n        expect(last_response.body).to eq('someone@example.com1')\n\n        get '/someone@testing.wrong/test/1'\n        expect(last_response.status).to eq(404)\n\n        get 'someone@test.com/test/wrong_number'\n        expect(last_response.status).to eq(404)\n\n        get 'someone@test.com/wrong_middle/1'\n        expect(last_response.status).to eq(404)\n      end\n\n      context 'namespace requirements' do\n        before do\n          subject.namespace :outer, requirements: { person_email: /abc@(.*).com/ } do\n            get('/:person_email') do\n              params[:person_email]\n            end\n\n            namespace :inner, requirements: { number: /[0-9]/, person_email: /someone@(.*).com/ } do\n              get '/:person_email/test/:number' do\n                params[:person_email] << params[:number]\n              end\n            end\n          end\n        end\n\n        it 'parse email param with provided requirements for params' do\n          get '/outer/abc@example.com'\n          expect(last_response.body).to eq('abc@example.com')\n        end\n\n        it \"overrides outer namespace's requirements\" do\n          get '/outer/inner/someone@testing.wrong/test/1'\n          expect(last_response.status).to eq(404)\n\n          get '/outer/inner/someone@testing.com/test/1'\n          expect(last_response.status).to eq(200)\n          expect(last_response.body).to eq('someone@testing.com1')\n        end\n      end\n    end\n\n    context 'from body parameters' do\n      before do\n        subject.post '/request_body' do\n          params[:user]\n        end\n        subject.put '/request_body' do\n          params[:user]\n        end\n      end\n\n      it 'converts JSON bodies to params' do\n        post '/request_body', Grape::Json.dump(user: 'Bobby T.'), 'CONTENT_TYPE' => 'application/json'\n        expect(last_response.body).to eq('Bobby T.')\n      end\n\n      it 'does not convert empty JSON bodies to params' do\n        put '/request_body', '', 'CONTENT_TYPE' => 'application/json'\n        expect(last_response.body).to eq('')\n      end\n\n      if Object.const_defined? :MultiXml\n        it 'converts XML bodies to params' do\n          post '/request_body', '<user>Bobby T.</user>', 'CONTENT_TYPE' => 'application/xml'\n          expect(last_response.body).to eq('Bobby T.')\n        end\n\n        it 'converts XML bodies to params' do\n          put '/request_body', '<user>Bobby T.</user>', 'CONTENT_TYPE' => 'application/xml'\n          expect(last_response.body).to eq('Bobby T.')\n        end\n      else\n        let(:body) { '<user>Bobby T.</user>' }\n\n        it 'converts XML bodies to params' do\n          post '/request_body', body, 'CONTENT_TYPE' => 'application/xml'\n          expect(last_response.body).to eq(Grape::Xml.parse(body)['user'].to_s)\n        end\n\n        it 'converts XML bodies to params' do\n          put '/request_body', body, 'CONTENT_TYPE' => 'application/xml'\n          expect(last_response.body).to eq(Grape::Xml.parse(body)['user'].to_s)\n        end\n      end\n\n      it 'does not include parameters not defined by the body' do\n        subject.post '/omitted_params' do\n          error! 400, 'expected nil' if params[:version]\n          params[:user]\n        end\n        post '/omitted_params', Grape::Json.dump(user: 'Bob'), 'CONTENT_TYPE' => 'application/json'\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq('Bob')\n      end\n\n      # Rack swallowed this error until v2.2.0\n      it 'returns a 400 if given an invalid multipart body', if: Gem::Version.new(Rack.release) >= Gem::Version.new('2.2.0') do\n        subject.params do\n          requires :file, type: Rack::Multipart::UploadedFile\n        end\n        subject.post '/upload' do\n          params[:file][:filename]\n        end\n        post '/upload', { file: '' }, 'CONTENT_TYPE' => 'multipart/form-data; boundary=foobar'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('file is invalid')\n      end\n    end\n\n    context 'when the limit on multipart files is exceeded' do\n      around do |example|\n        limit = Rack::Utils.multipart_part_limit\n        Rack::Utils.multipart_part_limit = 1\n        example.run\n        Rack::Utils.multipart_part_limit = limit\n      end\n\n      it 'returns a 413 if given too many multipart files' do\n        subject.params do\n          requires :file, type: Rack::Multipart::UploadedFile\n        end\n        subject.post '/upload' do\n          params[:file][:filename]\n        end\n        post '/upload', { file: Rack::Test::UploadedFile.new(__FILE__, 'text/plain'), extra: Rack::Test::UploadedFile.new(__FILE__, 'text/plain') }\n        expect(last_response.status).to eq(413)\n        expect(last_response.body).to eq(\"the number of uploaded files exceeded the system's configured limit (1)\")\n      end\n    end\n\n    it 'responds with a 415 for an unsupported content-type' do\n      subject.format :json\n      # subject.content_type :json, \"application/json\"\n      subject.put '/request_body' do\n        params[:user]\n      end\n      put '/request_body', '<user>Bobby T.</user>', 'CONTENT_TYPE' => 'application/xml'\n      expect(last_response.status).to eq(415)\n      expect(last_response.body).to eq('{\"error\":\"The provided content-type \\'application/xml\\' is not supported.\"}')\n    end\n\n    it 'does not accept text/plain in JSON format if application/json is specified as content type' do\n      subject.format :json\n      subject.default_format :json\n      subject.put '/request_body' do\n        params[:user]\n      end\n      put '/request_body', Grape::Json.dump(user: 'Bob'), 'CONTENT_TYPE' => 'text/plain'\n\n      expect(last_response.status).to eq(415)\n      expect(last_response.body).to eq('{\"error\":\"The provided content-type \\'text/plain\\' is not supported.\"}')\n    end\n\n    context 'content type with params' do\n      before do\n        subject.format :json\n        subject.content_type :json, 'application/json; charset=utf-8'\n\n        subject.post do\n          params[:data]\n        end\n        post '/', Grape::Json.dump(data: { some: 'payload' }), 'CONTENT_TYPE' => 'application/json'\n      end\n\n      it 'does not response with 406 for same type without params' do\n        expect(last_response.status).not_to be 406\n      end\n\n      it 'responses with given content type in headers' do\n        expect(last_response.content_type).to eq 'application/json; charset=utf-8'\n      end\n    end\n\n    context 'precedence' do\n      before do\n        subject.format :json\n        subject.namespace '/:id' do\n          get do\n            {\n              params: params[:id]\n            }\n          end\n          post do\n            {\n              params: params[:id]\n            }\n          end\n          put do\n            {\n              params: params[:id]\n            }\n          end\n        end\n      end\n\n      it 'route string params have higher precedence than body params' do\n        post '/123', { id: 456 }.to_json\n        expect(JSON.parse(last_response.body)['params']).to eq '123'\n        put '/123', { id: 456 }.to_json\n        expect(JSON.parse(last_response.body)['params']).to eq '123'\n      end\n\n      it 'route string params have higher precedence than URL params' do\n        get '/123?id=456'\n        expect(JSON.parse(last_response.body)['params']).to eq '123'\n        post '/123?id=456'\n        expect(JSON.parse(last_response.body)['params']).to eq '123'\n      end\n    end\n\n    context 'sets a value to params' do\n      it 'params' do\n        subject.params do\n          requires :a, type: String\n        end\n        subject.get '/foo' do\n          params[:a] = 'bar'\n        end\n\n        get '/foo', a: 'foo'\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('bar')\n      end\n    end\n  end\n\n  describe '#error!' do\n    it 'accepts a message' do\n      subject.get('/hey') do\n        error! 'This is not valid.'\n        'This is valid.'\n      end\n\n      get '/hey'\n      expect(last_response.status).to eq(500)\n      expect(last_response.body).to eq('This is not valid.')\n    end\n\n    it 'accepts a code' do\n      subject.desc 'patate' do\n        http_codes [[401, 'Unauthorized']]\n      end\n      subject.get('/hey') do\n        error! 'Unauthorized.', 401\n      end\n\n      get '/hey'\n      expect(last_response.status).to eq(401)\n      expect(last_response.body).to eq('Unauthorized.')\n    end\n\n    it 'accepts an object and render it in format' do\n      subject.get '/hey' do\n        error!({ 'dude' => 'rad' }, 403)\n      end\n\n      get '/hey.json'\n      expect(last_response.status).to eq(403)\n      expect(last_response.body).to eq('{\"dude\":\"rad\"}')\n    end\n\n    it 'accepts a frozen object' do\n      subject.get '/hey' do\n        error!({ 'dude' => 'rad' }.freeze, 403)\n      end\n\n      get '/hey.json'\n      expect(last_response.status).to eq(403)\n      expect(last_response.body).to eq('{\"dude\":\"rad\"}')\n    end\n\n    it 'can specifiy headers' do\n      subject.get '/hey' do\n        error!({ 'dude' => 'rad' }, 403, 'X-Custom' => 'value')\n      end\n\n      get '/hey.json'\n      expect(last_response.status).to eq(403)\n      expect(last_response.headers['X-Custom']).to eq('value')\n    end\n\n    it 'merges additional headers with headers set before call' do\n      subject.before do\n        header 'X-Before-Test', 'before-sample'\n      end\n\n      subject.get '/hey' do\n        header 'X-Test', 'test-sample'\n        error!({ 'dude' => 'rad' }, 403, 'X-Error' => 'error')\n      end\n\n      get '/hey.json'\n      expect(last_response.headers['X-Before-Test']).to eq('before-sample')\n      expect(last_response.headers['X-Test']).to eq('test-sample')\n      expect(last_response.headers['X-Error']).to eq('error')\n    end\n\n    it 'does not merges additional headers with headers set after call' do\n      subject.after do\n        header 'X-After-Test', 'after-sample'\n      end\n\n      subject.get '/hey' do\n        error!({ 'dude' => 'rad' }, 403, 'X-Error' => 'error')\n      end\n\n      get '/hey.json'\n      expect(last_response.headers['X-Error']).to eq('error')\n      expect(last_response.headers['X-After-Test']).to be_nil\n    end\n\n    it 'sets the status code for the endpoint' do\n      memoized_endpoint = nil\n\n      subject.get '/hey' do\n        memoized_endpoint = self\n        error!({ 'dude' => 'rad' }, 403, 'X-Custom' => 'value')\n      end\n\n      get '/hey.json'\n\n      expect(memoized_endpoint.status).to eq(403)\n    end\n  end\n\n  describe '#redirect' do\n    it 'redirects to a url with status 302' do\n      subject.get('/hey') do\n        redirect '/ha'\n      end\n      get '/hey'\n      expect(last_response.status).to eq 302\n      expect(last_response.location).to eq '/ha'\n      expect(last_response.body).to eq 'This resource has been moved temporarily to /ha.'\n    end\n\n    it 'has status code 303 if it is not get request and it is http 1.1' do\n      subject.post('/hey') do\n        redirect '/ha'\n      end\n      post '/hey', {}, 'HTTP_VERSION' => 'HTTP/1.1', 'SERVER_PROTOCOL' => 'HTTP/1.1'\n      expect(last_response.status).to eq 303\n      expect(last_response.location).to eq '/ha'\n      expect(last_response.body).to eq 'An alternate resource is located at /ha.'\n    end\n\n    it 'support permanent redirect' do\n      subject.get('/hey') do\n        redirect '/ha', permanent: true\n      end\n      get '/hey'\n      expect(last_response.status).to eq 301\n      expect(last_response.location).to eq '/ha'\n      expect(last_response.body).to eq 'This resource has been moved permanently to /ha.'\n    end\n\n    it 'allows for an optional redirect body override' do\n      subject.get('/hey') do\n        redirect '/ha', body: 'test body'\n      end\n      get '/hey'\n      expect(last_response.body).to eq 'test body'\n    end\n  end\n\n  describe 'NameError' do\n    context 'when referencing an undefined local variable or method' do\n      it 'raises NameError but stripping the internals of the Grape::Endpoint class and including the API route' do\n        subject.get('/hey') { undefined_helper }\n        expect { get '/hey' }.to raise_error(NameError, /^undefined local variable or method ['`]undefined_helper' for/)\n      end\n    end\n  end\n\n  it 'does not persist params between calls' do\n    subject.post('/new') do\n      params[:text]\n    end\n\n    post '/new', text: 'abc'\n    expect(last_response.body).to eq('abc')\n\n    post '/new', text: 'def'\n    expect(last_response.body).to eq('def')\n  end\n\n  it 'resets all instance variables (except block) between calls' do\n    subject.helpers do\n      def memoized\n        @memoized ||= params[:howdy]\n      end\n    end\n\n    subject.get('/hello') do\n      memoized\n    end\n\n    get '/hello?howdy=hey'\n    expect(last_response.body).to eq('hey')\n    get '/hello?howdy=yo'\n    expect(last_response.body).to eq('yo')\n  end\n\n  context 'when calling return' do\n    it 'does not raise a LocalJumpError' do\n      subject.get('/home') do\n        return 'Hello'\n      end\n\n      get '/home'\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('Hello')\n    end\n  end\n\n  context 'filters' do\n    describe 'before filters' do\n      it 'runs the before filter if set' do\n        subject.before { env['before_test'] = 'OK' }\n        subject.get('/before_test') { env['before_test'] }\n\n        get '/before_test'\n        expect(last_response.body).to eq('OK')\n      end\n    end\n\n    describe 'after filters' do\n      it 'overrides the response body if it sets it' do\n        subject.after { body 'after' }\n        subject.get('/after_test') { 'during' }\n        get '/after_test'\n        expect(last_response.body).to eq('after')\n      end\n\n      it 'does not override the response body with its return' do\n        subject.after { 'after' }\n        subject.get('/after_test') { 'body' }\n        get '/after_test'\n        expect(last_response.body).to eq('body')\n      end\n    end\n\n    it 'allows adding to response with present' do\n      subject.format :json\n      subject.before { present :before, 'before' }\n      subject.before_validation { present :before_validation, 'before_validation' }\n      subject.after_validation { present :after_validation, 'after_validation' }\n      subject.after { present :after, 'after' }\n      subject.get :all_filters do\n        present :endpoint, 'endpoint'\n      end\n\n      get '/all_filters'\n      json = JSON.parse(last_response.body)\n      expect(json.keys).to match_array %w[before before_validation after_validation endpoint after]\n    end\n\n    context 'when terminating the response with error!' do\n      it 'breaks normal call chain' do\n        called = []\n        subject.before { called << 'before' }\n        subject.before_validation { called << 'before_validation' }\n        subject.after_validation { error! :oops, 500 }\n        subject.after { called << 'after' }\n        subject.get :error_filters do\n          called << 'endpoint'\n          ''\n        end\n\n        get '/error_filters'\n        expect(last_response.status).to be 500\n        expect(called).to match_array %w[before before_validation]\n      end\n\n      it 'allows prior and parent filters of same type to run' do\n        called = []\n        subject.before { called << 'parent' }\n        subject.namespace :parent do\n          before { called << 'prior' }\n\n          before { error! :oops, 500 }\n\n          before { called << 'subsequent' }\n\n          get :hello do\n            called << :endpoint\n            'Hello!'\n          end\n        end\n\n        get '/parent/hello'\n        expect(last_response.status).to be 500\n        expect(called).to match_array %w[parent prior]\n      end\n    end\n  end\n\n  context 'anchoring' do\n    describe 'delete 204' do\n      it 'allows for the anchoring option with a delete method' do\n        subject.delete('/example', anchor: true)\n        delete '/example/and/some/more'\n        expect(last_response).to be_not_found\n      end\n\n      it 'anchors paths by default for the delete method' do\n        subject.delete '/example'\n        delete '/example/and/some/more'\n        expect(last_response).to be_not_found\n      end\n\n      it 'responds to /example/and/some/more for the non-anchored delete method' do\n        subject.delete '/example', anchor: false\n        delete '/example/and/some/more'\n        expect(last_response).to be_no_content\n        expect(last_response.body).to be_empty\n      end\n    end\n\n    describe 'delete 200, with response body' do\n      it 'responds to /example/and/some/more for the non-anchored delete method' do\n        subject.delete('/example', anchor: false) do\n          status 200\n          body 'deleted'\n        end\n        delete '/example/and/some/more'\n        expect(last_response).to be_successful\n        expect(last_response.body).not_to be_empty\n      end\n    end\n\n    describe 'delete 200, with a return value (no explicit body)' do\n      it 'responds to /example delete method' do\n        subject.delete(:example) { 'deleted' }\n        delete '/example'\n        expect(last_response.status).to be 200\n        expect(last_response.body).not_to be_empty\n      end\n    end\n\n    describe 'delete 204, with nil has return value (no explicit body)' do\n      it 'responds to /example delete method' do\n        subject.delete(:example) { nil }\n        delete '/example'\n        expect(last_response.status).to be 204\n        expect(last_response.body).to be_empty\n      end\n    end\n\n    describe 'delete 204, with empty array has return value (no explicit body)' do\n      it 'responds to /example delete method' do\n        subject.delete(:example) { '' }\n        delete '/example'\n        expect(last_response.status).to be 204\n        expect(last_response.body).to be_empty\n      end\n    end\n\n    describe 'all other' do\n      %w[post get head put options patch].each do |verb|\n        it \"allows for the anchoring option with a #{verb.upcase} method\" do\n          subject.__send__(verb, '/example', anchor: true) do\n            verb\n          end\n          __send__(verb, '/example/and/some/more')\n          expect(last_response.status).to be 404\n        end\n\n        it \"anchors paths by default for the #{verb.upcase} method\" do\n          subject.__send__(verb, '/example') do\n            verb\n          end\n          __send__(verb, '/example/and/some/more')\n          expect(last_response.status).to be 404\n        end\n\n        it \"responds to /example/and/some/more for the non-anchored #{verb.upcase} method\" do\n          subject.__send__(verb, '/example', anchor: false) do\n            verb\n          end\n          __send__(verb, '/example/and/some/more')\n          expect(last_response.status).to eql verb == 'post' ? 201 : 200\n          expect(last_response.body).to eql verb == 'head' ? '' : verb\n        end\n      end\n    end\n  end\n\n  context 'request' do\n    it 'is set to the url requested' do\n      subject.get('/url') do\n        request.url\n      end\n      get '/url'\n      expect(last_response.body).to eq('http://example.org/url')\n    end\n\n    ['v1', :v1].each do |version|\n      it \"includes version #{version}\" do\n        subject.version version, using: :path\n        subject.get('/url') do\n          request.url\n        end\n        get \"/#{version}/url\"\n        expect(last_response.body).to eq(\"http://example.org/#{version}/url\")\n      end\n    end\n    it 'includes prefix' do\n      subject.version 'v1', using: :path\n      subject.prefix 'api'\n      subject.get('/url') do\n        request.url\n      end\n      get '/api/v1/url'\n      expect(last_response.body).to eq('http://example.org/api/v1/url')\n    end\n  end\n\n  context 'version headers' do\n    before do\n      # NOTE: a 404 is returned instead of the 406 if cascade: false is not set.\n      subject.version 'v1', using: :header, vendor: 'ohanapi', cascade: false\n      subject.get '/test' do\n        'Hello!'\n      end\n    end\n\n    it 'result in a 406 response if they are invalid' do\n      get '/test', {}, 'HTTP_ACCEPT' => 'application/vnd.ohanapi.v1+json'\n      expect(last_response.status).to eq(406)\n    end\n\n    it 'result in a 406 response if they cannot be parsed' do\n      get '/test', {}, 'HTTP_ACCEPT' => 'application/vnd.ohanapi.v1+json; version=1'\n      expect(last_response.status).to eq(406)\n    end\n  end\n\n  context 'binary' do\n    before do\n      subject.get do\n        stream FileStreamer.new(__FILE__)\n      end\n    end\n\n    it 'suports stream objects in response' do\n      get '/'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq File.read(__FILE__)\n    end\n  end\n\n  context 'validation errors' do\n    before do\n      subject.before do\n        header['Access-Control-Allow-Origin'] = '*'\n      end\n      subject.params do\n        requires :id, type: String\n      end\n      subject.get do\n        'should not get here'\n      end\n    end\n\n    it 'returns the errors, and passes headers' do\n      get '/'\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq 'id is missing'\n      expect(last_response.headers['Access-Control-Allow-Origin']).to eq('*')\n    end\n  end\n\n  context 'instrumentation' do\n    before do\n      subject.before do\n        # Placeholder\n      end\n      subject.get do\n        'hello'\n      end\n\n      @events = []\n      @subscriber = ActiveSupport::Notifications.subscribe(/grape/) do |*args|\n        @events << ActiveSupport::Notifications::Event.new(*args)\n      end\n    end\n\n    after do\n      ActiveSupport::Notifications.unsubscribe(@subscriber)\n    end\n\n    it 'notifies AS::N' do\n      get '/'\n\n      # In order that the events finalized (time each block ended)\n      expect(@events).to contain_exactly(\n        have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),\n                                                                       filters: a_collection_containing_exactly(an_instance_of(Proc)),\n                                                                       type: :before }),\n        have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),\n                                                                       filters: [],\n                                                                       type: :before_validation }),\n        have_attributes(name: 'endpoint_run_validators.grape', payload: { endpoint: a_kind_of(described_class),\n                                                                          validators: [],\n                                                                          request: a_kind_of(Grape::Request) }),\n        have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),\n                                                                       filters: [],\n                                                                       type: :after_validation }),\n        have_attributes(name: 'endpoint_render.grape',      payload: { endpoint: a_kind_of(described_class) }),\n        have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),\n                                                                       filters: [],\n                                                                       type: :after }),\n        have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),\n                                                                       filters: [],\n                                                                       type: :finally }),\n        have_attributes(name: 'endpoint_run.grape', payload: { endpoint: a_kind_of(described_class),\n                                                               env: an_instance_of(Hash) }),\n        have_attributes(name: 'format_response.grape', payload: { env: an_instance_of(Hash),\n                                                                  formatter: a_kind_of(Module) })\n      )\n\n      # In order that events were initialized\n      expect(@events.sort_by(&:time)).to contain_exactly(\n        have_attributes(name: 'endpoint_run.grape', payload: { endpoint: a_kind_of(described_class),\n                                                               env: an_instance_of(Hash) }),\n        have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),\n                                                                       filters: a_collection_containing_exactly(an_instance_of(Proc)),\n                                                                       type: :before }),\n        have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),\n                                                                       filters: [],\n                                                                       type: :before_validation }),\n        have_attributes(name: 'endpoint_run_validators.grape', payload: { endpoint: a_kind_of(described_class),\n                                                                          validators: [],\n                                                                          request: a_kind_of(Grape::Request) }),\n        have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),\n                                                                       filters: [],\n                                                                       type: :after_validation }),\n        have_attributes(name: 'endpoint_render.grape',      payload: { endpoint: a_kind_of(described_class) }),\n        have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),\n                                                                       filters: [],\n                                                                       type: :after }),\n        have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(described_class),\n                                                                       filters: [],\n                                                                       type: :finally }),\n        have_attributes(name: 'format_response.grape', payload: { env: an_instance_of(Hash),\n                                                                  formatter: a_kind_of(Module) })\n      )\n    end\n  end\n\n  describe '#inspect' do\n    subject { described_class.new(settings, **options).inspect }\n\n    let(:options) do\n      {\n        method: :path,\n        path: '/path',\n        app: {},\n        route_options: { anchor: false },\n        forward_match: true,\n        for: Class.new\n      }\n    end\n    let(:settings) { Grape::Util::InheritableSetting.new }\n\n    it 'does not raise an error' do\n      expect { subject }.not_to raise_error\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/exceptions/base_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Exceptions::Base do\n  describe '#to_s' do\n    subject { described_class.new(message: message).to_s }\n\n    let(:message) { 'a_message' }\n\n    it { is_expected.to eq(message) }\n  end\n\n  describe '#message' do\n    subject { described_class.new(message: message).message }\n\n    let(:message) { 'a_message' }\n\n    it { is_expected.to eq(message) }\n  end\n\n  describe '#compose_message' do\n    subject { described_class.new.__send__(:compose_message, key, **attributes) }\n\n    let(:key) { :invalid_formatter }\n    let(:attributes) { { klass: String, to_format: 'xml' } }\n\n    after { I18n.reload! }\n\n    context 'when I18n enforces available locales' do\n      context 'when the fallback locale is available' do\n        around do |example|\n          I18n.available_locales = %i[de en]\n          I18n.with_locale(:de) { example.run }\n        ensure\n          I18n.available_locales = %i[en]\n        end\n\n        it 'returns the translated message' do\n          expect(subject).to eq('cannot convert String to xml')\n        end\n      end\n\n      context 'when the fallback locale is not available' do\n        around do |example|\n          I18n.available_locales = %i[de jp]\n          I18n.with_locale(:de) do\n            example.run\n          ensure\n            I18n.available_locales = %i[en]\n          end\n        end\n\n        it 'returns the scoped translation key as a string' do\n          expect(subject).to eq(\"grape.errors.messages.#{key}\")\n        end\n      end\n    end\n\n    context 'when I18n does not enforce available locales' do\n      around do |example|\n        I18n.enforce_available_locales = false\n        example.run\n      ensure\n        I18n.enforce_available_locales = true\n      end\n\n      context 'when the fallback locale is available' do\n        around do |example|\n          I18n.available_locales = %i[de en]\n          I18n.with_locale(:de) { example.run }\n        ensure\n          I18n.available_locales = %i[en]\n        end\n\n        it 'returns the translated message' do\n          expect(subject).to eq('cannot convert String to xml')\n        end\n      end\n\n      context 'when the fallback locale is not available' do\n        around do |example|\n          I18n.available_locales = %i[de jp]\n          I18n.with_locale(:de) { example.run }\n        ensure\n          I18n.available_locales = %i[en]\n        end\n\n        it 'returns the translated message' do\n          expect(subject).to eq('cannot convert String to xml')\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/exceptions/body_parse_errors_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Exceptions::ValidationErrors do\n  context 'api with rescue_from :all handler' do\n    subject { Class.new(Grape::API) }\n\n    before do\n      subject.rescue_from :all do |_e|\n        error! 'message was processed', 400\n      end\n      subject.params do\n        requires :beer\n      end\n      subject.post '/beer' do\n        'beer received'\n      end\n    end\n\n    def app\n      subject\n    end\n\n    context 'with content_type json' do\n      it 'can recover from failed body parsing' do\n        post '/beer', 'test', 'CONTENT_TYPE' => 'application/json'\n        expect(last_response.status).to eq 400\n        expect(last_response.body).to eq('message was processed')\n      end\n    end\n\n    context 'with content_type xml' do\n      it 'can recover from failed body parsing' do\n        post '/beer', 'test', 'CONTENT_TYPE' => 'application/xml'\n        expect(last_response.status).to eq 400\n        expect(last_response.body).to eq('message was processed')\n      end\n    end\n\n    context 'with content_type text' do\n      it 'can recover from failed body parsing' do\n        post '/beer', 'test', 'CONTENT_TYPE' => 'text/plain'\n        expect(last_response.status).to eq 400\n        expect(last_response.body).to eq('message was processed')\n      end\n    end\n\n    context 'with no specific content_type' do\n      it 'can recover from failed body parsing' do\n        post '/beer', 'test', {}\n        expect(last_response.status).to eq 400\n        expect(last_response.body).to eq('message was processed')\n      end\n    end\n  end\n\n  context 'api with rescue_from :grape_exceptions handler' do\n    subject { Class.new(Grape::API) }\n\n    before do\n      subject.rescue_from :all do |_e|\n        error! 'message was processed', 400\n      end\n      subject.rescue_from :grape_exceptions\n\n      subject.params do\n        requires :beer\n      end\n      subject.post '/beer' do\n        'beer received'\n      end\n    end\n\n    def app\n      subject\n    end\n\n    context 'with content_type json' do\n      it 'returns body parsing error message' do\n        post '/beer', 'test', 'CONTENT_TYPE' => 'application/json'\n        expect(last_response.status).to eq 400\n        expect(last_response.body).to include 'message body does not match declared format'\n      end\n    end\n\n    context 'with content_type xml' do\n      it 'returns body parsing error message' do\n        post '/beer', 'test', 'CONTENT_TYPE' => 'application/xml'\n        expect(last_response.status).to eq 400\n        expect(last_response.body).to include 'message body does not match declared format'\n      end\n    end\n  end\n\n  context 'api with rescue_from :grape_exceptions handler with block' do\n    subject { Class.new(Grape::API) }\n\n    before do\n      subject.rescue_from :grape_exceptions do |e|\n        error! \"Custom Error Contents, Original Message: #{e.message}\", 400\n      end\n\n      subject.params do\n        requires :beer\n      end\n\n      subject.post '/beer' do\n        'beer received'\n      end\n    end\n\n    def app\n      subject\n    end\n\n    context 'with content_type json' do\n      it 'returns body parsing error message' do\n        post '/beer', 'test', 'CONTENT_TYPE' => 'application/json'\n        expect(last_response.status).to eq 400\n        expect(last_response.body).to include 'message body does not match declared format'\n        expect(last_response.body).to include 'Custom Error Contents, Original Message'\n      end\n    end\n\n    context 'with content_type xml' do\n      it 'returns body parsing error message' do\n        post '/beer', 'test', 'CONTENT_TYPE' => 'application/xml'\n        expect(last_response.status).to eq 400\n        expect(last_response.body).to include 'message body does not match declared format'\n        expect(last_response.body).to include 'Custom Error Contents, Original Message'\n      end\n    end\n  end\n\n  context 'api without a rescue handler' do\n    subject { Class.new(Grape::API) }\n\n    before do\n      subject.params do\n        requires :beer\n      end\n      subject.post '/beer' do\n        'beer received'\n      end\n    end\n\n    def app\n      subject\n    end\n\n    context 'and with content_type json' do\n      it 'can recover from failed body parsing' do\n        post '/beer', 'test', 'CONTENT_TYPE' => 'application/json'\n        expect(last_response.status).to eq 400\n        expect(last_response.body).to include('message body does not match declared format')\n        expect(last_response.body).to include('application/json')\n      end\n    end\n\n    context 'with content_type xml' do\n      it 'can recover from failed body parsing' do\n        post '/beer', 'test', 'CONTENT_TYPE' => 'application/xml'\n        expect(last_response.status).to eq 400\n        expect(last_response.body).to include('message body does not match declared format')\n        expect(last_response.body).to include('application/xml')\n      end\n    end\n\n    context 'with content_type text' do\n      it 'can recover from failed body parsing' do\n        post '/beer', 'test', 'CONTENT_TYPE' => 'text/plain'\n        expect(last_response.status).to eq 400\n        expect(last_response.body).to eq('beer is missing')\n      end\n    end\n\n    context 'and with no specific content_type' do\n      it 'can recover from failed body parsing' do\n        post '/beer', 'test', {}\n        expect(last_response.status).to eq 400\n        # plain response with text/html\n        expect(last_response.body).to eq('beer is missing')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/exceptions/invalid_accept_header_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Exceptions::InvalidAcceptHeader do\n  shared_examples_for 'a valid request' do\n    it 'does return with status 200' do\n      expect(last_response.status).to eq 200\n    end\n\n    it 'does return the expected result' do\n      expect(last_response.body).to eq('beer received')\n    end\n  end\n\n  shared_examples_for 'a cascaded request' do\n    it 'does not find a matching route' do\n      expect(last_response.status).to eq 404\n    end\n  end\n\n  shared_examples_for 'a not-cascaded request' do\n    it 'does not include the X-Cascade=pass header' do\n      expect(last_response.headers).not_to have_key('X-Cascade')\n    end\n\n    it 'does not accept the request' do\n      expect(last_response.status).to eq 406\n    end\n  end\n\n  shared_examples_for 'a rescued request' do\n    it 'does not include the X-Cascade=pass header' do\n      expect(last_response.headers['X-Cascade']).to be_nil\n    end\n\n    it 'does show rescue handler processing' do\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq('message was processed')\n    end\n  end\n\n  context 'API with cascade=false and rescue_from :all handler' do\n    subject { Class.new(Grape::API) }\n\n    before do\n      subject.version 'v99', using: :header, vendor: 'vendorname', format: :json, cascade: false\n      subject.rescue_from :all do |e|\n        error! 'message was processed', 400, e[:headers]\n      end\n      subject.get '/beer' do\n        'beer received'\n      end\n    end\n\n    def app\n      subject\n    end\n\n    context 'that received a request with correct vendor and version' do\n      before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v99' }\n\n      it_behaves_like 'a valid request'\n    end\n\n    context 'that receives' do\n      context 'an invalid vendor in the request' do\n        before do\n          get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.invalidvendor-v99',\n                           'CONTENT_TYPE' => 'application/json'\n        end\n\n        it_behaves_like 'a rescued request'\n      end\n    end\n  end\n\n  context 'API with cascade=false and without a rescue handler' do\n    subject { Class.new(Grape::API) }\n\n    before do\n      subject.version 'v99', using: :header, vendor: 'vendorname', format: :json, cascade: false\n      subject.get '/beer' do\n        'beer received'\n      end\n    end\n\n    def app\n      subject\n    end\n\n    context 'that received a request with correct vendor and version' do\n      before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v99' }\n\n      it_behaves_like 'a valid request'\n    end\n\n    context 'that receives' do\n      context 'an invalid version in the request' do\n        before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v77' }\n\n        it_behaves_like 'a not-cascaded request'\n      end\n\n      context 'an invalid vendor in the request' do\n        before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.invalidvendor-v99' }\n\n        it_behaves_like 'a not-cascaded request'\n      end\n    end\n  end\n\n  context 'API with cascade=false and with rescue_from :all handler and http_codes' do\n    subject { Class.new(Grape::API) }\n\n    before do\n      subject.version 'v99', using: :header, vendor: 'vendorname', format: :json, cascade: false\n      subject.rescue_from :all do |e|\n        error! 'message was processed', 400, e[:headers]\n      end\n      subject.desc 'Get beer' do\n        failure [[400, 'Bad Request'], [401, 'Unauthorized'], [403, 'Forbidden'],\n                 [404, 'Resource not found'], [406, 'API vendor or version not found'],\n                 [500, 'Internal processing error']]\n      end\n      subject.get '/beer' do\n        'beer received'\n      end\n    end\n\n    def app\n      subject\n    end\n\n    context 'that received a request with correct vendor and version' do\n      before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v99' }\n\n      it_behaves_like 'a valid request'\n    end\n\n    context 'that receives' do\n      context 'an invalid vendor in the request' do\n        before do\n          get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.invalidvendor-v99',\n                           'CONTENT_TYPE' => 'application/json'\n        end\n\n        it_behaves_like 'a rescued request'\n      end\n    end\n  end\n\n  context 'API with cascade=false, http_codes but without a rescue handler' do\n    subject { Class.new(Grape::API) }\n\n    before do\n      subject.version 'v99', using: :header, vendor: 'vendorname', format: :json, cascade: false\n      subject.desc 'Get beer' do\n        failure [[400, 'Bad Request'], [401, 'Unauthorized'], [403, 'Forbidden'],\n                 [404, 'Resource not found'], [406, 'API vendor or version not found'],\n                 [500, 'Internal processing error']]\n      end\n      subject.get '/beer' do\n        'beer received'\n      end\n    end\n\n    def app\n      subject\n    end\n\n    context 'that received a request with correct vendor and version' do\n      before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v99' }\n\n      it_behaves_like 'a valid request'\n    end\n\n    context 'that receives' do\n      context 'an invalid version in the request' do\n        before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v77' }\n\n        it_behaves_like 'a not-cascaded request'\n      end\n\n      context 'an invalid vendor in the request' do\n        before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.invalidvendor-v99' }\n\n        it_behaves_like 'a not-cascaded request'\n      end\n    end\n  end\n\n  context 'API with cascade=true and rescue_from :all handler' do\n    subject { Class.new(Grape::API) }\n\n    before do\n      subject.version 'v99', using: :header, vendor: 'vendorname', format: :json, cascade: true\n      subject.rescue_from :all do |e|\n        error! 'message was processed', 400, e[:headers]\n      end\n      subject.get '/beer' do\n        'beer received'\n      end\n    end\n\n    def app\n      subject\n    end\n\n    context 'that received a request with correct vendor and version' do\n      before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v99' }\n\n      it_behaves_like 'a valid request'\n    end\n\n    context 'that receives' do\n      context 'an invalid version in the request' do\n        before do\n          get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v77',\n                           'CONTENT_TYPE' => 'application/json'\n        end\n\n        it_behaves_like 'a cascaded request'\n      end\n\n      context 'an invalid vendor in the request' do\n        before do\n          get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.invalidvendor-v99',\n                           'CONTENT_TYPE' => 'application/json'\n        end\n\n        it_behaves_like 'a cascaded request'\n      end\n    end\n  end\n\n  context 'API with cascade=true and without a rescue handler' do\n    subject { Class.new(Grape::API) }\n\n    before do\n      subject.version 'v99', using: :header, vendor: 'vendorname', format: :json, cascade: true\n      subject.get '/beer' do\n        'beer received'\n      end\n    end\n\n    def app\n      subject\n    end\n\n    context 'that received a request with correct vendor and version' do\n      before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v99' }\n\n      it_behaves_like 'a valid request'\n    end\n\n    context 'that receives' do\n      context 'an invalid version in the request' do\n        before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v77' }\n\n        it_behaves_like 'a cascaded request'\n      end\n\n      context 'an invalid vendor in the request' do\n        before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.invalidvendor-v99' }\n\n        it_behaves_like 'a cascaded request'\n      end\n    end\n  end\n\n  context 'API with cascade=true and with rescue_from :all handler and http_codes' do\n    subject { Class.new(Grape::API) }\n\n    before do\n      subject.version 'v99', using: :header, vendor: 'vendorname', format: :json, cascade: true\n      subject.rescue_from :all do |e|\n        error! 'message was processed', 400, e[:headers]\n      end\n      subject.desc 'Get beer' do\n        failure [[400, 'Bad Request'], [401, 'Unauthorized'], [403, 'Forbidden'],\n                 [404, 'Resource not found'], [406, 'API vendor or version not found'],\n                 [500, 'Internal processing error']]\n      end\n      subject.get '/beer' do\n        'beer received'\n      end\n    end\n\n    def app\n      subject\n    end\n\n    context 'that received a request with correct vendor and version' do\n      before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v99' }\n\n      it_behaves_like 'a valid request'\n    end\n\n    context 'that receives' do\n      context 'an invalid version in the request' do\n        before do\n          get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v77',\n                           'CONTENT_TYPE' => 'application/json'\n        end\n\n        it_behaves_like 'a cascaded request'\n      end\n\n      context 'an invalid vendor in the request' do\n        before do\n          get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.invalidvendor-v99',\n                           'CONTENT_TYPE' => 'application/json'\n        end\n\n        it_behaves_like 'a cascaded request'\n      end\n    end\n  end\n\n  context 'API with cascade=true, http_codes but without a rescue handler' do\n    subject { Class.new(Grape::API) }\n\n    before do\n      subject.version 'v99', using: :header, vendor: 'vendorname', format: :json, cascade: true\n      subject.desc 'Get beer' do\n        failure [[400, 'Bad Request'], [401, 'Unauthorized'], [403, 'Forbidden'],\n                 [404, 'Resource not found'], [406, 'API vendor or version not found'],\n                 [500, 'Internal processing error']]\n      end\n      subject.get '/beer' do\n        'beer received'\n      end\n    end\n\n    def app\n      subject\n    end\n\n    context 'that received a request with correct vendor and version' do\n      before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v99' }\n\n      it_behaves_like 'a valid request'\n    end\n\n    context 'that receives' do\n      context 'an invalid version in the request' do\n        before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.vendorname-v77' }\n\n        it_behaves_like 'a cascaded request'\n      end\n\n      context 'an invalid vendor in the request' do\n        before { get '/beer', {}, 'HTTP_ACCEPT' => 'application/vnd.invalidvendor-v99' }\n\n        it_behaves_like 'a cascaded request'\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/exceptions/invalid_formatter_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Exceptions::InvalidFormatter do\n  describe '#message' do\n    let(:error) do\n      described_class.new(String, 'xml')\n    end\n\n    it 'contains the problem in the message' do\n      expect(error.message).to include(\n        'cannot convert String to xml'\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/exceptions/invalid_response_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Exceptions::InvalidResponse do\n  describe '#message' do\n    let(:error) { described_class.new }\n\n    it 'contains the problem in the message' do\n      expect(error.message).to include('Invalid response')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/exceptions/invalid_versioner_option_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Exceptions::InvalidVersionerOption do\n  describe '#message' do\n    let(:error) do\n      described_class.new('headers')\n    end\n\n    it 'contains the problem in the message' do\n      expect(error.message).to include(\n        'unknown :using for versioner: headers'\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/exceptions/missing_group_type_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Grape::Exceptions::MissingGroupType do\n  describe '#message' do\n    subject { described_class.new.message }\n\n    it { is_expected.to include 'group type is required' }\n  end\nend\n"
  },
  {
    "path": "spec/grape/exceptions/missing_mime_type_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Exceptions::MissingMimeType do\n  describe '#message' do\n    let(:error) do\n      described_class.new('new_json')\n    end\n\n    it 'contains the problem in the message' do\n      expect(error.message).to include 'missing mime type for new_json'\n    end\n\n    it 'contains the resolution in the message' do\n      expect(error.message).to include \"or add your own with content_type :new_json, 'application/new_json' \"\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/exceptions/unknown_validator_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Exceptions::UnknownValidator do\n  describe '#message' do\n    let(:error) do\n      described_class.new('gt_10')\n    end\n\n    it 'contains the problem in the message' do\n      expect(error.message).to include(\n        'unknown validator: gt_10'\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/exceptions/unsupported_group_type_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Grape::Exceptions::UnsupportedGroupType do\n  subject { described_class.new }\n\n  describe '#message' do\n    subject { described_class.new.message }\n\n    it { is_expected.to include 'group type must be Array, Hash, JSON or Array[JSON]' }\n  end\nend\n"
  },
  {
    "path": "spec/grape/exceptions/validation_errors_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Exceptions::ValidationErrors do\n  let(:validation_message) { 'FooBar is invalid' }\n  let(:validation_error) { instance_double Grape::Exceptions::Validation, params: [validation_message], message: '' }\n\n  context 'initialize' do\n    subject do\n      described_class.new(errors: [validation_error], headers: headers)\n    end\n\n    let(:headers) do\n      {\n        'A-Header-Key' => 'A-Header-Value'\n      }\n    end\n\n    it 'assigns headers through base class' do\n      expect(subject.headers).to eq(headers)\n    end\n  end\n\n  context 'message' do\n    context 'is not repeated' do\n      subject(:message) { error.message.split(',').map(&:strip) }\n\n      let(:error) do\n        described_class.new(errors: [validation_error, validation_error])\n      end\n\n      it { expect(message).to include validation_message }\n      it { expect(message.size).to eq 1 }\n    end\n  end\n\n  describe '#full_messages' do\n    context 'with errors' do\n      subject { described_class.new(errors: [validation_error_1, validation_error_2]).full_messages }\n\n      let(:validation_error_1) { Grape::Exceptions::Validation.new(params: ['id'], message: :presence) }\n      let(:validation_error_2) { Grape::Exceptions::Validation.new(params: ['name'], message: :presence) }\n\n      it 'returns an array with each errors full message' do\n        expect(subject).to contain_exactly('id is missing', 'name is missing')\n      end\n    end\n\n    context 'when attributes is an array of symbols' do\n      subject { described_class.new(errors: [validation_error]).full_messages }\n\n      let(:validation_error) { Grape::Exceptions::Validation.new(params: [:admin_field], message: 'Can not set admin-only field') }\n\n      it 'returns an array with an error full message' do\n        expect(subject.first).to eq('admin_field Can not set admin-only field')\n      end\n    end\n  end\n\n  context 'api' do\n    subject { Class.new(Grape::API) }\n\n    def app\n      subject\n    end\n\n    it 'can return structured json with separate fields' do\n      subject.format :json\n      subject.rescue_from described_class do |e|\n        error!(e, 400)\n      end\n      subject.params do\n        optional :beer\n        optional :wine\n        optional :juice\n        exactly_one_of :beer, :wine, :juice\n      end\n      subject.get '/exactly_one_of' do\n        'exactly_one_of works!'\n      end\n      get '/exactly_one_of', beer: 'string', wine: 'anotherstring'\n      expect(last_response).to be_bad_request\n      expect(JSON.parse(last_response.body)).to eq(\n        [\n          'params' => %w[beer wine],\n          'messages' => ['are mutually exclusive']\n        ]\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/exceptions/validation_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Exceptions::Validation do\n  it 'fails when params are missing' do\n    expect { described_class.new(message: 'presence') }.to raise_error(ArgumentError, /missing keyword:.+?params/)\n  end\n\n  context 'when message is a Symbol' do\n    subject(:error) { described_class.new(params: ['id'], message: :presence) }\n\n    it 'stores message_key' do\n      expect(error.message_key).to eq(:presence)\n    end\n\n    it 'translates the message' do\n      expect(error.message).to eq('is missing')\n    end\n  end\n\n  context 'when message is a Hash' do\n    subject(:error) { described_class.new(params: ['id'], message: { key: :between, min: 2, max: 10 }) }\n\n    before do\n      I18n.backend.store_translations(:en, grape: { errors: { messages: { between: 'must be between %<min>s and %<max>s' } } })\n    end\n\n    after { I18n.reload! }\n\n    it 'stores the :key entry as message_key' do\n      expect(error.message_key).to eq(:between)\n    end\n\n    it 'translates the message with interpolation params' do\n      expect(error.message).to eq('must be between 2 and 10')\n    end\n  end\n\n  context 'when message is a Proc' do\n    it 'calls the proc to produce the message' do\n      expect(described_class.new(params: ['id'], message: -> { 'computed' }).message).to eq('computed')\n    end\n  end\n\n  context 'when message is a String' do\n    subject(:error) { described_class.new(params: ['id'], message: 'raw message') }\n\n    it 'does not store the message_key' do\n      expect(error.message_key).to be_nil\n    end\n\n    it 'returns the string as-is' do\n      expect(error.message).to eq('raw message')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/integration/global_namespace_function_spec.rb",
    "content": "# frozen_string_literal: true\n\n# see https://github.com/ruby-grape/grape/issues/1348\n\ndef namespace\n  raise\nend\n\ndescribe Grape::API do\n  subject do\n    Class.new(Grape::API) do\n      format :json\n      get do\n        { ok: true }\n      end\n    end\n  end\n\n  def app\n    subject\n  end\n\n  context 'with a global namespace function' do\n    it 'works' do\n      get '/'\n      expect(last_response.status).to eq 200\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/integration/rack_sendfile_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Rack::Sendfile do\n  subject do\n    content_object = file_object\n    app = Class.new(Grape::API) do\n      use Rack::Sendfile, 'X-Accel-Redirect'\n      format :json\n      get do\n        if content_object.is_a?(String)\n          sendfile content_object\n        else\n          stream content_object\n        end\n      end\n    end\n\n    options = {\n      method: Rack::GET,\n      'HTTP_X_ACCEL_MAPPING' => '/accel/mapping/=/replaced/'\n    }\n    env = Rack::MockRequest.env_for('/', options)\n    app.call(env)\n  end\n\n  context 'when calling sendfile' do\n    let(:file_object) do\n      '/accel/mapping/some/path'\n    end\n\n    it 'contains Sendfile headers' do\n      headers = subject[1]\n      expect(headers).to include('X-Accel-Redirect')\n    end\n  end\n\n  context 'when streaming non file content' do\n    let(:file_object) do\n      double(:file_object, each: nil)\n    end\n\n    it 'not contains Sendfile headers' do\n      headers = subject[1]\n      expect(headers).not_to include('X-Accel-Redirect')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/integration/rack_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Rack do\n  describe 'from a Tempfile' do\n    subject { last_response.body }\n\n    let(:app) do\n      Class.new(Grape::API) do\n        format :json\n\n        params do\n          requires :file, type: File\n        end\n\n        post do\n          params[:file].then do |file|\n            {\n              filename: file[:filename],\n              type: file[:type],\n              content: file[:tempfile].read\n            }\n          end\n        end\n      end\n    end\n\n    let(:response_body) do\n      {\n        filename: File.basename(tempfile.path),\n        type: 'text/plain',\n        content: 'rubbish'\n      }.to_json\n    end\n\n    let(:tempfile) do\n      Tempfile.new.tap do |t|\n        t.write('rubbish')\n        t.rewind\n      end\n    end\n\n    before do\n      post '/', file: Rack::Test::UploadedFile.new(tempfile.path, 'text/plain')\n    end\n\n    it 'correctly populates params from a Tempfile' do\n      expect(subject).to eq(response_body)\n    ensure\n      tempfile.close!\n    end\n  end\n\n  context 'when the app is mounted' do\n    let(:ping_mount) do\n      Class.new(Grape::API) do\n        get 'ping'\n      end\n    end\n\n    let(:app) do\n      app_to_mount = ping_mount\n      Class.new(Grape::API) do\n        namespace 'namespace' do\n          mount app_to_mount\n        end\n      end\n    end\n\n    it 'finds the app on the namespace' do\n      get '/namespace/ping'\n      expect(last_response).to be_successful\n    end\n  end\n\n  # https://github.com/ruby-grape/grape/issues/2576\n  describe 'when an api is mounted' do\n    let(:api) do\n      Class.new(Grape::API) do\n        format :json\n        version 'v1', using: :path\n\n        resource :system do\n          get :ping do\n            { message: 'pong' }\n          end\n        end\n      end\n    end\n\n    let(:parent_api) do\n      api_to_mount = api\n      Class.new(Grape::API) do\n        format :json\n        namespace '/api' do\n          mount api_to_mount\n        end\n      end\n    end\n\n    it 'is not polluted with the parent namespace' do\n      env = Rack::MockRequest.env_for('/v1/api/system/ping', method: 'GET')\n      response = Rack::MockResponse[*parent_api.call(env)]\n\n      expect(response.status).to eq(200)\n      parsed_body = JSON.parse(response.body)\n      expect(parsed_body['message']).to eq('pong')\n    end\n\n    it 'can call the api' do\n      env = Rack::MockRequest.env_for('/v1/system/ping', method: 'GET')\n      response = Rack::MockResponse[*api.call(env)]\n\n      expect(response.status).to eq(200)\n      parsed_body = JSON.parse(response.body)\n      expect(parsed_body['message']).to eq('pong')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/loading_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::API do\n  subject do\n    context = self\n\n    Class.new(Grape::API) do\n      format :json\n      mount context.combined_api => '/'\n    end\n  end\n\n  let(:jobs_api) do\n    Class.new(Grape::API) do\n      namespace :one do\n        namespace :two do\n          namespace :three do\n            get :one do\n            end\n\n            get :two do\n            end\n          end\n        end\n      end\n    end\n  end\n\n  let(:combined_api) do\n    context = self\n\n    Class.new(Grape::API) do\n      version :v1, using: :accept_version_header, cascade: true\n      mount context.jobs_api\n    end\n  end\n\n  def app\n    subject\n  end\n\n  it 'execute first request in reasonable time' do\n    started = Time.now\n    get '/mount1/nested/test_method'\n    expect(Time.now - started).to be < 5\n  end\nend\n"
  },
  {
    "path": "spec/grape/middleware/auth/base_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Middleware::Auth::Base do\n  subject do\n    Class.new(Grape::API) do\n      http_basic realm: 'my_realm' do |user, password|\n        user && password && user == password\n      end\n      get '/authorized' do\n        'DONE'\n      end\n    end\n  end\n\n  let(:app) { subject }\n\n  it 'authenticates if given valid creds' do\n    get '/authorized', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin', 'admin')\n    expect(last_response).to be_successful\n    expect(last_response.body).to eq('DONE')\n  end\n\n  it 'throws a 401 is wrong auth is given' do\n    get '/authorized', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin', 'wrong')\n    expect(last_response).to be_unauthorized\n  end\nend\n"
  },
  {
    "path": "spec/grape/middleware/auth/dsl_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Middleware::Auth::DSL do\n  subject { Class.new(Grape::API) }\n\n  let(:block) { -> {} }\n  let(:settings) do\n    {\n      opaque: 'secret',\n      proc: block,\n      realm: 'API Authorization',\n      type: :http_digest\n    }\n  end\n\n  describe '.auth' do\n    it 'sets auth parameters' do\n      expect(subject.base_instance).to receive(:use).with(Grape::Middleware::Auth::Base, settings)\n\n      subject.auth :http_digest, realm: settings[:realm], opaque: settings[:opaque], &settings[:proc]\n      expect(subject.auth).to eq(settings)\n    end\n\n    it 'can be called multiple times' do\n      expect(subject.base_instance).to receive(:use).with(Grape::Middleware::Auth::Base, settings)\n      expect(subject.base_instance).to receive(:use).with(Grape::Middleware::Auth::Base, settings.merge(realm: 'super_secret'))\n\n      subject.auth :http_digest, realm: settings[:realm], opaque: settings[:opaque], &settings[:proc]\n      first_settings = subject.auth\n\n      subject.auth :http_digest, realm: 'super_secret', opaque: settings[:opaque], &settings[:proc]\n\n      expect(subject.auth).to eq(settings.merge(realm: 'super_secret'))\n      expect(subject.auth.object_id).not_to eq(first_settings.object_id)\n    end\n  end\n\n  describe '.http_basic' do\n    it 'sets auth parameters' do\n      subject.http_basic realm: 'my_realm', &settings[:proc]\n      expect(subject.auth).to eq(realm: 'my_realm', type: :http_basic, proc: block)\n    end\n  end\n\n  describe '.http_digest' do\n    context 'when realm is a hash' do\n      it 'sets auth parameters' do\n        subject.http_digest realm: { realm: 'my_realm', opaque: 'my_opaque' }, &settings[:proc]\n        expect(subject.auth).to eq(realm: { realm: 'my_realm', opaque: 'my_opaque' }, type: :http_digest, proc: block)\n      end\n    end\n\n    context 'when realm is not hash' do\n      it 'sets auth parameters' do\n        subject.http_digest realm: 'my_realm', opaque: 'my_opaque', &settings[:proc]\n        expect(subject.auth).to eq(realm: 'my_realm', type: :http_digest, proc: block, opaque: 'my_opaque')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/middleware/auth/strategies_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Middleware::Auth::Strategies do\n  describe 'Basic Auth' do\n    let(:app) do\n      proc = ->(u, p) { u && p && u == p }\n      Rack::Builder.app do\n        use Grape::Middleware::Error\n        use(Grape::Middleware::Auth::Base, type: :http_basic, proc: proc)\n        run ->(_env) { [200, {}, ['Hello there.']] }\n      end\n    end\n\n    it 'throws a 401 if no auth is given' do\n      get '/whatever'\n      expect(last_response).to be_unauthorized\n    end\n\n    it 'authenticates if given valid creds' do\n      get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin', 'admin')\n      expect(last_response).to be_successful\n      expect(last_response.body).to eq('Hello there.')\n    end\n\n    it 'throws a 401 is wrong auth is given' do\n      get '/whatever', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('admin', 'wrong')\n      expect(last_response).to be_unauthorized\n    end\n  end\n\n  describe 'Unknown Auth' do\n    context 'when type is not register' do\n      let(:app) do\n        Class.new(Grape::API) do\n          use Grape::Middleware::Auth::Base, type: :unknown\n          get('/whatever') { 'Hello there.' }\n        end\n      end\n\n      it 'throws a 401' do\n        expect { get '/whatever' }.to raise_error(Grape::Exceptions::UnknownAuthStrategy, 'unknown auth strategy: unknown')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/middleware/base_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Middleware::Base do\n  subject { described_class.new(blank_app) }\n\n  let(:blank_app) { ->(_) { [200, {}, 'Hi there.'] } }\n\n  before do\n    # Keep it one object for testing.\n    allow(subject).to receive(:dup).and_return(subject)\n  end\n\n  it 'has the app as an accessor' do\n    expect(subject.app).to eq(blank_app)\n  end\n\n  it 'calls through to the app' do\n    expect(subject.call({})).to eq([200, {}, 'Hi there.'])\n  end\n\n  context 'callbacks' do\n    after { subject.call!({}) }\n\n    it 'calls #before' do\n      expect(subject).to receive(:before)\n    end\n\n    it 'calls #after' do\n      expect(subject).to receive(:after)\n    end\n  end\n\n  context 'callbacks on error' do\n    let(:blank_app) { ->(_) { raise StandardError } }\n\n    it 'calls #after' do\n      expect(subject).to receive(:after)\n      expect { subject.call({}) }.to raise_error(StandardError)\n    end\n  end\n\n  context 'after callback' do\n    before do\n      allow(subject).to receive(:after).and_return([200, {}, 'Hello from after callback'])\n    end\n\n    it 'overwrites application response' do\n      expect(subject.call!({}).last).to eq('Hello from after callback')\n    end\n  end\n\n  context 'after callback with errors' do\n    it 'does not overwrite the application response' do\n      expect(subject.call({})).to eq([200, {}, 'Hi there.'])\n    end\n\n    context 'with patched warnings' do\n      before do\n        @warnings = warnings = []\n        allow(subject).to receive(:warn) { |m| warnings << m }\n        allow(subject).to receive(:after).and_raise(StandardError)\n      end\n\n      it 'does show a warning' do\n        expect { subject.call({}) }.to raise_error(StandardError)\n        expect(@warnings).not_to be_empty\n      end\n    end\n  end\n\n  it 'is able to access the response' do\n    subject.call({})\n    expect(subject.response).to be_a(Rack::Response)\n  end\n\n  describe '#response' do\n    subject do\n      described_class.new(response)\n    end\n\n    before { subject.call({}) }\n\n    context 'when Array' do\n      let(:rack_response) { Rack::Response.new('test', 204, abc: 1) }\n      let(:response) { ->(_) { [204, { abc: 1 }, 'test'] } }\n\n      it 'status' do\n        expect(subject.response.status).to eq(204)\n      end\n\n      it 'body' do\n        expect(subject.response.body).to eq(['test'])\n      end\n\n      it 'header' do\n        expect(subject.response.headers).to have_key(:abc)\n      end\n\n      it 'returns the memoized Rack::Response instance' do\n        allow(Rack::Response).to receive(:new).and_return(rack_response)\n        expect(subject.response).to eq(rack_response)\n      end\n    end\n\n    context 'when Rack::Response' do\n      let(:rack_response) { Rack::Response.new('test', 204, abc: 1) }\n      let(:response) { ->(_) { rack_response } }\n\n      it 'status' do\n        expect(subject.response.status).to eq(204)\n      end\n\n      it 'body' do\n        expect(subject.response.body).to eq(['test'])\n      end\n\n      it 'header' do\n        expect(subject.response.headers).to have_key(:abc)\n      end\n\n      it 'returns the memoized Rack::Response instance' do\n        expect(subject.response).to eq(rack_response)\n      end\n    end\n  end\n\n  describe '#context' do\n    subject { described_class.new(blank_app) }\n\n    it 'allows access to response context' do\n      subject.call(Grape::Env::API_ENDPOINT => { header: 'some header' })\n      expect(subject.context).to eq(header: 'some header')\n    end\n  end\n\n  context 'options' do\n    it 'persists options passed at initialization' do\n      expect(described_class.new(blank_app, abc: true).options[:abc]).to be true\n    end\n\n    context 'defaults' do\n      let(:example_ware) do\n        Class.new(Grape::Middleware::Base) do\n          const_set(:DEFAULT_OPTIONS, { monkey: true }.freeze)\n        end\n      end\n\n      it 'persists the default options' do\n        expect(example_ware.new(blank_app).options[:monkey]).to be true\n      end\n\n      it 'overrides default options when provided' do\n        expect(example_ware.new(blank_app, monkey: false).options[:monkey]).to be false\n      end\n    end\n  end\n\n  context 'header' do\n    let(:example_ware) do\n      Class.new(Grape::Middleware::Base) do\n        def before\n          header 'X-Test-Before', 'Hi'\n        end\n\n        def after\n          header 'X-Test-After', 'Bye'\n          nil\n        end\n      end\n    end\n\n    let(:app) do\n      context = self\n\n      Rack::Builder.app do\n        use context.example_ware\n        run ->(_) { [200, {}, ['Yeah']] }\n      end\n    end\n\n    it 'is able to set a header' do\n      get '/'\n      expect(last_response.headers['X-Test-Before']).to eq('Hi')\n      expect(last_response.headers['X-Test-After']).to eq('Bye')\n    end\n  end\n\n  context 'header overwrite' do\n    let(:example_ware) do\n      Class.new(Grape::Middleware::Base) do\n        def before\n          header 'X-Test-Overwriting', 'Hi'\n        end\n\n        def after\n          header 'X-Test-Overwriting', 'Bye'\n          nil\n        end\n      end\n    end\n    let(:api) do\n      Class.new(Grape::API) do\n        get('/') do\n          header 'X-Test-Overwriting', 'Yeah'\n          'Hello'\n        end\n      end\n    end\n\n    let(:app) do\n      context = self\n\n      Rack::Builder.app do\n        use context.example_ware\n        run context.api.new\n      end\n    end\n\n    it 'overwrites header by after headers' do\n      get '/'\n      expect(last_response.headers['X-Test-Overwriting']).to eq('Bye')\n    end\n  end\n\n  describe 'query_params' do\n    let(:dummy_middleware) do\n      Class.new(Grape::Middleware::Base) do\n        def before\n          query_params\n        end\n      end\n    end\n\n    let(:app) do\n      context = self\n      Rack::Builder.app do\n        use context.dummy_middleware\n        run ->(_) { [200, {}, ['Yeah']] }\n      end\n    end\n\n    context 'when query params are conflicting' do\n      it 'raises an ConflictingTypes error' do\n        expect { get '/?x[y]=1&x[y]z=2' }.to raise_error(Grape::Exceptions::ConflictingTypes)\n      end\n    end\n\n    context 'when query params is over the specified limit' do\n      let(:query_params) { \"foo#{'[a]' * Rack::Utils.param_depth_limit}=bar\" }\n\n      it 'raises an ConflictingTypes error' do\n        expect { get \"/?foo#{'[a]' * Rack::Utils.param_depth_limit}=bar\" }.to raise_error(Grape::Exceptions::TooDeepParameters)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/middleware/error_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Middleware::Error do\n  let(:error_entity) do\n    Class.new(Grape::Entity) do\n      expose :code\n      expose :static\n\n      def static\n        'static text'\n      end\n    end\n  end\n  let(:err_app) do\n    Class.new do\n      class << self\n        attr_accessor :error, :format\n\n        def call(_env)\n          throw :error, error\n        end\n      end\n    end\n  end\n  let(:options) { { default_message: 'Aww, hamburgers.' } }\n\n  let(:app) do\n    opts = options\n    context = self\n    Rack::Builder.app do\n      use Spec::Support::EndpointFaker\n      use Grape::Middleware::Error, **opts # rubocop:disable RSpec/DescribedClass\n      run context.err_app\n    end\n  end\n\n  it 'sets the status code appropriately' do\n    err_app.error = { status: 410 }\n    get '/'\n    expect(last_response.status).to eq(410)\n  end\n\n  it 'sets the status code based on the rack util status code symbol' do\n    err_app.error = { status: :gone }\n    get '/'\n    expect(last_response.status).to eq(410)\n  end\n\n  it 'sets the error message appropriately' do\n    err_app.error = { message: 'Awesome stuff.' }\n    get '/'\n    expect(last_response.body).to eq('Awesome stuff.')\n  end\n\n  it 'defaults to a 500 status' do\n    err_app.error = {}\n    get '/'\n    expect(last_response).to be_server_error\n  end\n\n  it 'has a default message' do\n    err_app.error = {}\n    get '/'\n    expect(last_response.body).to eq('Aww, hamburgers.')\n  end\n\n  context 'with http code' do\n    let(:options) {  { default_message: 'Aww, hamburgers.' } }\n\n    it 'adds the status code if wanted' do\n      err_app.error = { message: { code: 200 } }\n      get '/'\n\n      expect(last_response.body).to eq({ code: 200 }.to_json)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/middleware/exception_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Middleware::Error do\n  let(:exception_app) do\n    Class.new do\n      class << self\n        def call(_env)\n          raise 'rain!'\n        end\n      end\n    end\n  end\n\n  let(:other_exception_app) do\n    Class.new do\n      class << self\n        def call(_env)\n          raise NotImplementedError, 'snow!'\n        end\n      end\n    end\n  end\n\n  let(:custom_error_app) do\n    custom_error = Class.new(Grape::Exceptions::Base)\n\n    Class.new do\n      define_singleton_method(:call) do |_env|\n        raise custom_error.new(status: 400, message: 'failed validation')\n      end\n    end\n  end\n\n  let(:error_hash_app) do\n    Class.new do\n      class << self\n        def error!(message, status)\n          throw :error, message: { error: message, detail: 'missing widget' }, status: status\n        end\n\n        def call(_env)\n          error!('rain!', 401)\n        end\n      end\n    end\n  end\n\n  let(:access_denied_app) do\n    Class.new do\n      class << self\n        def error!(message, status)\n          throw :error, message: message, status: status\n        end\n\n        def call(_env)\n          error!('Access Denied', 401)\n        end\n      end\n    end\n  end\n\n  let(:app) do\n    opts = options\n    app = running_app\n    Rack::Builder.app do\n      use Rack::Lint\n      use Spec::Support::EndpointFaker\n      if opts.any?\n        use Grape::Middleware::Error, **opts\n      else\n        use Grape::Middleware::Error\n      end\n      run app\n    end\n  end\n\n  context 'with defaults' do\n    let(:running_app) { exception_app }\n    let(:options) { {} }\n\n    it 'does not trap errors by default' do\n      expect { get '/' }.to raise_error(RuntimeError, 'rain!')\n    end\n  end\n\n  context 'with rescue_all' do\n    context 'StandardError exception' do\n      let(:running_app) { exception_app }\n      let(:options) { { rescue_all: true } }\n\n      it 'sets the message appropriately' do\n        get '/'\n        expect(last_response.body).to eq('rain!')\n      end\n\n      it 'defaults to a 500 status' do\n        get '/'\n        expect(last_response.status).to eq(500)\n      end\n    end\n\n    context 'Non-StandardError exception' do\n      let(:running_app) { other_exception_app }\n      let(:options) { { rescue_all: true } }\n\n      it 'does not trap errors other than StandardError' do\n        expect { get '/' }.to raise_error(NotImplementedError, 'snow!')\n      end\n    end\n  end\n\n  context 'Non-StandardError exception with a provided rescue handler' do\n    context 'default error response' do\n      let(:running_app) { other_exception_app }\n      let(:options) { { rescue_handlers: { NotImplementedError => nil } } }\n\n      it 'rescues the exception using the default handler' do\n        get '/'\n        expect(last_response.body).to eq('snow!')\n      end\n    end\n\n    context 'custom error response' do\n      let(:running_app) { other_exception_app }\n      let(:options) { { rescue_handlers: { NotImplementedError => -> { Rack::Response.new('rescued', 200, {}) } } } }\n\n      it 'rescues the exception using the provided handler' do\n        get '/'\n        expect(last_response.body).to eq('rescued')\n      end\n    end\n  end\n\n  context do\n    let(:running_app) { exception_app }\n    let(:options) { { rescue_all: true, default_status: 500 } }\n\n    it 'is possible to specify a different default status code' do\n      get '/'\n      expect(last_response.status).to eq(500)\n    end\n  end\n\n  context do\n    let(:running_app) { exception_app }\n    let(:options) { { rescue_all: true, format: :json } }\n\n    it 'is possible to return errors in json format' do\n      get '/'\n      expect(last_response.body).to eq('{\"error\":\"rain!\"}')\n    end\n  end\n\n  context do\n    let(:running_app) { error_hash_app }\n    let(:options) { { rescue_all: true, format: :json } }\n\n    it 'is possible to return hash errors in json format' do\n      get '/'\n      expect(['{\"error\":\"rain!\",\"detail\":\"missing widget\"}',\n              '{\"detail\":\"missing widget\",\"error\":\"rain!\"}']).to include(last_response.body)\n    end\n  end\n\n  context do\n    let(:running_app) { exception_app }\n    let(:options) { { rescue_all: true, format: :xml } }\n\n    it 'is possible to return errors in xml format' do\n      get '/'\n      expect(last_response.body).to eq(\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n<error>\\n  <message>rain!</message>\\n</error>\\n\")\n    end\n  end\n\n  context do\n    let(:running_app) { error_hash_app }\n    let(:options) { { rescue_all: true, format: :xml } }\n\n    it 'is possible to return hash errors in xml format' do\n      get '/'\n      expect([\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n<error>\\n  <detail>missing widget</detail>\\n  <error>rain!</error>\\n</error>\\n\",\n              \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n<error>\\n  <error>rain!</error>\\n  <detail>missing widget</detail>\\n</error>\\n\"]).to include(last_response.body)\n    end\n  end\n\n  context do\n    let(:running_app) { exception_app }\n    let(:options) do\n      {\n        rescue_all: true,\n        format: :custom,\n        error_formatters: {\n          custom: lambda do |message, _backtrace, _options, _env, _original_exception|\n            { custom_formatter: message }.inspect\n          end\n        }\n      }\n    end\n\n    it 'is possible to specify a custom formatter' do\n      get '/'\n      response = Rack::Utils.escape_html({ custom_formatter: 'rain!' }.inspect)\n      expect(last_response.body).to eq(response)\n    end\n  end\n\n  context do\n    let(:running_app) { access_denied_app }\n    let(:options) { {} }\n\n    it 'does not trap regular error! codes' do\n      get '/'\n      expect(last_response.status).to eq(401)\n    end\n  end\n\n  context do\n    let(:running_app) { custom_error_app }\n    let(:options) { { rescue_all: false } }\n\n    it 'responds to custom Grape exceptions appropriately' do\n      get '/'\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('failed validation')\n    end\n  end\n\n  context 'with rescue_options :backtrace and :exception set to true' do\n    let(:running_app) { exception_app }\n    let(:options) do\n      {\n        rescue_all: true,\n        format: :json,\n        rescue_options: { backtrace: true, original_exception: true }\n      }\n    end\n\n    it 'is possible to return the backtrace and the original exception in json format' do\n      get '/'\n      expect(last_response.body).to include('error', 'rain!', 'backtrace', 'original_exception', 'RuntimeError')\n    end\n  end\n\n  context do\n    let(:running_app) { exception_app }\n    let(:options) do\n      {\n        rescue_all: true,\n        format: :xml,\n        rescue_options: { backtrace: true, original_exception: true }\n      }\n    end\n\n    it 'is possible to return the backtrace and the original exception in xml format' do\n      get '/'\n      expect(last_response.body).to include('error', 'rain!', 'backtrace', 'original-exception', 'RuntimeError')\n    end\n  end\n\n  context do\n    let(:running_app) { exception_app }\n    let(:options) do\n      {\n        rescue_all: true,\n        format: :txt,\n        rescue_options: { backtrace: true, original_exception: true }\n      }\n    end\n\n    it 'is possible to return the backtrace and the original exception in txt format' do\n      get '/'\n      expect(last_response.body).to include('error', 'rain!', 'backtrace', 'original exception', 'RuntimeError')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/middleware/formatter_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Middleware::Formatter do\n  subject { described_class.new(app) }\n\n  before { allow(subject).to receive(:dup).and_return(subject) }\n\n  let(:body) { { 'foo' => 'bar' } }\n  let(:app) { ->(_env) { [200, {}, [body]] } }\n\n  context 'serialization' do\n    let(:body) { { 'abc' => 'def' } }\n    let(:env) do\n      { Rack::PATH_INFO => '/somewhere', 'HTTP_ACCEPT' => 'application/json' }\n    end\n\n    it 'looks at the bodies for possibly serializable data' do\n      r = Rack::MockResponse[*subject.call(env)]\n      expect(r.body).to eq(Grape::Json.dump(body))\n    end\n\n    context 'default format' do\n      let(:body) { ['foo'] }\n      let(:env) do\n        { Rack::PATH_INFO => '/somewhere', 'HTTP_ACCEPT' => '*/*' }\n      end\n\n      before do\n        subject.options[:default_format] = :json\n      end\n\n      it 'returns JSON' do\n        body.instance_eval do\n          def to_json(*_args)\n            '\"bar\"'\n          end\n        end\n        r = Rack::MockResponse[*subject.call(env)]\n        expect(r.body).to eq('\"bar\"')\n      end\n    end\n\n    context 'xml' do\n      let(:body) { +'string' }\n      let(:env) do\n        { Rack::PATH_INFO => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json' }\n      end\n\n      it 'calls #to_xml if the content type is xml' do\n        body.instance_eval do\n          def to_xml\n            '<bar/>'\n          end\n        end\n        r = Rack::MockResponse[*subject.call(env)]\n        expect(r.body).to eq('<bar/>')\n      end\n    end\n  end\n\n  context 'error handling' do\n    let(:formatter) { double(:formatter) }\n    let(:env) do\n      { Rack::PATH_INFO => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json' }\n    end\n\n    before do\n      allow(Grape::Formatter).to receive(:formatter_for) { formatter }\n    end\n\n    it 'rescues formatter-specific exceptions' do\n      allow(formatter).to receive(:call) { raise Grape::Exceptions::InvalidFormatter.new(String, 'xml') }\n\n      expect do\n        catch(:error) { subject.call(env) }\n      end.not_to raise_error\n    end\n\n    it 'does not rescue other exceptions' do\n      allow(formatter).to receive(:call) { raise StandardError }\n\n      expect do\n        catch(:error) { subject.call(Rack::PATH_INFO => '/somewhere.xml', 'HTTP_ACCEPT' => 'application/json') }\n      end.to raise_error(StandardError)\n    end\n  end\n\n  context 'detection' do\n    context 'when path contains invalid byte sequence' do\n      it 'does not raise an exception' do\n        expect { subject.call(Rack::PATH_INFO => \"/info.\\x80\") }.not_to raise_error\n      end\n    end\n\n    it 'uses the xml extension if one is provided' do\n      subject.call(Rack::PATH_INFO => '/info.xml')\n      expect(subject.env[Grape::Env::API_FORMAT]).to eq(:xml)\n    end\n\n    it 'uses the json extension if one is provided' do\n      subject.call(Rack::PATH_INFO => '/info.json')\n      expect(subject.env[Grape::Env::API_FORMAT]).to eq(:json)\n    end\n\n    it 'uses the format parameter if one is provided' do\n      subject.call(Rack::PATH_INFO => '/info', Rack::QUERY_STRING => 'format=json')\n      expect(subject.env[Grape::Env::API_FORMAT]).to eq(:json)\n    end\n\n    it 'uses the default format if none is provided' do\n      subject.call(Rack::PATH_INFO => '/info')\n      expect(subject.env[Grape::Env::API_FORMAT]).to eq(:txt)\n    end\n\n    it 'uses the requested format if provided in headers' do\n      subject.call(Rack::PATH_INFO => '/info', 'HTTP_ACCEPT' => 'application/json')\n      expect(subject.env[Grape::Env::API_FORMAT]).to eq(:json)\n    end\n\n    it 'uses the file extension format if provided before headers' do\n      subject.call(Rack::PATH_INFO => '/info.txt', 'HTTP_ACCEPT' => 'application/json')\n      expect(subject.env[Grape::Env::API_FORMAT]).to eq(:txt)\n    end\n  end\n\n  context 'accept header detection' do\n    context 'when header contains invalid byte sequence' do\n      it 'does not raise an exception' do\n        expect { subject.call(Rack::PATH_INFO => '/info', 'HTTP_ACCEPT' => \"Hello \\x80\") }.not_to raise_error\n      end\n    end\n\n    it 'detects from the Accept header' do\n      subject.call(Rack::PATH_INFO => '/info', 'HTTP_ACCEPT' => 'application/xml')\n      expect(subject.env[Grape::Env::API_FORMAT]).to eq(:xml)\n    end\n\n    it 'uses quality rankings to determine formats' do\n      subject.call(Rack::PATH_INFO => '/info', 'HTTP_ACCEPT' => 'application/json; q=0.3,application/xml; q=1.0')\n      expect(subject.env[Grape::Env::API_FORMAT]).to eq(:xml)\n\n      subject.call(Rack::PATH_INFO => '/info', 'HTTP_ACCEPT' => 'application/json; q=1.0,application/xml; q=0.3')\n      expect(subject.env[Grape::Env::API_FORMAT]).to eq(:json)\n    end\n\n    it 'handles quality rankings mixed with nothing' do\n      subject.call(Rack::PATH_INFO => '/info', 'HTTP_ACCEPT' => 'application/json,application/xml; q=1.0')\n      expect(subject.env[Grape::Env::API_FORMAT]).to eq(:xml)\n\n      subject.call(Rack::PATH_INFO => '/info', 'HTTP_ACCEPT' => 'application/xml; q=1.0,application/json')\n      expect(subject.env[Grape::Env::API_FORMAT]).to eq(:json)\n    end\n\n    it 'handles quality rankings that have a default 1.0 value' do\n      subject.call(Rack::PATH_INFO => '/info', 'HTTP_ACCEPT' => 'application/json,application/xml;q=0.5')\n      expect(subject.env[Grape::Env::API_FORMAT]).to eq(:json)\n      subject.call(Rack::PATH_INFO => '/info', 'HTTP_ACCEPT' => 'application/xml;q=0.5,application/json')\n      expect(subject.env[Grape::Env::API_FORMAT]).to eq(:json)\n    end\n\n    it 'parses headers with other attributes' do\n      subject.call(Rack::PATH_INFO => '/info', 'HTTP_ACCEPT' => 'application/json; abc=2.3; q=1.0,application/xml; q=0.7')\n      expect(subject.env[Grape::Env::API_FORMAT]).to eq(:json)\n    end\n\n    it 'ensures that a quality of 0 is less preferred than any other content type' do\n      subject.call(Rack::PATH_INFO => '/info', 'HTTP_ACCEPT' => 'application/json;q=0.0,application/xml')\n      expect(subject.env[Grape::Env::API_FORMAT]).to eq(:xml)\n      subject.call(Rack::PATH_INFO => '/info', 'HTTP_ACCEPT' => 'application/xml,application/json;q=0.0')\n      expect(subject.env[Grape::Env::API_FORMAT]).to eq(:xml)\n    end\n\n    context 'with custom vendored content types' do\n      context 'when registered' do\n        subject { described_class.new(app, content_types: { custom: 'application/vnd.test+json' }) }\n\n        it 'uses the custom type' do\n          subject.call(Rack::PATH_INFO => '/info', 'HTTP_ACCEPT' => 'application/vnd.test+json')\n          expect(subject.env[Grape::Env::API_FORMAT]).to eq(:custom)\n        end\n      end\n\n      context 'when unregistered' do\n        it 'returns the default content type text/plain' do\n          r = Rack::MockResponse[*subject.call(Rack::PATH_INFO => '/info', 'HTTP_ACCEPT' => 'application/vnd.test+json')]\n          expect(r.headers[Rack::CONTENT_TYPE]).to eq('text/plain')\n        end\n      end\n    end\n\n    it 'parses headers with symbols as hash keys' do\n      subject.call(Rack::PATH_INFO => '/info', 'HTTP_ACCEPT' => 'application/xml', system_time: '091293')\n      expect(subject.env[:system_time]).to eq('091293')\n    end\n  end\n\n  context 'content-type' do\n    it 'is set for json' do\n      _, headers, = subject.call(Rack::PATH_INFO => '/info.json')\n      expect(headers[Rack::CONTENT_TYPE]).to eq('application/json')\n    end\n\n    it 'is set for xml' do\n      _, headers, = subject.call(Rack::PATH_INFO => '/info.xml')\n      expect(headers[Rack::CONTENT_TYPE]).to eq('application/xml')\n    end\n\n    it 'is set for txt' do\n      _, headers, = subject.call(Rack::PATH_INFO => '/info.txt')\n      expect(headers[Rack::CONTENT_TYPE]).to eq('text/plain')\n    end\n\n    it 'is set for custom' do\n      s = described_class.new(app, content_types: { custom: 'application/x-custom' })\n      _, headers, = s.call(Rack::PATH_INFO => '/info.custom')\n      expect(headers[Rack::CONTENT_TYPE]).to eq('application/x-custom')\n    end\n\n    it 'is set for vendored with registered type' do\n      s = described_class.new(app, content_types: { custom: 'application/vnd.test+json' })\n      _, headers, = s.call(Rack::PATH_INFO => '/info', 'HTTP_ACCEPT' => 'application/vnd.test+json')\n      expect(headers[Rack::CONTENT_TYPE]).to eq('application/vnd.test+json')\n    end\n  end\n\n  context 'format' do\n    it 'uses custom formatter' do\n      s = described_class.new(app, content_types: { custom: \"don't care\" }, formatters: { custom: ->(_obj, _env) { 'CUSTOM FORMAT' } })\n      r = Rack::MockResponse[*s.call(Rack::PATH_INFO => '/info.custom')]\n      expect(r.body).to eq('CUSTOM FORMAT')\n    end\n\n    context 'default' do\n      let(:body) { ['blah'] }\n\n      it 'uses default json formatter' do\n        r = Rack::MockResponse[*subject.call(Rack::PATH_INFO => '/info.json')]\n        expect(r.body).to eq(Grape::Json.dump(body))\n      end\n    end\n\n    it 'uses custom json formatter' do\n      subject.options[:formatters] = { json: ->(_obj, _env) { 'CUSTOM JSON FORMAT' } }\n      r = Rack::MockResponse[*subject.call(Rack::PATH_INFO => '/info.json')]\n      expect(r.body).to eq('CUSTOM JSON FORMAT')\n    end\n  end\n\n  context 'no content responses' do\n    let(:no_content_response) { ->(status) { [status, {}, []] } }\n\n    statuses_without_body = if Gem::Version.new(Rack.release) >= Gem::Version.new('2.1.0')\n                              Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.keys\n                            else\n                              Rack::Utils::STATUS_WITH_NO_ENTITY_BODY\n                            end\n\n    statuses_without_body.each do |status|\n      it \"does not modify a #{status} response\" do\n        expected_response = no_content_response[status]\n        allow(app).to receive(:call).and_return(expected_response)\n        expect(subject.call({})).to eq(expected_response)\n      end\n    end\n  end\n\n  context 'input' do\n    content_types = ['application/json', 'application/json; charset=utf-8'].freeze\n    %w[POST PATCH PUT DELETE].each do |method|\n      context 'when body is not nil or empty' do\n        context 'when Content-Type is supported' do\n          let(:io) { StringIO.new('{\"is_boolean\":true,\"string\":\"thing\"}') }\n          let(:content_type) { 'application/json' }\n\n          it \"parses the body from #{method} and copies values into rack.request.form_hash\" do\n            subject.call(\n              Rack::PATH_INFO => '/info',\n              Rack::REQUEST_METHOD => method,\n              'CONTENT_TYPE' => content_type,\n              Rack::RACK_INPUT => io,\n              'CONTENT_LENGTH' => io.length.to_s\n            )\n            expect(subject.env[Rack::RACK_REQUEST_FORM_HASH]['is_boolean']).to be true\n            expect(subject.env[Rack::RACK_REQUEST_FORM_HASH]['string']).to eq('thing')\n          end\n        end\n\n        context 'when Content-Type is not supported' do\n          let(:io) { StringIO.new('{\"is_boolean\":true,\"string\":\"thing\"}') }\n          let(:content_type) { 'application/atom+xml' }\n\n          it 'returns a 415 HTTP error status' do\n            error = catch(:error) do\n              subject.call(\n                Rack::PATH_INFO => '/info',\n                Rack::REQUEST_METHOD => method,\n                'CONTENT_TYPE' => content_type,\n                Rack::RACK_INPUT => io,\n                'CONTENT_LENGTH' => io.length.to_s\n              )\n            end\n            expect(error[:status]).to eq(415)\n            expect(error[:message]).to eq(\"The provided content-type 'application/atom+xml' is not supported.\")\n          end\n        end\n      end\n\n      context 'when body is nil' do\n        let(:io) { double }\n\n        before do\n          allow(io).to receive_message_chain(rewind: nil, read: nil)\n        end\n\n        it 'does not read and parse the body' do\n          expect(subject).not_to receive(:read_rack_input)\n          subject.call(\n            Rack::PATH_INFO => '/info',\n            Rack::REQUEST_METHOD => method,\n            'CONTENT_TYPE' => 'application/json',\n            Rack::RACK_INPUT => io,\n            'CONTENT_LENGTH' => '0'\n          )\n        end\n      end\n\n      context 'when body is empty' do\n        let(:io) { double }\n\n        before do\n          allow(io).to receive_messages(rewind: nil, read: '')\n        end\n\n        it 'does not read and parse the body' do\n          expect(subject).not_to receive(:read_rack_input)\n          subject.call(\n            Rack::PATH_INFO => '/info',\n            Rack::REQUEST_METHOD => method,\n            'CONTENT_TYPE' => 'application/json',\n            Rack::RACK_INPUT => io,\n            'CONTENT_LENGTH' => 0\n          )\n        end\n      end\n\n      content_types.each do |content_type|\n        context content_type do\n          it \"parses the body from #{method} and copies values into rack.request.form_hash\" do\n            io = StringIO.new('{\"is_boolean\":true,\"string\":\"thing\"}')\n            subject.call(\n              Rack::PATH_INFO => '/info',\n              Rack::REQUEST_METHOD => method,\n              'CONTENT_TYPE' => content_type,\n              Rack::RACK_INPUT => io,\n              'CONTENT_LENGTH' => io.length.to_s\n            )\n            expect(subject.env[Rack::RACK_REQUEST_FORM_HASH]['is_boolean']).to be true\n            expect(subject.env[Rack::RACK_REQUEST_FORM_HASH]['string']).to eq('thing')\n          end\n        end\n      end\n      it \"parses the chunked body from #{method} and copies values into rack.request.from_hash\" do\n        io = StringIO.new('{\"is_boolean\":true,\"string\":\"thing\"}')\n        subject.call(\n          Rack::PATH_INFO => '/infol',\n          Rack::REQUEST_METHOD => method,\n          'CONTENT_TYPE' => 'application/json',\n          Rack::RACK_INPUT => io,\n          'HTTP_TRANSFER_ENCODING' => 'chunked'\n        )\n        expect(subject.env[Rack::RACK_REQUEST_FORM_HASH]['is_boolean']).to be true\n        expect(subject.env[Rack::RACK_REQUEST_FORM_HASH]['string']).to eq('thing')\n      end\n\n      it 'rewinds IO' do\n        io = StringIO.new('{\"is_boolean\":true,\"string\":\"thing\"}')\n        io.read\n        subject.call(\n          Rack::PATH_INFO => '/infol',\n          Rack::REQUEST_METHOD => method,\n          'CONTENT_TYPE' => 'application/json',\n          Rack::RACK_INPUT => io,\n          'HTTP_TRANSFER_ENCODING' => 'chunked'\n        )\n        expect(subject.env[Rack::RACK_REQUEST_FORM_HASH]['is_boolean']).to be true\n        expect(subject.env[Rack::RACK_REQUEST_FORM_HASH]['string']).to eq('thing')\n      end\n\n      it \"parses the body from an xml #{method} and copies values into rack.request.from_hash\" do\n        io = StringIO.new('<thing><name>Test</name></thing>')\n        subject.call(\n          Rack::PATH_INFO => '/info.xml',\n          Rack::REQUEST_METHOD => method,\n          'CONTENT_TYPE' => 'application/xml',\n          Rack::RACK_INPUT => io,\n          'CONTENT_LENGTH' => io.length.to_s\n        )\n        if Object.const_defined? :MultiXml\n          expect(subject.env[Rack::RACK_REQUEST_FORM_HASH]['thing']['name']).to eq('Test')\n        else\n          expect(subject.env[Rack::RACK_REQUEST_FORM_HASH]['thing']['name']['__content__']).to eq('Test')\n        end\n      end\n\n      [Rack::Request::FORM_DATA_MEDIA_TYPES, Rack::Request::PARSEABLE_DATA_MEDIA_TYPES].flatten.each do |content_type|\n        it \"ignores #{content_type}\" do\n          io = StringIO.new('name=Other+Test+Thing')\n          subject.call(\n            Rack::PATH_INFO => '/info',\n            Rack::REQUEST_METHOD => method,\n            'CONTENT_TYPE' => content_type,\n            Rack::RACK_INPUT => io,\n            'CONTENT_LENGTH' => io.length.to_s\n          )\n          expect(subject.env[Rack::RACK_REQUEST_FORM_HASH]).to be_nil\n        end\n      end\n    end\n  end\n\n  context 'send file' do\n    let(:file) { double(File) }\n    let(:file_body) { Grape::ServeStream::StreamResponse.new(file) }\n    let(:app) { ->(_env) { [200, {}, file_body] } }\n    let(:body) { 'data' }\n    let(:env) do\n      { Rack::PATH_INFO => '/somewhere', 'HTTP_ACCEPT' => 'application/json' }\n    end\n    let(:headers) do\n      if Gem::Version.new(Rack.release) < Gem::Version.new('3.1')\n        { Rack::CONTENT_TYPE => 'application/json', Rack::CONTENT_LENGTH => body.bytesize.to_s }\n      else\n        { Rack::CONTENT_TYPE => 'application/json' }\n      end\n    end\n\n    it 'returns a file response' do\n      expect(file).to receive(:each).and_yield(body)\n      r = Rack::MockResponse[*subject.call(env)]\n      expect(r).to be_successful\n      expect(r.headers).to eq(headers)\n      expect(r.body).to eq('data')\n    end\n  end\n\n  context 'inheritable formatters' do\n    subject { described_class.new(app, formatters: { invalid: invalid_formatter }, content_types: { invalid: 'application/x-invalid' }) }\n\n    let(:invalid_formatter) do\n      Class.new do\n        def self.call(_, _)\n          { message: 'invalid' }.to_json\n        end\n      end\n    end\n\n    let(:app) { ->(_env) { [200, {}, ['']] } }\n    let(:env) do\n      Rack::MockRequest.env_for('/hello.invalid', 'HTTP_ACCEPT' => 'application/x-invalid')\n    end\n\n    it 'returns response by invalid formatter' do\n      r = Rack::MockResponse[*subject.call(env)]\n      expect(JSON.parse(r.body)).to eq('message' => 'invalid')\n    end\n  end\n\n  context 'custom parser raises exception and rescue options are enabled for backtrace and original_exception' do\n    it 'adds the backtrace and original_exception to the error output' do\n      subject = described_class.new(\n        app,\n        rescue_options: { backtrace: true, original_exception: true },\n        parsers: { json: ->(_object, _env) { raise StandardError, 'fail' } }\n      )\n      io = StringIO.new('{invalid}')\n      error = catch(:error) do\n        subject.call(\n          Rack::PATH_INFO => '/info',\n          Rack::REQUEST_METHOD => Rack::POST,\n          'CONTENT_TYPE' => 'application/json',\n          Rack::RACK_INPUT => io,\n          'CONTENT_LENGTH' => io.length.to_s\n        )\n      end\n\n      expect(error[:message]).to eq 'fail'\n      expect(error[:backtrace].size).to be >= 1\n      expect(error[:original_exception].class).to eq StandardError\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/middleware/globals_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Middleware::Globals do\n  subject { described_class.new(blank_app) }\n\n  before { allow(subject).to receive(:dup).and_return(subject) }\n\n  let(:blank_app) { ->(_env) { [200, {}, 'Hi there.'] } }\n\n  it 'calls through to the app' do\n    expect(subject.call({})).to eq([200, {}, 'Hi there.'])\n  end\n\n  context 'environment' do\n    it 'sets the grape.request environment' do\n      subject.call({})\n      expect(subject.env[Grape::Env::GRAPE_REQUEST]).to be_a(Grape::Request)\n    end\n\n    it 'sets the grape.request.headers environment' do\n      subject.call({})\n      expect(subject.env[Grape::Env::GRAPE_REQUEST_HEADERS]).to be_a(Hash)\n    end\n\n    it 'sets the grape.request.params environment' do\n      subject.call(Rack::QUERY_STRING => 'test=1', Rack::RACK_INPUT => StringIO.new)\n      expect(subject.env[Grape::Env::GRAPE_REQUEST_PARAMS]).to be_a(Hash)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/middleware/stack_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Middleware::Stack do\n  subject { described_class.new }\n\n  let(:foo_middleware) { Class.new }\n  let(:bar_middleware) { Class.new }\n  let(:block_middleware) do\n    Class.new do\n      attr_reader :block\n\n      def initialize(&block)\n        @block = block\n      end\n    end\n  end\n  let(:proc) { -> {} }\n  let(:others) { [[:use, bar_middleware], [:insert_before, bar_middleware, block_middleware, proc]] }\n\n  before do\n    subject.use foo_middleware\n  end\n\n  describe '#use' do\n    it 'pushes a middleware class onto the stack' do\n      expect { subject.use bar_middleware }\n        .to change(subject, :size).by(1)\n      expect(subject.last).to eq(bar_middleware)\n    end\n\n    it 'pushes a middleware class with arguments onto the stack' do\n      expect { subject.use bar_middleware, false, my_arg: 42 }\n        .to change(subject, :size).by(1)\n      expect(subject.last).to eq(bar_middleware)\n      expect(subject.last.args).to eq([false, { my_arg: 42 }])\n    end\n\n    it 'pushes a middleware class with block arguments onto the stack' do\n      expect { subject.use block_middleware, &proc }\n        .to change(subject, :size).by(1)\n      expect(subject.last).to eq(block_middleware)\n      expect(subject.last.args).to eq([])\n      expect(subject.last.block).to eq(proc)\n    end\n  end\n\n  describe '#insert' do\n    it 'inserts a middleware class at the integer index' do\n      expect { subject.insert 0, bar_middleware }\n        .to change(subject, :size).by(1)\n      expect(subject[0]).to eq(bar_middleware)\n      expect(subject[1]).to eq(foo_middleware)\n    end\n  end\n\n  describe '#insert_before' do\n    it 'inserts a middleware before another middleware class' do\n      expect { subject.insert_before foo_middleware, bar_middleware }\n        .to change(subject, :size).by(1)\n      expect(subject[0]).to eq(bar_middleware)\n      expect(subject[1]).to eq(foo_middleware)\n    end\n\n    it 'inserts a middleware before an anonymous class given by its superclass' do\n      subject.use Class.new(block_middleware)\n\n      expect { subject.insert_before block_middleware, bar_middleware }\n        .to change(subject, :size).by(1)\n\n      expect(subject[1]).to eq(bar_middleware)\n      expect(subject[2]).to eq(block_middleware)\n    end\n\n    it 'raises an error on an invalid index' do\n      stub_const('StackSpec::BlockMiddleware', block_middleware)\n      expect { subject.insert_before block_middleware, bar_middleware }\n        .to raise_error(RuntimeError, 'No such middleware to insert before: StackSpec::BlockMiddleware')\n    end\n  end\n\n  describe '#insert_after' do\n    it 'inserts a middleware after another middleware class' do\n      expect { subject.insert_after foo_middleware, bar_middleware }\n        .to change(subject, :size).by(1)\n      expect(subject[1]).to eq(bar_middleware)\n      expect(subject[0]).to eq(foo_middleware)\n    end\n\n    it 'inserts a middleware after an anonymous class given by its superclass' do\n      subject.use Class.new(block_middleware)\n\n      expect { subject.insert_after block_middleware, bar_middleware }\n        .to change(subject, :size).by(1)\n\n      expect(subject[1]).to eq(block_middleware)\n      expect(subject[2]).to eq(bar_middleware)\n    end\n\n    it 'raises an error on an invalid index' do\n      stub_const('StackSpec::BlockMiddleware', block_middleware)\n      expect { subject.insert_after block_middleware, bar_middleware }\n        .to raise_error(RuntimeError, 'No such middleware to insert after: StackSpec::BlockMiddleware')\n    end\n  end\n\n  describe '#merge_with' do\n    it 'applies a collection of operations and middlewares' do\n      expect { subject.merge_with(others) }\n        .to change(subject, :size).by(2)\n      expect(subject[0]).to eq(foo_middleware)\n      expect(subject[1]).to eq(block_middleware)\n      expect(subject[2]).to eq(bar_middleware)\n    end\n\n    context 'middleware spec with proc declaration exists' do\n      let(:middleware_spec_with_proc) { [:use, foo_middleware, proc] }\n\n      it 'properly forwards spec arguments' do\n        expect(subject).to receive(:use).with(foo_middleware)\n        subject.merge_with([middleware_spec_with_proc])\n      end\n    end\n  end\n\n  describe '#build' do\n    it 'returns a rack builder instance' do\n      expect(subject.build).to be_instance_of(Rack::Builder)\n    end\n\n    context 'when @others are present' do\n      let(:others) { [[:insert_after, Grape::Middleware::Formatter, bar_middleware]] }\n\n      it 'applies the middleware specs stored in @others' do\n        subject.concat others\n        subject.use Grape::Middleware::Formatter\n        subject.build\n        expect(subject[0]).to eq foo_middleware\n        expect(subject[1]).to eq Grape::Middleware::Formatter\n        expect(subject[2]).to eq bar_middleware\n      end\n    end\n  end\n\n  describe '#concat' do\n    it 'adds non :use specs to @others' do\n      expect { subject.concat others }.to change(subject, :others).from([]).to([[others.last]])\n    end\n\n    it 'calls +merge_with+ with the :use specs' do\n      expect(subject).to receive(:merge_with).with [[:use, bar_middleware]]\n      subject.concat others\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/middleware/versioner/accept_version_header_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Middleware::Versioner::AcceptVersionHeader do\n  subject { described_class.new(app, **@options) }\n\n  let(:app) { ->(env) { [200, env, env] } }\n\n  before do\n    @options = {\n      version_options: {\n        using: :accept_version_header\n      }\n    }\n  end\n\n  describe '#bad encoding' do\n    before do\n      @options[:versions] = %w[v1]\n    end\n\n    it 'does not raise an error' do\n      expect do\n        subject.call('HTTP_ACCEPT_VERSION' => \"\\x80\")\n      end.to throw_symbol(:error, status: 406, headers: { 'X-Cascade' => 'pass' }, message: 'The requested version is not supported.')\n    end\n  end\n\n  context 'api.version' do\n    before do\n      @options[:versions] = ['v1']\n    end\n\n    it 'is set' do\n      status, _, env = subject.call('HTTP_ACCEPT_VERSION' => 'v1')\n      expect(env[Grape::Env::API_VERSION]).to eql 'v1'\n      expect(status).to eq(200)\n    end\n\n    it 'is set if format provided' do\n      status, _, env = subject.call('HTTP_ACCEPT_VERSION' => 'v1')\n      expect(env[Grape::Env::API_VERSION]).to eql 'v1'\n      expect(status).to eq(200)\n    end\n\n    it 'fails with 406 Not Acceptable if version is not supported' do\n      expect do\n        subject.call('HTTP_ACCEPT_VERSION' => 'v2').last\n      end.to throw_symbol(\n        :error,\n        status: 406,\n        headers: { 'X-Cascade' => 'pass' },\n        message: 'The requested version is not supported.'\n      )\n    end\n  end\n\n  it 'succeeds if :strict is not set' do\n    expect(subject.call('HTTP_ACCEPT_VERSION' => '').first).to eq(200)\n    expect(subject.call({}).first).to eq(200)\n  end\n\n  it 'succeeds if :strict is set to false' do\n    @options[:version_options][:strict] = false\n    expect(subject.call('HTTP_ACCEPT_VERSION' => '').first).to eq(200)\n    expect(subject.call({}).first).to eq(200)\n  end\n\n  context 'when :strict is set' do\n    before do\n      @options[:versions] = ['v1']\n      @options[:version_options][:strict] = true\n    end\n\n    it 'fails with 406 Not Acceptable if header is not set' do\n      expect do\n        subject.call({}).last\n      end.to throw_symbol(\n        :error,\n        status: 406,\n        headers: { 'X-Cascade' => 'pass' },\n        message: 'Accept-Version header must be set.'\n      )\n    end\n\n    it 'fails with 406 Not Acceptable if header is empty' do\n      expect do\n        subject.call('HTTP_ACCEPT_VERSION' => '').last\n      end.to throw_symbol(\n        :error,\n        status: 406,\n        headers: { 'X-Cascade' => 'pass' },\n        message: 'Accept-Version header must be set.'\n      )\n    end\n\n    it 'succeeds if proper header is set' do\n      expect(subject.call('HTTP_ACCEPT_VERSION' => 'v1').first).to eq(200)\n    end\n  end\n\n  context 'when :strict and cascade: false' do\n    before do\n      @options[:versions] = ['v1']\n      @options[:version_options][:strict] = true\n      @options[:version_options][:cascade] = false\n    end\n\n    it 'fails with 406 Not Acceptable if header is not set' do\n      expect do\n        subject.call({}).last\n      end.to throw_symbol(\n        :error,\n        status: 406,\n        headers: {},\n        message: 'Accept-Version header must be set.'\n      )\n    end\n\n    it 'fails with 406 Not Acceptable if header is empty' do\n      expect do\n        subject.call('HTTP_ACCEPT_VERSION' => '').last\n      end.to throw_symbol(\n        :error,\n        status: 406,\n        headers: {},\n        message: 'Accept-Version header must be set.'\n      )\n    end\n\n    it 'succeeds if proper header is set' do\n      expect(subject.call('HTTP_ACCEPT_VERSION' => 'v1').first).to eq(200)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/middleware/versioner/header_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Middleware::Versioner::Header do\n  subject { described_class.new(app, **@options) }\n\n  let(:app) { ->(env) { [200, env, env] } }\n\n  before do\n    @options = {\n      version_options: {\n        using: :header,\n        vendor: 'vendor'\n      }\n    }\n  end\n\n  context 'api.type and api.subtype' do\n    it 'sets type and subtype to first choice of content type if no preference given' do\n      status, _, env = subject.call('HTTP_ACCEPT' => '*/*')\n      expect(env[Grape::Env::API_TYPE]).to eql 'application'\n      expect(env[Grape::Env::API_SUBTYPE]).to eql 'vnd.vendor+xml'\n      expect(status).to eq(200)\n    end\n\n    it 'sets preferred type' do\n      status, _, env = subject.call('HTTP_ACCEPT' => 'application/*')\n      expect(env[Grape::Env::API_TYPE]).to eql 'application'\n      expect(env[Grape::Env::API_SUBTYPE]).to eql 'vnd.vendor+xml'\n      expect(status).to eq(200)\n    end\n\n    it 'sets preferred type and subtype' do\n      status, _, env = subject.call('HTTP_ACCEPT' => 'text/plain')\n      expect(env[Grape::Env::API_TYPE]).to eql 'text'\n      expect(env[Grape::Env::API_SUBTYPE]).to eql 'plain'\n      expect(status).to eq(200)\n    end\n  end\n\n  context 'api.format' do\n    it 'is set' do\n      status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor+json')\n      expect(env[Grape::Env::API_FORMAT]).to eql 'json'\n      expect(status).to eq(200)\n    end\n\n    it 'is nil if not provided' do\n      status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor')\n      expect(env[Grape::Env::API_FORMAT]).to be_nil\n      expect(status).to eq(200)\n    end\n\n    ['v1', :v1].each do |version|\n      context \"when version is set to #{version}\" do\n        before do\n          @options[:versions] = [version]\n        end\n\n        it 'is set' do\n          status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')\n          expect(env[Grape::Env::API_FORMAT]).to eql 'json'\n          expect(status).to eq(200)\n        end\n\n        it 'is nil if not provided' do\n          status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')\n          expect(env[Grape::Env::API_FORMAT]).to be_nil\n          expect(status).to eq(200)\n        end\n      end\n    end\n  end\n\n  context 'api.vendor' do\n    it 'is set' do\n      status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor')\n      expect(env[Grape::Env::API_VENDOR]).to eql 'vendor'\n      expect(status).to eq(200)\n    end\n\n    it 'is set if format provided' do\n      status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor+json')\n      expect(env[Grape::Env::API_VENDOR]).to eql 'vendor'\n      expect(status).to eq(200)\n    end\n\n    it 'fails with 406 Not Acceptable if vendor is invalid' do\n      expect { subject.call('HTTP_ACCEPT' => 'application/vnd.othervendor+json').last }\n        .to raise_exception do |exception|\n          expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)\n          expect(exception.headers).to eql('X-Cascade' => 'pass')\n          expect(exception.status).to be 406\n          expect(exception.message).to include 'API vendor not found'\n        end\n    end\n\n    context 'when version is set' do\n      before do\n        @options[:versions] = ['v1']\n      end\n\n      it 'is set' do\n        status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')\n        expect(env[Grape::Env::API_VENDOR]).to eql 'vendor'\n        expect(status).to eq(200)\n      end\n\n      it 'is set if format provided' do\n        status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')\n        expect(env[Grape::Env::API_VENDOR]).to eql 'vendor'\n        expect(status).to eq(200)\n      end\n\n      it 'fails with 406 Not Acceptable if vendor is invalid' do\n        expect { subject.call('HTTP_ACCEPT' => 'application/vnd.othervendor-v1+json').last }\n          .to raise_exception do |exception|\n            expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)\n            expect(exception.headers).to eql('X-Cascade' => 'pass')\n            expect(exception.status).to be 406\n            expect(exception.message).to include('API vendor not found')\n          end\n      end\n    end\n  end\n\n  context 'api.version' do\n    before do\n      @options[:versions] = ['v1']\n    end\n\n    it 'is set' do\n      status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1')\n      expect(env[Grape::Env::API_VERSION]).to eql 'v1'\n      expect(status).to eq(200)\n    end\n\n    it 'is set if format provided' do\n      status, _, env = subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json')\n      expect(env[Grape::Env::API_VERSION]).to eql 'v1'\n      expect(status).to eq(200)\n    end\n\n    it 'fails with 406 Not Acceptable if version is invalid' do\n      expect { subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v2+json').last }.to raise_exception do |exception|\n        expect(exception).to be_a(Grape::Exceptions::InvalidVersionHeader)\n        expect(exception.headers).to eql('X-Cascade' => 'pass')\n        expect(exception.status).to be 406\n        expect(exception.message).to include('API version not found')\n      end\n    end\n  end\n\n  it 'succeeds if :strict is not set' do\n    expect(subject.call('HTTP_ACCEPT' => '').first).to eq(200)\n    expect(subject.call({}).first).to eq(200)\n  end\n\n  it 'succeeds if :strict is set to false' do\n    @options[:version_options][:strict] = false\n    expect(subject.call('HTTP_ACCEPT' => '').first).to eq(200)\n    expect(subject.call({}).first).to eq(200)\n  end\n\n  it 'succeeds if :strict is set to false and given an invalid header' do\n    @options[:version_options][:strict] = false\n    expect(subject.call('HTTP_ACCEPT' => 'yaml').first).to eq(200)\n    expect(subject.call({}).first).to eq(200)\n  end\n\n  context 'when :strict is set' do\n    before do\n      @options[:versions] = ['v1']\n      @options[:version_options][:strict] = true\n    end\n\n    it 'fails with 406 Not Acceptable if header is not set' do\n      expect { subject.call({}).last }.to raise_exception do |exception|\n        expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)\n        expect(exception.headers).to eql('X-Cascade' => 'pass')\n        expect(exception.status).to be 406\n        expect(exception.message).to include('Accept header must be set.')\n      end\n    end\n\n    it 'fails with 406 Not Acceptable if header is empty' do\n      expect { subject.call('HTTP_ACCEPT' => '').last }.to raise_exception do |exception|\n        expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)\n        expect(exception.headers).to eql('X-Cascade' => 'pass')\n        expect(exception.status).to be 406\n        expect(exception.message).to include('Accept header must be set.')\n      end\n    end\n\n    it 'succeeds if proper header is set' do\n      expect(subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json').first).to eq(200)\n    end\n  end\n\n  context 'when :strict and cascade: false' do\n    before do\n      @options[:versions] = ['v1']\n      @options[:version_options][:strict] = true\n      @options[:version_options][:cascade] = false\n    end\n\n    it 'fails with 406 Not Acceptable if header is not set' do\n      expect { subject.call({}).last }.to raise_exception do |exception|\n        expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)\n        expect(exception.headers).to eql({})\n        expect(exception.status).to be 406\n        expect(exception.message).to include('Accept header must be set.')\n      end\n    end\n\n    it 'fails with 406 Not Acceptable if header is application/xml' do\n      expect { subject.call('HTTP_ACCEPT' => 'application/xml').last }\n        .to raise_exception do |exception|\n        expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)\n        expect(exception.headers).to eql({})\n        expect(exception.status).to be 406\n        expect(exception.message).to include('API vendor or version not found.')\n      end\n    end\n\n    it 'fails with 406 Not Acceptable if header is empty' do\n      expect { subject.call('HTTP_ACCEPT' => '').last }.to raise_exception do |exception|\n        expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)\n        expect(exception.headers).to eql({})\n        expect(exception.status).to be 406\n        expect(exception.message).to include('Accept header must be set.')\n      end\n    end\n\n    it 'fails with 406 Not Acceptable if header contains a single invalid accept' do\n      expect { subject.call('HTTP_ACCEPT' => 'application/json;application/vnd.vendor-v1+json').first }\n        .to raise_exception do |exception|\n        expect(exception).to be_a(Grape::Exceptions::InvalidAcceptHeader)\n        expect(exception.headers).to eql({})\n        expect(exception.status).to be 406\n        expect(exception.message).to include('API vendor or version not found.')\n      end\n    end\n\n    it 'succeeds if proper header is set' do\n      expect(subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json').first).to eq(200)\n    end\n  end\n\n  context 'when multiple versions are specified' do\n    before do\n      @options[:versions] = %w[v1 v2]\n    end\n\n    it 'succeeds with v1' do\n      expect(subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v1+json').first).to eq(200)\n    end\n\n    it 'succeeds with v2' do\n      expect(subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v2+json').first).to eq(200)\n    end\n\n    it 'fails with another version' do\n      expect { subject.call('HTTP_ACCEPT' => 'application/vnd.vendor-v3+json') }.to raise_exception do |exception|\n        expect(exception).to be_a(Grape::Exceptions::InvalidVersionHeader)\n        expect(exception.headers).to eql('X-Cascade' => 'pass')\n        expect(exception.status).to be 406\n        expect(exception.message).to include('API version not found')\n      end\n    end\n  end\n\n  context 'when there are multiple versions with complex vendor specified with rescue_from :all' do\n    subject do\n      Class.new(Grape::API) do\n        rescue_from :all\n      end\n    end\n\n    let(:v1_app) do\n      Class.new(Grape::API) do\n        version 'v1', using: :header, vendor: 'test.a-cool_resource', cascade: false, strict: true\n        content_type :v1_test, 'application/vnd.test.a-cool_resource-v1+json'\n        formatter :v1_test, ->(object, _) { object }\n        format :v1_test\n\n        resources :users do\n          get :hello do\n            'one'\n          end\n        end\n      end\n    end\n\n    let(:v2_app) do\n      Class.new(Grape::API) do\n        version 'v2', using: :header, vendor: 'test.a-cool_resource', strict: true\n        content_type :v2_test, 'application/vnd.test.a-cool_resource-v2+json'\n        formatter :v2_test, ->(object, _) { object }\n        format :v2_test\n\n        resources :users do\n          get :hello do\n            'two'\n          end\n        end\n      end\n    end\n\n    def app\n      subject.mount v2_app\n      subject.mount v1_app\n      subject\n    end\n\n    context 'with header versioned endpoints and a rescue_all block defined' do\n      it 'responds correctly to a v1 request' do\n        versioned_get '/users/hello', 'v1', using: :header, vendor: 'test.a-cool_resource'\n        expect(last_response.body).to eq('one')\n        expect(last_response.body).not_to include('API vendor or version not found')\n      end\n\n      it 'responds correctly to a v2 request' do\n        versioned_get '/users/hello', 'v2', using: :header, vendor: 'test.a-cool_resource'\n        expect(last_response.body).to eq('two')\n        expect(last_response.body).not_to include('API vendor or version not found')\n      end\n    end\n  end\n\n  context 'with missing vendor option' do\n    subject do\n      Class.new(Grape::API) do\n        version 'v1', using: :header\n      end\n    end\n\n    def app\n      subject\n    end\n\n    it 'fails' do\n      expect { versioned_get '/', 'v1', using: :header }.to raise_error Grape::Exceptions::MissingVendorOption\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/middleware/versioner/param_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Middleware::Versioner::Param do\n  subject { described_class.new(app, **options) }\n\n  let(:app) { ->(env) { [200, env, env[Grape::Env::API_VERSION]] } }\n  let(:options) { {} }\n\n  it 'sets the API version based on the default param (apiver)' do\n    env = Rack::MockRequest.env_for('/awesome', params: { 'apiver' => 'v1' })\n    expect(subject.call(env)[1][Grape::Env::API_VERSION]).to eq('v1')\n  end\n\n  it 'cuts (only) the version out of the params' do\n    env = Rack::MockRequest.env_for('/awesome', params: { 'apiver' => 'v1', 'other_param' => '5' })\n    env[Rack::RACK_REQUEST_QUERY_HASH] = Rack::Utils.parse_nested_query(env[Rack::QUERY_STRING])\n    expect(subject.call(env)[1][Rack::RACK_REQUEST_QUERY_HASH]['apiver']).to be_nil\n    expect(subject.call(env)[1][Rack::RACK_REQUEST_QUERY_HASH]['other_param']).to eq('5')\n  end\n\n  it 'provides a nil version if no version is given' do\n    env = Rack::MockRequest.env_for('/')\n    expect(subject.call(env).last).to be_nil\n  end\n\n  context 'with specified parameter name' do\n    let(:options) { { version_options: { parameter: 'v' } } }\n\n    it 'sets the API version based on the custom parameter name' do\n      env = Rack::MockRequest.env_for('/awesome', params: { 'v' => 'v1' })\n      expect(subject.call(env)[1][Grape::Env::API_VERSION]).to eq('v1')\n    end\n\n    it 'does not set the API version based on the default param' do\n      env = Rack::MockRequest.env_for('/awesome', params: { 'apiver' => 'v1' })\n      expect(subject.call(env)[1][Grape::Env::API_VERSION]).to be_nil\n    end\n  end\n\n  context 'with specified versions' do\n    let(:options) { { versions: %w[v1 v2] } }\n\n    it 'throws an error if a non-allowed version is specified' do\n      env = Rack::MockRequest.env_for('/awesome', params: { 'apiver' => 'v3' })\n      expect(catch(:error) { subject.call(env) }[:status]).to eq(404)\n    end\n\n    it 'allows versions that have been specified' do\n      env = Rack::MockRequest.env_for('/awesome', params: { 'apiver' => 'v1' })\n      expect(subject.call(env)[1][Grape::Env::API_VERSION]).to eq('v1')\n    end\n  end\n\n  context 'when no version is set' do\n    let(:options) do\n      {\n        versions: ['v1'],\n        version_options: { using: :header }\n      }\n    end\n\n    it 'returns a 200 (matches the first version found)' do\n      env = Rack::MockRequest.env_for('/awesome', params: {})\n      expect(subject.call(env).first).to eq(200)\n    end\n  end\n\n  context 'when there are multiple versions without a custom param' do\n    subject { Class.new(Grape::API) }\n\n    let(:v1_app) do\n      Class.new(Grape::API) do\n        version 'v1', using: :param\n        content_type :v1_test, 'application/vnd.test.a-cool_resource-v1+json'\n        formatter :v1_test, ->(object, _) { object }\n        format :v1_test\n\n        resources :users do\n          get :hello do\n            'one'\n          end\n        end\n      end\n    end\n\n    let(:v2_app) do\n      Class.new(Grape::API) do\n        version 'v2', using: :param\n        content_type :v2_test, 'application/vnd.test.a-cool_resource-v2+json'\n        formatter :v2_test, ->(object, _) { object }\n        format :v2_test\n\n        resources :users do\n          get :hello do\n            'two'\n          end\n        end\n      end\n    end\n\n    def app\n      subject.mount v2_app\n      subject.mount v1_app\n      subject\n    end\n\n    it 'responds correctly to a v1 request' do\n      versioned_get '/users/hello', 'v1', using: :param, parameter: :apiver\n      expect(last_response.body).to eq('one')\n      expect(last_response.body).not_to include('API vendor or version not found')\n    end\n\n    it 'responds correctly to a v2 request' do\n      versioned_get '/users/hello', 'v2', using: :param, parameter: :apiver\n      expect(last_response.body).to eq('two')\n      expect(last_response.body).not_to include('API vendor or version not found')\n    end\n  end\n\n  context 'when there are multiple versions with a custom param' do\n    subject { Class.new(Grape::API) }\n\n    let(:v1_app) do\n      Class.new(Grape::API) do\n        version 'v1', using: :param, parameter: 'v'\n        content_type :v1_test, 'application/vnd.test.a-cool_resource-v1+json'\n        formatter :v1_test, ->(object, _) { object }\n        format :v1_test\n\n        resources :users do\n          get :hello do\n            'one'\n          end\n        end\n      end\n    end\n\n    let(:v2_app) do\n      Class.new(Grape::API) do\n        version 'v2', using: :param, parameter: 'v'\n        content_type :v2_test, 'application/vnd.test.a-cool_resource-v2+json'\n        formatter :v2_test, ->(object, _) { object }\n        format :v2_test\n\n        resources :users do\n          get :hello do\n            'two'\n          end\n        end\n      end\n    end\n\n    def app\n      subject.mount v2_app\n      subject.mount v1_app\n      subject\n    end\n\n    it 'responds correctly to a v1 request' do\n      versioned_get '/users/hello', 'v1', using: :param, parameter: 'v'\n      expect(last_response.body).to eq('one')\n      expect(last_response.body).not_to include('API vendor or version not found')\n    end\n\n    it 'responds correctly to a v2 request' do\n      versioned_get '/users/hello', 'v2', using: :param, parameter: 'v'\n      expect(last_response.body).to eq('two')\n      expect(last_response.body).not_to include('API vendor or version not found')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/middleware/versioner/path_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Middleware::Versioner::Path do\n  subject { described_class.new(app, **options) }\n\n  let(:app) { ->(env) { [200, env, env[Grape::Env::API_VERSION]] } }\n  let(:options) { {} }\n\n  it 'sets the API version based on the first path' do\n    expect(subject.call(Rack::PATH_INFO => '/v1/awesome').last).to eq('v1')\n  end\n\n  it 'does not cut the version out of the path' do\n    expect(subject.call(Rack::PATH_INFO => '/v1/awesome')[1][Rack::PATH_INFO]).to eq('/v1/awesome')\n  end\n\n  it 'provides a nil version if no path is given' do\n    expect(subject.call(Rack::PATH_INFO => '/').last).to be_nil\n  end\n\n  context 'with a pattern' do\n    let(:options) { { pattern: /v./i } }\n\n    it 'sets the version if it matches' do\n      expect(subject.call(Rack::PATH_INFO => '/v1/awesome').last).to eq('v1')\n    end\n\n    it 'ignores the version if it fails to match' do\n      expect(subject.call(Rack::PATH_INFO => '/awesome/radical').last).to be_nil\n    end\n  end\n\n  [%w[v1 v2], %i[v1 v2], [:v1, 'v2'], ['v1', :v2]].each do |versions|\n    context \"with specified versions as #{versions}\" do\n      let(:options) { { versions: versions } }\n\n      it 'throws an error if a non-allowed version is specified' do\n        expect(catch(:error) { subject.call(Rack::PATH_INFO => '/v3/awesome') }[:status]).to eq(404)\n      end\n\n      it 'allows versions that have been specified' do\n        expect(subject.call(Rack::PATH_INFO => '/v1/asoasd').last).to eq('v1')\n      end\n    end\n  end\n\n  context 'with prefix, but requested version is not matched' do\n    let(:options) { { prefix: '/v1', pattern: /v./i } }\n\n    it 'recognizes potential version' do\n      expect(subject.call(Rack::PATH_INFO => '/v3/foo').last).to eq('v3')\n    end\n  end\n\n  context 'with mount path' do\n    let(:options) { { mount_path: '/mounted', versions: [:v1] } }\n\n    it 'recognizes potential version' do\n      expect(subject.call(Rack::PATH_INFO => '/mounted/v1/foo').last).to eq('v1')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/middleware/versioner_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Middleware::Versioner do\n  subject { described_class.using(strategy) }\n\n  context 'when :path' do\n    let(:strategy) { :path }\n\n    it { is_expected.to eq(Grape::Middleware::Versioner::Path) }\n  end\n\n  context 'when :header' do\n    let(:strategy) { :header }\n\n    it { is_expected.to eq(Grape::Middleware::Versioner::Header) }\n  end\n\n  context 'when :param' do\n    let(:strategy) { :param }\n\n    it { is_expected.to eq(Grape::Middleware::Versioner::Param) }\n  end\n\n  context 'when :accept_version_header' do\n    let(:strategy) { :accept_version_header }\n\n    it { is_expected.to eq(Grape::Middleware::Versioner::AcceptVersionHeader) }\n  end\n\n  context 'when unknown' do\n    let(:strategy) { :unknown }\n\n    it 'raises an error' do\n      expect { subject }.to raise_error Grape::Exceptions::InvalidVersionerOption, Grape::Exceptions::InvalidVersionerOption.new(strategy).message\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/named_api_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::API do\n  subject(:api_name) { NamedAPI.endpoints.last.options[:for].to_s }\n\n  let(:api) do\n    Class.new(Grape::API) do\n      get 'test' do\n        'response'\n      end\n    end\n  end\n\n  let(:name) { 'NamedAPI' }\n\n  before { stub_const(name, api) }\n\n  it 'can access the name of the API' do\n    expect(api_name).to eq name\n  end\nend\n"
  },
  {
    "path": "spec/grape/params_builder/hash_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::ParamsBuilder::Hash do\n  subject { app }\n\n  let(:app) do\n    Class.new(Grape::API)\n  end\n\n  describe 'in an endpoint' do\n    describe '#params' do\n      before do\n        subject.params do\n          build_with :hash\n        end\n\n        subject.get do\n          params.class\n        end\n      end\n\n      it 'is of type Hash' do\n        get '/'\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('Hash')\n      end\n    end\n  end\n\n  describe 'in an api' do\n    before do\n      subject.build_with :hash\n    end\n\n    describe '#params' do\n      before do\n        subject.get do\n          params.class\n        end\n      end\n\n      it 'is Hash' do\n        get '/'\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('Hash')\n      end\n    end\n\n    it 'symbolizes params keys' do\n      subject.params do\n        optional :a, type: Hash do\n          optional :b, type: Hash do\n            optional :c, type: String\n          end\n          optional :d, type: Array\n        end\n      end\n\n      subject.get '/foo' do\n        [params[:a][:b][:c], params[:a][:d]]\n      end\n\n      get '/foo', 'a' => { b: { c: 'bar' }, 'd' => ['foo'] }\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('[\"bar\", [\"foo\"]]')\n    end\n\n    it 'symbolizes the params' do\n      subject.params do\n        build_with :hash\n        requires :a, type: String\n      end\n\n      subject.get '/foo' do\n        [params[:a], params['a']]\n      end\n\n      get '/foo', a: 'bar'\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('[\"bar\", nil]')\n    end\n\n    it 'does not overwrite route_param with a regular param if they have same name' do\n      subject.namespace :route_param do\n        route_param :foo do\n          get { params.to_json }\n        end\n      end\n\n      get '/route_param/bar', foo: 'baz'\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('{\"foo\":\"bar\"}')\n    end\n\n    it 'does not overwrite route_param with a defined regular param if they have same name' do\n      subject.namespace :route_param do\n        params do\n          requires :foo, type: String\n        end\n        route_param :foo do\n          get do\n            params[:foo]\n          end\n        end\n      end\n\n      get '/route_param/bar', foo: 'baz'\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('bar')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/params_builder/hash_with_indifferent_access_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::ParamsBuilder::HashWithIndifferentAccess do\n  subject { app }\n\n  let(:app) do\n    Class.new(Grape::API)\n  end\n\n  describe 'in an endpoint' do\n    describe '#params' do\n      before do\n        subject.params do\n          build_with :hash_with_indifferent_access\n        end\n\n        subject.get do\n          params.class\n        end\n      end\n\n      it 'is of type Hash' do\n        get '/'\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('ActiveSupport::HashWithIndifferentAccess')\n      end\n    end\n  end\n\n  describe 'in an api' do\n    before do\n      subject.build_with :hash_with_indifferent_access\n    end\n\n    describe '#params' do\n      before do\n        subject.get do\n          params.class\n        end\n      end\n\n      it 'is a Hash' do\n        get '/'\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('ActiveSupport::HashWithIndifferentAccess')\n      end\n\n      it 'parses sub hash params' do\n        subject.params do\n          build_with :hash_with_indifferent_access\n\n          optional :a, type: Hash do\n            optional :b, type: Hash do\n              optional :c, type: String\n            end\n            optional :d, type: Array\n          end\n        end\n\n        subject.get '/foo' do\n          [params[:a]['b'][:c], params['a'][:d]]\n        end\n\n        get '/foo', a: { b: { c: 'bar' }, d: ['foo'] }\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('[\"bar\", [\"foo\"]]')\n      end\n\n      it 'params are indifferent to symbol or string keys' do\n        subject.params do\n          build_with :hash_with_indifferent_access\n          optional :a, type: Hash do\n            optional :b, type: Hash do\n              optional :c, type: String\n            end\n            optional :d, type: Array\n          end\n        end\n\n        subject.get '/foo' do\n          [params[:a]['b'][:c], params['a'][:d]]\n        end\n\n        get '/foo', 'a' => { b: { c: 'bar' }, 'd' => ['foo'] }\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('[\"bar\", [\"foo\"]]')\n      end\n\n      it 'responds to string keys' do\n        subject.params do\n          build_with :hash_with_indifferent_access\n          requires :a, type: String\n        end\n\n        subject.get '/foo' do\n          [params[:a], params['a']]\n        end\n\n        get '/foo', a: 'bar'\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('[\"bar\", \"bar\"]')\n      end\n    end\n\n    it 'does not overwrite route_param with a regular param if they have same name' do\n      subject.namespace :route_param do\n        route_param :foo do\n          get { params.to_json }\n        end\n      end\n\n      get '/route_param/bar', foo: 'baz'\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('{\"foo\":\"bar\"}')\n    end\n\n    it 'does not overwrite route_param with a defined regular param if they have same name' do\n      subject.namespace :route_param do\n        params do\n          requires :foo, type: String\n        end\n        route_param :foo do\n          get do\n            [params[:foo], params['foo']]\n          end\n        end\n      end\n\n      get '/route_param/bar', foo: 'baz'\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('[\"bar\", \"bar\"]')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/parser_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Parser do\n  subject { described_class }\n\n  describe '.parser_for' do\n    let(:options) { {} }\n\n    it 'returns parser correctly' do\n      expect(subject.parser_for(:json)).to eq(Grape::Parser::Json)\n    end\n\n    context 'when parser is available' do\n      let(:parsers) do\n        { customized_json: Grape::Parser::Json }\n      end\n\n      it 'returns registered parser if available' do\n        expect(subject.parser_for(:customized_json, parsers)).to eq(Grape::Parser::Json)\n      end\n    end\n\n    context 'when parser does not exist' do\n      it 'returns nil' do\n        expect(subject.parser_for(:undefined)).to be_nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/path_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Path do\n  describe '#origin' do\n    context 'mount_path' do\n      it 'is not included when it is nil' do\n        path = described_class.new(nil, nil, mount_path: '/foo/bar')\n        expect(path.origin).to eql '/foo/bar'\n      end\n\n      it 'is included when it is not nil' do\n        path = described_class.new(nil, nil, {})\n        expect(path.origin).to eql('/')\n      end\n    end\n\n    context 'root_prefix' do\n      it 'is not included when it is nil' do\n        path = described_class.new(nil, nil, {})\n        expect(path.origin).to eql('/')\n      end\n\n      it 'is included after the mount path' do\n        path = described_class.new(\n          nil,\n          nil,\n          mount_path: '/foo',\n          root_prefix: '/hello'\n        )\n\n        expect(path.origin).to eql('/foo/hello')\n      end\n    end\n\n    it 'uses the namespace after the mount path and root prefix' do\n      path = described_class.new(\n        nil,\n        'namespace',\n        mount_path: '/foo',\n        root_prefix: '/hello'\n      )\n\n      expect(path.origin).to eql('/foo/hello/namespace')\n    end\n\n    it 'uses the raw path after the namespace' do\n      path = described_class.new(\n        'raw_path',\n        'namespace',\n        mount_path: '/foo',\n        root_prefix: '/hello'\n      )\n\n      expect(path.origin).to eql('/foo/hello/namespace/raw_path')\n    end\n  end\n\n  describe '#suffix' do\n    context 'when using a specific format' do\n      it 'accepts specified format' do\n        path = described_class.new(nil, nil, format: 'json', content_types: 'application/json')\n        expect(path.suffix).to eql('(.json)')\n      end\n    end\n\n    context 'when path versioning is used' do\n      it \"includes a '/'\" do\n        path = described_class.new(nil, nil, version: :v1, version_options: { using: :path })\n        expect(path.suffix).to eql('(/.:format)')\n      end\n    end\n\n    context 'when path versioning is not used' do\n      it \"does not include a '/' when the path has a namespace\" do\n        path = described_class.new(nil, 'namespace', {})\n        expect(path.suffix).to eql('(.:format)')\n      end\n\n      it \"does not include a '/' when the path has a path\" do\n        path = described_class.new('/path', nil, version: :v1, version_options: { using: :path })\n        expect(path.suffix).to eql('(.:format)')\n      end\n\n      it \"includes a '/' otherwise\" do\n        path = described_class.new(nil, nil, version: :v1, version_options: { using: :path })\n        expect(path.suffix).to eql('(/.:format)')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/presenters/presenter_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Presenters::Presenter do\n  subject { dummy_class.new }\n\n  let(:dummy_class) do\n    Class.new do\n      include Grape::DSL::InsideRoute\n\n      attr_reader :env, :request, :new_settings\n\n      def initialize\n        @env = {}\n        @header = {}\n        @new_settings = { namespace_inheritable: {}, namespace_stackable: {} }\n      end\n    end\n  end\n\n  describe 'represent' do\n    let(:object_mock) do\n      Object.new\n    end\n\n    it 'represent object' do\n      expect(described_class.represent(object_mock)).to eq object_mock\n    end\n  end\n\n  describe 'present' do\n    let(:hash_mock) do\n      { key: :value }\n    end\n\n    describe 'instance' do\n      before do\n        subject.present hash_mock, with: described_class\n      end\n\n      it 'presents dummy hash' do\n        expect(subject.body).to eq hash_mock\n      end\n    end\n\n    describe 'multiple presenter' do\n      let(:hash_mock1) do\n        { key1: :value1 }\n      end\n\n      let(:hash_mock2) do\n        { key2: :value2 }\n      end\n\n      describe 'instance' do\n        before do\n          subject.present hash_mock1, with: described_class\n          subject.present hash_mock2, with: described_class\n        end\n\n        it 'presents both dummy presenter' do\n          expect(subject.body[:key1]).to eq hash_mock1[:key1]\n          expect(subject.body[:key2]).to eq hash_mock2[:key2]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/request_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Request do\n  let(:default_method) { Rack::GET }\n  let(:default_params) { {} }\n  let(:default_options) do\n    {\n      method: method,\n      params: params\n    }\n  end\n  let(:default_env) do\n    Rack::MockRequest.env_for('/', options)\n  end\n  let(:method) { default_method }\n  let(:params) { default_params }\n  let(:options) { default_options }\n  let(:env) { default_env }\n\n  let(:request) do\n    described_class.new(env)\n  end\n\n  describe '#params' do\n    let(:params) do\n      {\n        a: '123',\n        b: 'xyz'\n      }\n    end\n\n    it 'by default returns stringified parameter keys' do\n      expect(request.params).to eq(ActiveSupport::HashWithIndifferentAccess.new('a' => '123', 'b' => 'xyz'))\n    end\n\n    context 'when build_params_with: Grape::Extensions::Hash::ParamBuilder is specified' do\n      let(:request) do\n        described_class.new(env, build_params_with: :hash)\n      end\n\n      it 'returns symbolized params' do\n        expect(request.params).to eq(a: '123', b: 'xyz')\n      end\n    end\n\n    describe 'with grape.routing_args' do\n      let(:options) do\n        default_options.merge('grape.routing_args' => routing_args)\n      end\n      let(:routing_args) do\n        {\n          version: '123',\n          route_info: '456',\n          c: 'ccc'\n        }\n      end\n\n      it 'cuts version and route_info' do\n        expect(request.params).to eq(ActiveSupport::HashWithIndifferentAccess.new(a: '123', b: 'xyz', c: 'ccc'))\n      end\n    end\n\n    context 'when rack_params raises an EOF error' do\n      before do\n        allow(request).to receive(:rack_params).and_raise(EOFError)\n      end\n\n      let(:message) { Grape::Exceptions::EmptyMessageBody.new(nil).to_s }\n\n      it 'raises an Grape::Exceptions::EmptyMessageBody' do\n        expect { request.params }.to raise_error(Grape::Exceptions::EmptyMessageBody, message)\n      end\n    end\n\n    context 'when rack_params raises a Rack::Multipart::MultipartPartLimitError' do\n      before do\n        allow(request).to receive(:rack_params).and_raise(Rack::Multipart::MultipartPartLimitError)\n      end\n\n      let(:message) { Grape::Exceptions::TooManyMultipartFiles.new(Rack::Utils.multipart_part_limit).to_s }\n\n      it 'raises an Rack::Multipart::MultipartPartLimitError' do\n        expect { request.params }.to raise_error(Grape::Exceptions::TooManyMultipartFiles, message)\n      end\n    end\n\n    context 'when rack_params raises a Rack::Multipart::MultipartTotalPartLimitError' do\n      before do\n        allow(request).to receive(:rack_params).and_raise(Rack::Multipart::MultipartTotalPartLimitError)\n      end\n\n      let(:message) { Grape::Exceptions::TooManyMultipartFiles.new(Rack::Utils.multipart_part_limit).to_s }\n\n      it 'raises an Rack::Multipart::MultipartPartLimitError' do\n        expect { request.params }.to raise_error(Grape::Exceptions::TooManyMultipartFiles, message)\n      end\n    end\n\n    context 'when rack_params raises a Rack::QueryParser::ParamsTooDeepError' do\n      before do\n        allow(request).to receive(:rack_params).and_raise(Rack::QueryParser::ParamsTooDeepError)\n      end\n\n      let(:message) { Grape::Exceptions::TooDeepParameters.new(Rack::Utils.param_depth_limit).to_s }\n\n      it 'raises a Grape::Exceptions::TooDeepParameters' do\n        expect { request.params }.to raise_error(Grape::Exceptions::TooDeepParameters, message)\n      end\n    end\n\n    context 'when rack_params raises a Rack::Utils::ParameterTypeError' do\n      before do\n        allow(request).to receive(:rack_params).and_raise(Rack::Utils::ParameterTypeError)\n      end\n\n      let(:message) { Grape::Exceptions::ConflictingTypes.new.to_s }\n\n      it 'raises a Grape::Exceptions::ConflictingTypes' do\n        expect { request.params }.to raise_error(Grape::Exceptions::ConflictingTypes, message)\n      end\n    end\n\n    context 'when rack_params raises a Rack::Utils::InvalidParameterError' do\n      before do\n        allow(request).to receive(:rack_params).and_raise(Rack::Utils::InvalidParameterError)\n      end\n\n      let(:message) { Grape::Exceptions::InvalidParameters.new.to_s }\n\n      it 'raises an Rack::Multipart::MultipartPartLimitError' do\n        expect { request.params }.to raise_error(Grape::Exceptions::InvalidParameters, message)\n      end\n    end\n  end\n\n  describe '#headers' do\n    let(:options) do\n      default_options.merge(request_headers)\n    end\n\n    describe 'with http headers in env' do\n      let(:request_headers) do\n        {\n          'HTTP_X_GRAPE_IS_COOL' => 'yeah'\n        }\n      end\n      let(:x_grape_is_cool_header) do\n        'x-grape-is-cool'\n      end\n\n      it 'cuts HTTP_ prefix and capitalizes header name words' do\n        expect(request.headers).to eq(x_grape_is_cool_header => 'yeah')\n      end\n    end\n\n    describe 'with non-HTTP_* stuff in env' do\n      let(:request_headers) do\n        {\n          'HTP_X_GRAPE_ENTITY_TOO' => 'but now we are testing Grape'\n        }\n      end\n\n      it 'does not include them' do\n        expect(request.headers).to eq({})\n      end\n    end\n\n    describe 'with symbolic header names' do\n      let(:request_headers) do\n        {\n          HTTP_GRAPE_LIKES_SYMBOLIC: 'it is true'\n        }\n      end\n      let(:env) do\n        default_env.merge(request_headers)\n      end\n      let(:grape_likes_symbolic_header) do\n        'grape-likes-symbolic'\n      end\n\n      it 'converts them to string' do\n        expect(request.headers).to eq(grape_likes_symbolic_header => 'it is true')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/router/greedy_route_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Grape::Router::GreedyRoute do\n  let(:instance) { described_class.new(pattern, endpoint: endpoint, allow_header: allow_header) }\n  let(:pattern) { :pattern }\n  let(:endpoint) { instance_double(Grape::Endpoint) }\n  let(:allow_header) { false }\n\n  describe 'inheritance' do\n    subject { instance }\n\n    it { is_expected.to be_a(Grape::Router::BaseRoute) }\n  end\n\n  describe '#pattern' do\n    subject { instance.pattern }\n\n    it { is_expected.to eq(pattern) }\n  end\n\n  describe '#endpoint' do\n    subject { instance.endpoint }\n\n    it { is_expected.to eq(endpoint) }\n  end\n\n  describe '#allow_header' do\n    subject { instance.allow_header }\n\n    it { is_expected.to eq(allow_header) }\n  end\n\n  describe '#params' do\n    subject { instance.params }\n\n    it { is_expected.to be_nil }\n  end\nend\n"
  },
  {
    "path": "spec/grape/router_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Router do\n  describe '.normalize_path' do\n    subject { described_class.normalize_path(path) }\n\n    context 'when no leading slash' do\n      let(:path) { 'foo%20bar%20baz' }\n\n      it { is_expected.to eq '/foo%20bar%20baz' }\n    end\n\n    context 'when path ends with slash' do\n      let(:path) { '/foo%20bar%20baz/' }\n\n      it { is_expected.to eq '/foo%20bar%20baz' }\n    end\n\n    context 'when path has recurring slashes' do\n      let(:path) { '////foo%20bar%20baz' }\n\n      it { is_expected.to eq '/foo%20bar%20baz' }\n    end\n\n    context 'when not greedy' do\n      let(:path) { '/foo%20bar%20baz' }\n\n      it { is_expected.to eq '/foo%20bar%20baz' }\n    end\n\n    context 'when encoded string in lowercase' do\n      let(:path) { '/foo%aabar%aabaz' }\n\n      it { is_expected.to eq '/foo%AAbar%AAbaz' }\n    end\n\n    context 'when nil' do\n      let(:path) { nil }\n\n      it { is_expected.to eq '/' }\n    end\n\n    context 'when empty string' do\n      let(:path) { '' }\n\n      it { is_expected.to eq '/' }\n    end\n\n    context 'when encoding is different' do\n      subject { described_class.normalize_path(path).encoding }\n\n      let(:path) { '/foo%AAbar%AAbaz'.b }\n\n      it { is_expected.to eq(Encoding::BINARY) }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/util/inheritable_setting_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Util::InheritableSetting do\n  before do\n    described_class.reset_global!\n    subject.inherit_from parent\n  end\n\n  let(:parent) do\n    described_class.new.tap do |settings|\n      settings.global[:global_thing] = :global_foo_bar\n      settings.namespace[:namespace_thing] = :namespace_foo_bar\n      settings.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar\n      settings.namespace_stackable[:namespace_stackable_thing] = :namespace_stackable_foo_bar\n      settings.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = :namespace_reverse_stackable_foo_bar\n      settings.route[:route_thing] = :route_foo_bar\n    end\n  end\n\n  let(:other_parent) do\n    described_class.new.tap do |settings|\n      settings.namespace[:namespace_thing] = :namespace_foo_bar_other\n      settings.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar_other\n      settings.namespace_stackable[:namespace_stackable_thing] = :namespace_stackable_foo_bar_other\n      settings.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = :namespace_reverse_stackable_foo_bar_other\n      settings.route[:route_thing] = :route_foo_bar_other\n    end\n  end\n\n  describe '#global' do\n    it 'sets a global value' do\n      subject.global[:some_thing] = :foo_bar\n      expect(subject.global[:some_thing]).to eq :foo_bar\n      subject.global[:some_thing] = :foo_bar_next\n      expect(subject.global[:some_thing]).to eq :foo_bar_next\n    end\n\n    it 'sets the global inherited values' do\n      expect(subject.global[:global_thing]).to eq :global_foo_bar\n    end\n\n    it 'overrides global values' do\n      subject.global[:global_thing] = :global_new_foo_bar\n      expect(parent.global[:global_thing]).to eq :global_new_foo_bar\n    end\n\n    it 'handles different parents' do\n      subject.global[:global_thing] = :global_new_foo_bar\n\n      subject.inherit_from other_parent\n\n      expect(parent.global[:global_thing]).to eq :global_new_foo_bar\n      expect(other_parent.global[:global_thing]).to eq :global_new_foo_bar\n    end\n  end\n\n  describe '#api_class' do\n    it 'is specific to the class' do\n      subject.api_class[:some_thing] = :foo_bar\n      parent.api_class[:some_thing] = :some_thing\n\n      expect(subject.api_class[:some_thing]).to eq :foo_bar\n      expect(parent.api_class[:some_thing]).to eq :some_thing\n    end\n  end\n\n  describe '#namespace' do\n    it 'sets a value until the end of a namespace' do\n      subject.namespace[:some_thing] = :foo_bar\n      expect(subject.namespace[:some_thing]).to eq :foo_bar\n    end\n\n    it 'uses new values when a new namespace starts' do\n      subject.namespace[:namespace_thing] = :new_namespace_foo_bar\n      expect(subject.namespace[:namespace_thing]).to eq :new_namespace_foo_bar\n\n      expect(parent.namespace[:namespace_thing]).to eq :namespace_foo_bar\n    end\n  end\n\n  describe '#namespace_inheritable' do\n    it 'works with inheritable values' do\n      expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :namespace_inheritable_foo_bar\n    end\n\n    it 'handles different parents' do\n      expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :namespace_inheritable_foo_bar\n\n      subject.inherit_from other_parent\n\n      expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :namespace_inheritable_foo_bar_other\n\n      subject.inherit_from parent\n\n      expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :namespace_inheritable_foo_bar\n\n      subject.inherit_from other_parent\n\n      subject.namespace_inheritable[:namespace_inheritable_thing] = :my_thing\n\n      expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :my_thing\n\n      subject.inherit_from parent\n\n      expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :my_thing\n    end\n  end\n\n  describe '#namespace_stackable' do\n    it 'works with stackable values' do\n      expect(subject.namespace_stackable[:namespace_stackable_thing]).to eq [:namespace_stackable_foo_bar]\n\n      subject.inherit_from other_parent\n\n      expect(subject.namespace_stackable[:namespace_stackable_thing]).to eq [:namespace_stackable_foo_bar_other]\n    end\n  end\n\n  describe '#namespace_reverse_stackable' do\n    it 'works with reverse stackable values' do\n      expect(subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar]\n\n      subject.inherit_from other_parent\n\n      expect(subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar_other]\n    end\n  end\n\n  describe '#route' do\n    it 'sets a value until the next route' do\n      subject.route[:some_thing] = :foo_bar\n      expect(subject.route[:some_thing]).to eq :foo_bar\n\n      subject.route_end\n\n      expect(subject.route[:some_thing]).to be_nil\n    end\n\n    it 'works with route values' do\n      expect(subject.route[:route_thing]).to eq :route_foo_bar\n    end\n  end\n\n  describe '#api_class' do\n    it 'is specific to the class' do\n      subject.api_class[:some_thing] = :foo_bar\n      expect(subject.api_class[:some_thing]).to eq :foo_bar\n    end\n  end\n\n  describe '#inherit_from' do\n    it 'notifies clones' do\n      new_settings = subject.point_in_time_copy\n      expect(new_settings).to receive(:inherit_from).with(other_parent)\n\n      subject.inherit_from other_parent\n    end\n  end\n\n  describe '#point_in_time_copy' do\n    let!(:cloned_obj) { subject.point_in_time_copy }\n\n    it 'resets point_in_time_copies' do\n      expect(cloned_obj.point_in_time_copies).to be_empty\n    end\n\n    it 'decouples namespace values' do\n      subject.namespace[:namespace_thing] = :namespace_foo_bar\n\n      cloned_obj.namespace[:namespace_thing] = :new_namespace_foo_bar\n      expect(subject.namespace[:namespace_thing]).to eq :namespace_foo_bar\n    end\n\n    it 'decouples namespace inheritable values' do\n      expect(cloned_obj.namespace_inheritable[:namespace_inheritable_thing]).to eq :namespace_inheritable_foo_bar\n\n      subject.namespace_inheritable[:namespace_inheritable_thing] = :my_thing\n      expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :my_thing\n\n      expect(cloned_obj.namespace_inheritable[:namespace_inheritable_thing]).to eq :namespace_inheritable_foo_bar\n\n      cloned_obj.namespace_inheritable[:namespace_inheritable_thing] = :my_cloned_thing\n      expect(cloned_obj.namespace_inheritable[:namespace_inheritable_thing]).to eq :my_cloned_thing\n      expect(subject.namespace_inheritable[:namespace_inheritable_thing]).to eq :my_thing\n    end\n\n    it 'decouples namespace stackable values' do\n      expect(cloned_obj.namespace_stackable[:namespace_stackable_thing]).to eq [:namespace_stackable_foo_bar]\n\n      subject.namespace_stackable[:namespace_stackable_thing] = :other_thing\n      expect(subject.namespace_stackable[:namespace_stackable_thing]).to eq %i[namespace_stackable_foo_bar other_thing]\n      expect(cloned_obj.namespace_stackable[:namespace_stackable_thing]).to eq [:namespace_stackable_foo_bar]\n    end\n\n    it 'decouples namespace reverse stackable values' do\n      expect(cloned_obj.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar]\n\n      subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = :other_thing\n      expect(subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq %i[other_thing namespace_reverse_stackable_foo_bar]\n      expect(cloned_obj.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar]\n    end\n\n    it 'decouples route values' do\n      expect(cloned_obj.route[:route_thing]).to eq :route_foo_bar\n\n      subject.route[:route_thing] = :new_route_foo_bar\n      expect(cloned_obj.route[:route_thing]).to eq :route_foo_bar\n    end\n\n    it 'adds itself to original as clone' do\n      expect(subject.point_in_time_copies).to include(cloned_obj)\n    end\n  end\n\n  describe '#to_hash' do\n    it 'return all settings as a hash' do\n      subject.global[:global_thing] = :global_foo_bar\n      subject.namespace[:namespace_thing] = :namespace_foo_bar\n      subject.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar\n      subject.namespace_stackable[:namespace_stackable_thing] = [:namespace_stackable_foo_bar]\n      subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = [:namespace_reverse_stackable_foo_bar]\n      subject.route[:route_thing] = :route_foo_bar\n      expect(subject.to_hash).to match(\n        global: { global_thing: :global_foo_bar },\n        namespace: { namespace_thing: :namespace_foo_bar },\n        namespace_inheritable: {\n          namespace_inheritable_thing: :namespace_inheritable_foo_bar\n        },\n        namespace_stackable: { namespace_stackable_thing: [:namespace_stackable_foo_bar, [:namespace_stackable_foo_bar]] },\n        namespace_reverse_stackable:\n          { namespace_reverse_stackable_thing: [[:namespace_reverse_stackable_foo_bar], :namespace_reverse_stackable_foo_bar] },\n        route: { route_thing: :route_foo_bar }\n      )\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/util/inheritable_values_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Util::InheritableValues do\n  subject { described_class.new(parent) }\n\n  let(:parent) { described_class.new }\n\n  describe '#delete' do\n    it 'deletes a key' do\n      subject[:some_thing] = :new_foo_bar\n      subject.delete :some_thing\n      expect(subject[:some_thing]).to be_nil\n    end\n\n    it 'does not delete parent values' do\n      parent[:some_thing] = :foo\n      subject[:some_thing] = :new_foo_bar\n      subject.delete :some_thing\n      expect(subject[:some_thing]).to eq :foo\n    end\n  end\n\n  describe '#[]' do\n    it 'returns a value' do\n      subject[:some_thing] = :foo\n      expect(subject[:some_thing]).to eq :foo\n    end\n\n    it 'returns parent value when no value is set' do\n      parent[:some_thing] = :foo\n      expect(subject[:some_thing]).to eq :foo\n    end\n\n    it 'overwrites parent value with the current one' do\n      parent[:some_thing] = :foo\n      subject[:some_thing] = :foo_bar\n      expect(subject[:some_thing]).to eq :foo_bar\n    end\n\n    it 'parent values are not changed' do\n      parent[:some_thing] = :foo\n      subject[:some_thing] = :foo_bar\n      expect(parent[:some_thing]).to eq :foo\n    end\n  end\n\n  describe '#[]=' do\n    it 'sets a value' do\n      subject[:some_thing] = :foo\n      expect(subject[:some_thing]).to eq :foo\n    end\n  end\n\n  describe '#to_hash' do\n    it 'returns a Hash representation' do\n      parent[:some_thing] = :foo\n      subject[:some_thing_more] = :foo_bar\n      expect(subject.to_hash).to eq(some_thing: :foo, some_thing_more: :foo_bar)\n    end\n  end\n\n  describe '#clone' do\n    let(:obj_cloned) { subject.clone }\n\n    context 'complex (i.e. not primitive) data types (ex. entity classes, please see bug #891)' do\n      let(:description) { { entity: double } }\n\n      before { subject[:description] = description }\n\n      it 'copies values; does not duplicate them' do\n        expect(obj_cloned[:description]).to eq description\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/util/media_type_spec.rb",
    "content": "# frozen_string_literal: true\n\nRSpec.describe Grape::Util::MediaType do\n  shared_examples 'MediaType' do\n    it { is_expected.to eq(described_class.new(type: type, subtype: subtype)) }\n  end\n\n  describe '.parse' do\n    subject(:media_type) { described_class.parse(header) }\n\n    context 'when header blank?' do\n      let(:header) { nil }\n\n      it { is_expected.to be_nil }\n    end\n\n    context 'when header is not a mime type' do\n      let(:header) { 'abc' }\n\n      it { is_expected.to be_nil }\n    end\n\n    context 'when header is a valid mime type' do\n      let(:header) { [type, subtype].join('/') }\n      let(:type) { 'text' }\n      let(:subtype) { 'html' }\n\n      it_behaves_like 'MediaType'\n\n      context 'when header is a vendor mime type' do\n        let(:type) { 'application' }\n        let(:subtype) { 'vnd.test-v1+json' }\n\n        it_behaves_like 'MediaType'\n      end\n\n      context 'when header is a vendor mime type without version' do\n        let(:type) { 'application' }\n        let(:subtype) { 'vnd.ms-word' }\n\n        it_behaves_like 'MediaType'\n      end\n    end\n  end\n\n  describe '.match?' do\n    subject { described_class.match?(media_type) }\n\n    context 'when media_type is blank?' do\n      let(:media_type) { nil }\n\n      it { is_expected.to be_falsey }\n    end\n\n    context 'when header is not a mime type' do\n      let(:media_type) { 'abc' }\n\n      it { is_expected.to be_falsey }\n    end\n\n    context 'when header is a valid mime type but not vendor' do\n      let(:media_type) { 'text/html' }\n\n      it { is_expected.to be_falsey }\n    end\n\n    context 'when header is a vendor mime type' do\n      let(:media_type) { 'application/vnd.test-v1+json' }\n\n      it { is_expected.to be_truthy }\n    end\n  end\n\n  describe '.best_quality' do\n    subject(:media_type) { described_class.best_quality(header, available_media_types) }\n\n    let(:available_media_types) { %w[application/json text/html] }\n\n    context 'when header is blank?' do\n      let(:header) { nil }\n      let(:type) { 'application' }\n      let(:subtype) { 'json' }\n\n      it_behaves_like 'MediaType'\n    end\n\n    context 'when header is not blank' do\n      let(:header) { [type, subtype].join('/') }\n      let(:type) { 'text' }\n      let(:subtype) { 'html' }\n\n      it 'calls Rack::Utils.best_q_match' do\n        allow(Rack::Utils).to receive(:best_q_match).and_call_original\n        expect(media_type).to eq(described_class.new(type: type, subtype: subtype))\n      end\n    end\n  end\n\n  describe '.==' do\n    subject { described_class.new(type: type, subtype: subtype) }\n\n    let(:type) { 'application' }\n    let(:subtype) { 'vnd.test-v1+json' }\n    let(:other_media_type_class) { Class.new(Struct.new(:type, :subtype, :vendor, :version, :format)) }\n    let(:other_media_type_instance) { other_media_type_class.new(type, subtype, 'test', 'v1', 'json') }\n\n    it { is_expected.not_to eq(other_media_type_class.new(type, subtype, 'test', 'v1', 'json')) }\n  end\n\n  describe '.hash' do\n    subject { Set.new([described_class.new(type: type, subtype: subtype)]) }\n\n    let(:type) { 'text' }\n    let(:subtype) { 'html' }\n\n    it { is_expected.to include(described_class.new(type: type, subtype: subtype)) }\n  end\nend\n"
  },
  {
    "path": "spec/grape/util/registry_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Util::Registry do\n  # Create a test class that includes the Registry module\n  subject { test_registry_class.new }\n\n  let(:test_registry_class) do\n    Class.new do\n      include Grape::Util::Registry\n\n      # Public methods to expose private functionality for testing\n      def registry_empty?\n        registry.empty?\n      end\n\n      def registry_get(key)\n        registry[key]\n      end\n    end\n  end\n\n  describe '#register' do\n    let(:test_class) do\n      Class.new do\n        def self.name\n          'TestModule::TestClass'\n        end\n      end\n    end\n\n    let(:simple_class) do\n      Class.new do\n        def self.name\n          'SimpleClass'\n        end\n      end\n    end\n\n    let(:camel_case_class) do\n      Class.new do\n        def self.name\n          'CamelCaseClass'\n        end\n      end\n    end\n\n    let(:anonymous_class) { Class.new }\n\n    let(:nil_name_class) do\n      Class.new do\n        def self.name\n          nil\n        end\n      end\n    end\n\n    let(:empty_name_class) do\n      Class.new do\n        def self.name\n          ''\n        end\n      end\n    end\n\n    context 'with valid class names' do\n      it 'registers a class with demodulized and underscored name' do\n        subject.register(test_class)\n        expect(subject.registry_get('test_class')).to eq(test_class)\n      end\n\n      it 'registers a simple class name correctly' do\n        subject.register(simple_class)\n        expect(subject.registry_get('simple_class')).to eq(simple_class)\n      end\n\n      it 'handles camel case class names' do\n        subject.register(camel_case_class)\n        expect(subject.registry_get('camel_case_class')).to eq(camel_case_class)\n      end\n\n      it 'uses indifferent access for registry keys' do\n        subject.register(test_class)\n        expect(subject.registry_get(:test_class)).to eq(test_class)\n        expect(subject.registry_get('test_class')).to eq(test_class)\n      end\n    end\n\n    context 'with invalid class names' do\n      it 'does not register anonymous classes' do\n        subject.register(anonymous_class)\n        expect(subject.registry_empty?).to be true\n      end\n\n      it 'does not register classes with nil names' do\n        subject.register(nil_name_class)\n        expect(subject.registry_empty?).to be true\n      end\n\n      it 'does not register classes with empty names' do\n        subject.register(empty_name_class)\n        expect(subject.registry_empty?).to be true\n      end\n    end\n\n    context 'with duplicate registrations' do\n      it 'warns when registering a duplicate short name' do\n        expect do\n          subject.register(test_class)\n          subject.register(test_class)\n        end.to output(/test_class is already registered with class.*It will be overridden/).to_stderr\n      end\n\n      it 'warns with correct short name for different class types' do\n        expect do\n          subject.register(simple_class)\n          subject.register(simple_class)\n        end.to output(/simple_class is already registered with class.*It will be overridden/).to_stderr\n      end\n\n      it 'warns with correct short name for camel case classes' do\n        expect do\n          subject.register(camel_case_class)\n          subject.register(camel_case_class)\n        end.to output(/camel_case_class is already registered with class.*It will be overridden/).to_stderr\n      end\n\n      it 'warns for each duplicate registration' do\n        expect do\n          subject.register(test_class)\n          subject.register(test_class)\n          subject.register(test_class) # Third registration should warn again\n        end.to output(/test_class is already registered with class.*It will be overridden/).to_stderr\n      end\n\n      it 'warns with exact message format' do\n        expected_message = \"test_class is already registered with class #{test_class}. It will be overridden globally with the following: #{test_class.name}\"\n        expect do\n          subject.register(test_class)\n          subject.register(test_class)\n        end.to output(/#{Regexp.escape(expected_message)}/).to_stderr\n      end\n\n      it 'overwrites existing registration when duplicate short name is registered' do\n        subject.register(test_class)\n        subject.register(test_class)\n\n        expect(subject.registry_get('test_class')).to eq(test_class)\n      end\n    end\n  end\n\n  describe 'edge cases' do\n    it 'handles classes with special characters in names' do\n      special_class = Class.new do\n        def self.name\n          'Special::Class::With::Many::Modules'\n        end\n      end\n\n      subject.register(special_class)\n      expect(subject.registry_get('modules')).to eq(special_class)\n    end\n\n    it 'handles classes with numbers in names' do\n      numbered_class = Class.new do\n        def self.name\n          'Class123WithNumbers'\n        end\n      end\n\n      subject.register(numbered_class)\n      expect(subject.registry_get('class123_with_numbers')).to eq(numbered_class)\n    end\n\n    it 'handles classes with acronyms' do\n      acronym_class = Class.new do\n        def self.name\n          'API::HTTPClient'\n        end\n      end\n\n      subject.register(acronym_class)\n      expect(subject.registry_get('http_client')).to eq(acronym_class)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/util/reverse_stackable_values_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Util::ReverseStackableValues do\n  subject { described_class.new(parent) }\n\n  let(:parent) { described_class.new }\n\n  describe '#keys' do\n    it 'returns all keys' do\n      subject[:some_thing] = :foo_bar\n      subject[:some_thing_else] = :foo_bar\n      expect(subject.keys).to eq %i[some_thing some_thing_else].sort\n    end\n\n    it 'returns merged keys with parent' do\n      parent[:some_thing] = :foo\n      parent[:some_thing_else] = :foo\n\n      subject[:some_thing] = :foo_bar\n      subject[:some_thing_more] = :foo_bar\n\n      expect(subject.keys).to eq %i[some_thing some_thing_else some_thing_more].sort\n    end\n  end\n\n  describe '#delete' do\n    it 'deletes a key' do\n      subject[:some_thing] = :new_foo_bar\n      subject.delete :some_thing\n      expect(subject[:some_thing]).to eq []\n    end\n\n    it 'does not delete parent values' do\n      parent[:some_thing] = :foo\n      subject[:some_thing] = :new_foo_bar\n      subject.delete :some_thing\n      expect(subject[:some_thing]).to eq [:foo]\n    end\n  end\n\n  describe '#[]' do\n    it 'returns an array of values' do\n      subject[:some_thing] = :foo\n      expect(subject[:some_thing]).to eq [:foo]\n    end\n\n    it 'returns parent value when no value is set' do\n      parent[:some_thing] = :foo\n      expect(subject[:some_thing]).to eq [:foo]\n    end\n\n    it 'combines parent and actual values (actual first)' do\n      parent[:some_thing] = :foo\n      subject[:some_thing] = :foo_bar\n      expect(subject[:some_thing]).to eq %i[foo_bar foo]\n    end\n\n    it 'parent values are not changed' do\n      parent[:some_thing] = :foo\n      subject[:some_thing] = :foo_bar\n      expect(parent[:some_thing]).to eq [:foo]\n    end\n  end\n\n  describe '#[]=' do\n    it 'sets a value' do\n      subject[:some_thing] = :foo\n      expect(subject[:some_thing]).to eq [:foo]\n    end\n\n    it 'pushes further values' do\n      subject[:some_thing] = :foo\n      subject[:some_thing] = :bar\n      expect(subject[:some_thing]).to eq %i[foo bar]\n    end\n\n    it 'can handle array values' do\n      subject[:some_thing] = :foo\n      subject[:some_thing] = %i[bar more]\n      expect(subject[:some_thing]).to eq [:foo, %i[bar more]]\n\n      parent[:some_thing_else] = %i[foo bar]\n      subject[:some_thing_else] = %i[some bar foo]\n\n      expect(subject[:some_thing_else]).to eq [%i[some bar foo], %i[foo bar]]\n    end\n  end\n\n  describe '#to_hash' do\n    it 'returns a Hash representation' do\n      parent[:some_thing] = :foo\n      subject[:some_thing] = %i[bar more]\n      subject[:some_thing_more] = :foo_bar\n      expect(subject.to_hash).to eq(\n        some_thing: [%i[bar more], :foo],\n        some_thing_more: [:foo_bar]\n      )\n    end\n  end\n\n  describe '#clone' do\n    let(:obj_cloned) { subject.clone }\n\n    it 'copies all values' do\n      parent = described_class.new\n      child = described_class.new parent\n      grandchild = described_class.new child\n\n      parent[:some_thing] = :foo\n      child[:some_thing] = %i[bar more]\n      grandchild[:some_thing] = :grand_foo_bar\n      grandchild[:some_thing_more] = :foo_bar\n\n      expect(grandchild.clone.to_hash).to eq(\n        some_thing: [:grand_foo_bar, %i[bar more], :foo],\n        some_thing_more: [:foo_bar]\n      )\n    end\n\n    context 'complex (i.e. not primitive) data types (ex. middleware, please see bug #930)' do\n      let(:middleware) { double }\n\n      before { subject[:middleware] = middleware }\n\n      it 'copies values; does not duplicate them' do\n        expect(obj_cloned[:middleware]).to eq [middleware]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/util/stackable_values_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Util::StackableValues do\n  subject { described_class.new(parent) }\n\n  let(:parent) { described_class.new }\n\n  describe '#keys' do\n    it 'returns all keys' do\n      subject[:some_thing] = :foo_bar\n      subject[:some_thing_else] = :foo_bar\n      expect(subject.keys).to eq %i[some_thing some_thing_else].sort\n    end\n\n    it 'returns merged keys with parent' do\n      parent[:some_thing] = :foo\n      parent[:some_thing_else] = :foo\n\n      subject[:some_thing] = :foo_bar\n      subject[:some_thing_more] = :foo_bar\n\n      expect(subject.keys).to eq %i[some_thing some_thing_else some_thing_more].sort\n    end\n  end\n\n  describe '#delete' do\n    it 'deletes a key' do\n      subject[:some_thing] = :new_foo_bar\n      subject.delete :some_thing\n      expect(subject[:some_thing]).to eq []\n    end\n\n    it 'does not delete parent values' do\n      parent[:some_thing] = :foo\n      subject[:some_thing] = :new_foo_bar\n      subject.delete :some_thing\n      expect(subject[:some_thing]).to eq [:foo]\n    end\n  end\n\n  describe '#[]' do\n    it 'returns an array of values' do\n      subject[:some_thing] = :foo\n      expect(subject[:some_thing]).to eq [:foo]\n    end\n\n    it 'returns parent value when no value is set' do\n      parent[:some_thing] = :foo\n      expect(subject[:some_thing]).to eq [:foo]\n    end\n\n    it 'combines parent and actual values' do\n      parent[:some_thing] = :foo\n      subject[:some_thing] = :foo_bar\n      expect(subject[:some_thing]).to eq %i[foo foo_bar]\n    end\n\n    it 'parent values are not changed' do\n      parent[:some_thing] = :foo\n      subject[:some_thing] = :foo_bar\n      expect(parent[:some_thing]).to eq [:foo]\n    end\n  end\n\n  describe '#[]=' do\n    it 'sets a value' do\n      subject[:some_thing] = :foo\n      expect(subject[:some_thing]).to eq [:foo]\n    end\n\n    it 'pushes further values' do\n      subject[:some_thing] = :foo\n      subject[:some_thing] = :bar\n      expect(subject[:some_thing]).to eq %i[foo bar]\n    end\n\n    it 'can handle array values' do\n      subject[:some_thing] = :foo\n      subject[:some_thing] = %i[bar more]\n      expect(subject[:some_thing]).to eq [:foo, %i[bar more]]\n\n      parent[:some_thing_else] = %i[foo bar]\n      subject[:some_thing_else] = %i[some bar foo]\n\n      expect(subject[:some_thing_else]).to eq [%i[foo bar], %i[some bar foo]]\n    end\n  end\n\n  describe '#to_hash' do\n    it 'returns a Hash representation' do\n      parent[:some_thing] = :foo\n      subject[:some_thing] = %i[bar more]\n      subject[:some_thing_more] = :foo_bar\n      expect(subject.to_hash).to eq(some_thing: [:foo, %i[bar more]], some_thing_more: [:foo_bar])\n    end\n  end\n\n  describe '#clone' do\n    let(:obj_cloned) { subject.clone }\n\n    it 'copies all values' do\n      parent = described_class.new\n      child = described_class.new parent\n      grandchild = described_class.new child\n\n      parent[:some_thing] = :foo\n      child[:some_thing] = %i[bar more]\n      grandchild[:some_thing] = :grand_foo_bar\n      grandchild[:some_thing_more] = :foo_bar\n\n      expect(grandchild.clone.to_hash).to eq(some_thing: [:foo, %i[bar more], :grand_foo_bar], some_thing_more: [:foo_bar])\n    end\n\n    context 'complex (i.e. not primitive) data types (ex. middleware, please see bug #930)' do\n      let(:middleware) { double }\n\n      before { subject[:middleware] = middleware }\n\n      it 'copies values; does not duplicate them' do\n        expect(obj_cloned[:middleware]).to eq [middleware]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/util/translation_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Util::Translation do\n  subject(:translator) do\n    Class.new do\n      include Grape::Util::Translation\n\n      def translate_message(key, **opts)\n        translate(key, **opts)\n      end\n    end.new\n  end\n\n  describe '#translate_message' do\n    context 'when the translation value uses a reserved I18n interpolation key' do\n      around do |example|\n        I18n.backend.store_translations(:en, grape: { errors: { messages: { reserved_key_test: 'value %{scope}' } } }) # rubocop:disable Style/FormatStringToken\n        example.run\n      ensure\n        I18n.reload!\n      end\n\n      it 'raises I18n::ReservedInterpolationKey' do\n        expect { translator.translate_message(:reserved_key_test) }.to raise_error(I18n::ReservedInterpolationKey)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/multiple_attributes_iterator_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::MultipleAttributesIterator do\n  describe '#each' do\n    subject(:iterator) { described_class.new(validator, scope, params) }\n\n    let(:scope) { Grape::Validations::ParamsScope.new(api: Class.new(Grape::API)) }\n    let(:validator) { double(attrs: %i[first second third]) }\n\n    context 'when params is a hash' do\n      let(:params) do\n        { first: 'string', second: 'string' }\n      end\n\n      it 'yields the whole params hash without the list of attrs' do\n        expect { |b| iterator.each(&b) }.to yield_with_args(params)\n      end\n    end\n\n    context 'when params is an array' do\n      let(:params) do\n        [{ first: 'string1', second: 'string1' }, { first: 'string2', second: 'string2' }]\n      end\n\n      it 'yields each element of the array without the list of attrs' do\n        expect { |b| iterator.each(&b) }.to yield_successive_args(params[0], params[1])\n      end\n    end\n\n    context 'when params is empty optional placeholder' do\n      let(:params) { [Grape::DSL::Parameters::EmptyOptionalValue] }\n\n      it 'does not yield it' do\n        expect { |b| iterator.each(&b) }.to yield_successive_args\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/param_scope_tracker_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::ParamScopeTracker do\n  describe '.current' do\n    it 'returns nil when no tracker is active' do\n      expect(described_class.current).to be_nil\n    end\n  end\n\n  describe '.track' do\n    it 'sets .current inside the block' do\n      described_class.track do\n        expect(described_class.current).to be_a(described_class)\n      end\n    end\n\n    it 'restores nil after the block' do\n      described_class.track { nil }\n      expect(described_class.current).to be_nil\n    end\n\n    it 'restores nil after an exception' do\n      expect { described_class.track { raise 'boom' } }.to raise_error('boom')\n      expect(described_class.current).to be_nil\n    end\n\n    it 'creates a fresh tracker for each invocation' do\n      first = nil\n      second = nil\n      described_class.track { first = described_class.current }\n      described_class.track { second = described_class.current }\n      expect(first).not_to equal(second)\n    end\n\n    context 'when nested (reentrant)' do\n      it 'restores the outer tracker, not nil' do\n        outer = nil\n        inner = nil\n\n        described_class.track do\n          outer = described_class.current\n          described_class.track { inner = described_class.current }\n          expect(described_class.current).to equal(outer)\n        end\n\n        expect(inner).not_to equal(outer)\n        expect(described_class.current).to be_nil\n      end\n\n      it 'restores outer tracker after inner raises' do\n        described_class.track do\n          outer = described_class.current\n          expect { described_class.track { raise 'inner' } }.to raise_error('inner')\n          expect(described_class.current).to equal(outer)\n        end\n      end\n    end\n  end\n\n  describe '#store_index / #index_for' do\n    subject(:tracker) { described_class.new }\n\n    let(:scope_a) { instance_double(Grape::Validations::ParamsScope) }\n    let(:scope_b) { instance_double(Grape::Validations::ParamsScope) }\n\n    it 'returns nil for an unknown scope' do\n      expect(tracker.index_for(scope_a)).to be_nil\n    end\n\n    it 'returns the stored index for the given scope' do\n      tracker.store_index(scope_a, 3)\n      expect(tracker.index_for(scope_a)).to eq(3)\n    end\n\n    it 'stores indices independently per scope' do\n      tracker.store_index(scope_a, 0)\n      tracker.store_index(scope_b, 7)\n      expect(tracker.index_for(scope_a)).to eq(0)\n      expect(tracker.index_for(scope_b)).to eq(7)\n    end\n\n    it 'uses object identity, not value equality, as the key' do\n      equal_double = instance_double(Grape::Validations::ParamsScope)\n      tracker.store_index(scope_a, 1)\n      expect(tracker.index_for(equal_double)).to be_nil\n    end\n\n    it 'overwrites a previously stored index' do\n      tracker.store_index(scope_a, 1)\n      tracker.store_index(scope_a, 5)\n      expect(tracker.index_for(scope_a)).to eq(5)\n    end\n  end\n\n  describe '#store_qualifying_params / #qualifying_params' do\n    subject(:tracker) { described_class.new }\n\n    let(:scope) { instance_double(Grape::Validations::ParamsScope) }\n\n    it 'returns EMPTY_PARAMS for an unknown scope' do\n      expect(tracker.qualifying_params(scope)).to equal(described_class::EMPTY_PARAMS)\n    end\n\n    it 'returns the stored params for the given scope' do\n      params = [{ id: 1 }, { id: 2 }]\n      tracker.store_qualifying_params(scope, params)\n      expect(tracker.qualifying_params(scope)).to eq(params)\n    end\n\n    it 'treats an explicitly stored empty array the same as never stored (blank)' do\n      tracker.store_qualifying_params(scope, [])\n      expect(tracker.qualifying_params(scope).presence).to be_nil\n    end\n\n    it 'uses object identity as the key' do\n      other_scope = instance_double(Grape::Validations::ParamsScope)\n      tracker.store_qualifying_params(scope, [{ id: 1 }])\n      expect(tracker.qualifying_params(other_scope)).to equal(described_class::EMPTY_PARAMS)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/params_documentation_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::ParamsDocumentation do\n  subject { klass.new(api_double) }\n\n  let(:api_double) do\n    Class.new do\n      include Grape::DSL::Settings\n    end.new\n  end\n\n  let(:klass) do\n    Class.new do\n      include Grape::Validations::ParamsDocumentation\n\n      attr_accessor :api\n\n      def initialize(api)\n        @api = api\n      end\n\n      def full_name(name)\n        \"full_name_#{name}\"\n      end\n    end\n  end\n\n  describe '#document_params' do\n    it 'stores documented params with all details' do\n      attrs = %i[foo bar]\n      validations = {\n        presence: true,\n        default: 42,\n        length: { min: 1, max: 10 },\n        desc: 'A foo',\n        documentation: { note: 'doc' }\n      }\n      type = Integer\n      values = [1, 2, 3]\n      except_values = [4, 5, 6]\n      subject.document_params(attrs, validations.dup, type, values, except_values)\n      expect(api_double.inheritable_setting.namespace_stackable[:params].first.keys).to include('full_name_foo', 'full_name_bar')\n      expect(api_double.inheritable_setting.namespace_stackable[:params].first['full_name_foo']).to include(\n        required: true,\n        type: 'Integer',\n        values: [1, 2, 3],\n        except_values: [4, 5, 6],\n        default: 42,\n        min_length: 1,\n        max_length: 10,\n        desc: 'A foo',\n        documentation: { note: 'doc' }\n      )\n    end\n\n    context 'when do_not_document is set' do\n      let(:validations) do\n        { desc: 'desc', description: 'description', documentation: { foo: 'bar' }, another_param: 'test' }\n      end\n\n      before do\n        api_double.inheritable_setting.namespace_inheritable[:do_not_document] = true\n      end\n\n      it 'removes desc, description, and documentation' do\n        subject.document_params([:foo], validations)\n        expect(validations).to eq({ another_param: 'test' })\n      end\n    end\n\n    context 'when validation is empty' do\n      let(:validations) do\n        {}\n      end\n\n      it 'does not raise an error' do\n        expect { subject.document_params([:foo], validations) }.not_to raise_error\n        expect(api_double.inheritable_setting.namespace_stackable[:params].first['full_name_foo']).to eq({ required: false })\n      end\n    end\n\n    context 'when desc is not present' do\n      let(:validations) do\n        { description: 'desc2' }\n      end\n\n      it 'uses description if desc is not present' do\n        subject.document_params([:foo], validations)\n        expect(api_double.inheritable_setting.namespace_stackable[:params].first['full_name_foo'][:desc]).to eq('desc2')\n      end\n    end\n\n    context 'when desc nor description is present' do\n      let(:validations) do\n        {}\n      end\n\n      it 'uses description if desc is not present' do\n        subject.document_params([:foo], validations)\n        expect(api_double.inheritable_setting.namespace_stackable[:params].first['full_name_foo']).to eq({ required: false })\n      end\n    end\n\n    context 'when documentation is not present' do\n      let(:validations) do\n        {}\n      end\n\n      it 'does not include documentation' do\n        subject.document_params([:foo], validations)\n        expect(api_double.inheritable_setting.namespace_stackable[:params].first['full_name_foo']).not_to have_key(:documentation)\n      end\n    end\n\n    context 'when type is nil' do\n      let(:validations) do\n        { presence: true }\n      end\n\n      it 'sets type as nil' do\n        subject.document_params([:foo], validations)\n        expect(api_double.inheritable_setting.namespace_stackable[:params].first['full_name_foo'][:type]).to be_nil\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/params_scope_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::ParamsScope do\n  subject do\n    Class.new(Grape::API)\n  end\n\n  def app\n    subject\n  end\n\n  context 'when using custom types' do\n    let(:custom_type) do\n      Class.new do\n        attr_reader :value\n\n        def self.parse(value)\n          raise if value == 'invalid'\n\n          new(value)\n        end\n\n        def initialize(value)\n          @value = value\n        end\n      end\n    end\n\n    it 'coerces the parameter via the type\\'s parse method' do\n      context = self\n      subject.params do\n        requires :foo, type: context.custom_type\n      end\n      subject.get('/types') { params[:foo].value }\n\n      get '/types', foo: 'valid'\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('valid')\n\n      get '/types', foo: 'invalid'\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to match(/foo is invalid/)\n    end\n  end\n\n  context 'param renaming' do\n    it do\n      subject.params do\n        requires :foo, as: :bar\n        optional :super, as: :hiper\n      end\n      subject.get('/renaming') { \"#{declared(params)['bar']}-#{declared(params)['hiper']}\" }\n      get '/renaming', foo: 'any', super: 'any2'\n\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('any-any2')\n    end\n\n    it do\n      subject.params do\n        requires :foo, as: :bar, type: String, coerce_with: lambda(&:strip)\n      end\n      subject.get('/renaming-coerced') { \"#{params['bar']}-#{params['foo']}\" }\n      get '/renaming-coerced', foo: ' there we go '\n\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('-there we go')\n    end\n\n    it do\n      subject.params do\n        requires :foo, as: :bar, allow_blank: false\n      end\n      subject.get('/renaming-not-blank') {}\n      get '/renaming-not-blank', foo: ''\n\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('foo is empty')\n    end\n\n    it do\n      subject.params do\n        requires :foo, as: :bar, allow_blank: false\n      end\n      subject.get('/renaming-not-blank-with-value') {}\n      get '/renaming-not-blank-with-value', foo: 'any'\n\n      expect(last_response.status).to eq(200)\n    end\n\n    it do\n      subject.params do\n        requires :foo, as: :baz, type: Hash do\n          requires :bar, as: :qux\n        end\n      end\n      subject.get('/nested-renaming') { declared(params).to_json }\n      get '/nested-renaming', foo: { bar: 'any' }\n\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('{\"baz\":{\"qux\":\"any\"}}')\n    end\n\n    it 'renaming can be defined before default' do\n      subject.params do\n        optional :foo, as: :bar, default: 'before'\n      end\n      subject.get('/rename-before-default') { declared(params)[:bar] }\n      get '/rename-before-default'\n\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('before')\n    end\n\n    it 'renaming can be defined after default' do\n      subject.params do\n        optional :foo, default: 'after', as: :bar\n      end\n      subject.get('/rename-after-default') { declared(params)[:bar] }\n      get '/rename-after-default'\n\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('after')\n    end\n  end\n\n  context 'array without coerce type explicitly given' do\n    it 'sets the type based on first element' do\n      subject.params do\n        requires :periods, type: Array, values: -> { %w[day month] }\n      end\n      subject.get('/required') { 'required works' }\n\n      get '/required', periods: %w[day month]\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('required works')\n    end\n\n    it 'fails to call API without Array type' do\n      subject.params do\n        requires :periods, type: Array, values: -> { %w[day month] }\n      end\n      subject.get('/required') { 'required works' }\n\n      get '/required', periods: 'day'\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('periods is invalid')\n    end\n\n    it 'raises exception when values are of different type' do\n      expect do\n        subject.params { requires :numbers, type: Array, values: [1, 'definitely not a number', 3] }\n      end.to raise_error Grape::Exceptions::IncompatibleOptionValues\n    end\n\n    it 'raises exception when range values have different endpoint types' do\n      expect do\n        subject.params { requires :numbers, type: Array, values: 0.0..10 }\n      end.to raise_error Grape::Exceptions::IncompatibleOptionValues\n    end\n  end\n\n  context 'coercing values validation with proc' do\n    it 'allows the proc to pass validation without checking' do\n      subject.params { requires :numbers, type: Integer, values: -> { [0, 1, 2] } }\n\n      subject.post('/required') { 'coercion with proc works' }\n      post '/required', numbers: '1'\n      expect(last_response.status).to eq(201)\n      expect(last_response.body).to eq('coercion with proc works')\n    end\n\n    it 'allows the proc to pass validation without checking in value' do\n      subject.params { requires :numbers, type: Integer, values: { value: -> { [0, 1, 2] } } }\n\n      subject.post('/required') { 'coercion with proc works' }\n      post '/required', numbers: '1'\n      expect(last_response.status).to eq(201)\n      expect(last_response.body).to eq('coercion with proc works')\n    end\n\n    it 'allows the proc to pass validation without checking in except' do\n      subject.params { requires :numbers, type: Integer, except_values: -> { [0, 1, 2] } }\n\n      subject.post('/required') { 'coercion with proc works' }\n      post '/required', numbers: '10'\n      expect(last_response.status).to eq(201)\n      expect(last_response.body).to eq('coercion with proc works')\n    end\n  end\n\n  context 'a Set with coerce type explicitly given' do\n    context 'and the values are allowed' do\n      it 'does not raise an exception' do\n        expect do\n          subject.params { optional :numbers, type: Set[Integer], values: 0..2, default: 0..2 }\n        end.not_to raise_error\n      end\n    end\n\n    context 'and the values are not allowed' do\n      it 'raises exception' do\n        expect do\n          subject.params { optional :numbers, type: Set[Integer], values: %w[a b] }\n        end.to raise_error Grape::Exceptions::IncompatibleOptionValues\n      end\n    end\n  end\n\n  context 'with range values' do\n    context \"when left range endpoint isn't #kind_of? the type\" do\n      it 'raises exception' do\n        expect do\n          subject.params { requires :latitude, type: Integer, values: -90.0..90 }\n        end.to raise_error Grape::Exceptions::IncompatibleOptionValues\n      end\n    end\n\n    context \"when right range endpoint isn't #kind_of? the type\" do\n      it 'raises exception' do\n        expect do\n          subject.params { requires :latitude, type: Integer, values: -90..90.0 }\n        end.to raise_error Grape::Exceptions::IncompatibleOptionValues\n      end\n    end\n\n    context 'when the default is an array' do\n      context 'and is the entire range of allowed values' do\n        it 'does not raise an exception' do\n          expect do\n            subject.params { optional :numbers, type: Array[Integer], values: 0..2, default: 0..2 }\n          end.not_to raise_error\n        end\n      end\n\n      context 'and is a subset of allowed values' do\n        it 'does not raise an exception' do\n          expect do\n            subject.params { optional :numbers, type: Array[Integer], values: [0, 1, 2], default: [1, 0] }\n          end.not_to raise_error\n        end\n      end\n    end\n\n    context 'when both range endpoints are #kind_of? the type' do\n      it 'accepts values in the range' do\n        subject.params do\n          requires :letter, type: String, values: 'a'..'z'\n        end\n        subject.get('/letter') { params[:letter] }\n\n        get '/letter', letter: 'j'\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('j')\n      end\n\n      it 'rejects values outside the range' do\n        subject.params do\n          requires :letter, type: String, values: 'a'..'z'\n        end\n        subject.get('/letter') { params[:letter] }\n\n        get '/letter', letter: 'J'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('letter does not have a valid value')\n      end\n    end\n  end\n\n  context 'parameters in group' do\n    it 'errors when no type is provided' do\n      expect do\n        subject.params do\n          group :a do\n            requires :b\n          end\n        end\n      end.to raise_error Grape::Exceptions::MissingGroupType\n\n      expect do\n        subject.params do\n          optional :a do\n            requires :b\n          end\n        end\n      end.to raise_error Grape::Exceptions::MissingGroupType\n    end\n\n    it 'allows Hash as type' do\n      subject.params do\n        group :a, type: Hash do\n          requires :b\n        end\n      end\n      subject.get('/group') { 'group works' }\n      get '/group', a: { b: true }\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('group works')\n\n      subject.params do\n        optional :a, type: Hash do\n          requires :b\n        end\n      end\n      get '/optional_type_hash'\n    end\n\n    it 'allows Array as type' do\n      subject.params do\n        group :a, type: Array do\n          requires :b\n        end\n      end\n      subject.get('/group') { 'group works' }\n      get '/group', a: [{ b: true }]\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('group works')\n\n      subject.params do\n        optional :a, type: Array do\n          requires :b\n        end\n      end\n      get '/optional_type_array'\n    end\n\n    it 'handles missing optional Array type' do\n      subject.params do\n        optional :a, type: Array do\n          requires :b\n        end\n      end\n      subject.get('/test') { declared(params).to_json }\n      get '/test'\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('{\"a\":[]}')\n    end\n\n    it 'errors with an unsupported type' do\n      expect do\n        subject.params do\n          group :a, type: Set do\n            requires :b\n          end\n        end\n      end.to raise_error Grape::Exceptions::UnsupportedGroupType\n\n      expect do\n        subject.params do\n          optional :a, type: Set do\n            requires :b\n          end\n        end\n      end.to raise_error Grape::Exceptions::UnsupportedGroupType\n    end\n  end\n\n  context 'when validations are dependent on a parameter' do\n    before do\n      subject.params do\n        optional :a\n        given :a do\n          requires :b\n        end\n      end\n      subject.get('/test') { declared(params).to_json }\n    end\n\n    it 'applies the validations only if the parameter is present' do\n      get '/test'\n      expect(last_response.status).to eq(200)\n\n      get '/test', a: true\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('b is missing')\n\n      get '/test', a: true, b: true\n      expect(last_response.status).to eq(200)\n    end\n\n    it 'applies the validations of multiple parameters' do\n      subject.params do\n        optional :a, :b\n        given :a, :b do\n          requires :c\n        end\n      end\n      subject.get('/multiple') { declared(params).to_json }\n\n      get '/multiple'\n      expect(last_response.status).to eq(200)\n\n      get '/multiple', a: true\n      expect(last_response.status).to eq(200)\n\n      get '/multiple', b: true\n      expect(last_response.status).to eq(200)\n\n      get '/multiple', a: true, b: true\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('c is missing')\n\n      get '/multiple', a: true, b: true, c: true\n      expect(last_response.status).to eq(200)\n    end\n\n    it 'applies only the appropriate validation' do\n      subject.params do\n        optional :a\n        optional :b\n        mutually_exclusive :a, :b\n        given :a do\n          requires :c, type: String\n        end\n        given :b do\n          requires :c, type: Integer\n        end\n      end\n      subject.get('/multiple') { declared(params).to_json }\n\n      get '/multiple'\n      expect(last_response.status).to eq(200)\n\n      get '/multiple', a: true, c: 'test'\n      expect(last_response.status).to eq(200)\n      expect(JSON.parse(last_response.body).symbolize_keys).to eq a: 'true', b: nil, c: 'test'\n\n      get '/multiple', b: true, c: '3'\n      expect(last_response.status).to eq(200)\n      expect(JSON.parse(last_response.body).symbolize_keys).to eq a: nil, b: 'true', c: 3\n\n      get '/multiple', a: true\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('c is missing')\n\n      get '/multiple', b: true\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('c is missing')\n\n      get '/multiple', a: true, b: true, c: 'test'\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('a, b are mutually exclusive, c is invalid')\n    end\n\n    it 'raises an error if the dependent parameter was never specified' do\n      expect do\n        subject.params do\n          given :c do\n          end\n        end\n      end.to raise_error(Grape::Exceptions::UnknownParameter)\n    end\n\n    it 'does not raise an error if the dependent parameter is a Hash' do\n      expect do\n        subject.params do\n          optional :a, type: Hash do\n            requires :b\n          end\n          given :a do\n            requires :c\n          end\n        end\n      end.not_to raise_error\n    end\n\n    it 'does not raise an error if when using nested given' do\n      expect do\n        subject.params do\n          optional :a, type: Hash do\n            requires :b\n          end\n          given :a do\n            requires :c\n            given :c do\n              requires :d\n            end\n          end\n        end\n      end.not_to raise_error\n    end\n\n    it 'allows nested dependent parameters' do\n      subject.params do\n        optional :a\n        given a: ->(val) { val == 'a' } do\n          optional :b\n          given b: ->(val) { val == 'b' } do\n            optional :c\n            given c: ->(val) { val == 'c' } do\n              requires :d\n            end\n          end\n        end\n      end\n      subject.get('/') { declared(params).to_json }\n\n      get '/'\n      expect(last_response.status).to eq 200\n\n      get '/', a: 'a', b: 'b', c: 'c'\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq 'd is missing'\n\n      get '/', a: 'a', b: 'b', c: 'c', d: 'd'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ a: 'a', b: 'b', c: 'c', d: 'd' }.to_json)\n    end\n\n    it 'allows renaming of dependent parameters' do\n      subject.params do\n        optional :a\n        given :a do\n          requires :b, as: :c\n        end\n      end\n\n      subject.get('/multiple') { declared(params).to_json }\n\n      get '/multiple', a: 'a', b: 'b'\n\n      body = JSON.parse(last_response.body)\n\n      expect(body.keys).to include('c')\n      expect(body.keys).not_to include('b')\n    end\n\n    it 'allows renaming of dependent on parameter' do\n      subject.params do\n        optional :a, as: :b\n        given a: ->(val) { val == 'x' } do\n          requires :c\n        end\n      end\n      subject.get('/') { declared(params) }\n\n      get '/', a: 'x'\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq 'c is missing'\n\n      get '/', a: 'y'\n      expect(last_response.status).to eq 200\n    end\n\n    it 'does not raise if the dependent parameter is not the renamed one' do\n      expect do\n        subject.params do\n          optional :a, as: :b\n          given :a do\n            requires :c\n          end\n        end\n      end.not_to raise_error\n    end\n\n    it 'raises an error if the dependent parameter is the renamed one' do\n      expect do\n        subject.params do\n          optional :a, as: :b\n          given :b do\n            requires :c\n          end\n        end\n      end.to raise_error(Grape::Exceptions::UnknownParameter)\n    end\n\n    it 'does not validate nested requires when given is false' do\n      subject.params do\n        requires :a, type: String, allow_blank: false, values: %w[x y z]\n        given a: ->(val) { val == 'x' } do\n          requires :inner1, type: Hash, allow_blank: false do\n            requires :foo, type: Integer, allow_blank: false\n          end\n        end\n        given a: ->(val) { val == 'y' } do\n          requires :inner2, type: Hash, allow_blank: false do\n            requires :bar, type: Integer, allow_blank: false\n            requires :baz, type: Array, allow_blank: false do\n              requires :baz_category, type: String, allow_blank: false\n            end\n          end\n        end\n        given a: ->(val) { val == 'z' } do\n          requires :inner3, type: Array, allow_blank: false do\n            requires :bar, type: Integer, allow_blank: false\n            requires :baz, type: Array, allow_blank: false do\n              requires :baz_category, type: String, allow_blank: false\n            end\n          end\n        end\n      end\n      subject.get('/varying') { declared(params).to_json }\n\n      get '/varying', a: 'x', inner1: { foo: 1 }\n      expect(last_response.status).to eq(200)\n\n      get '/varying', a: 'y', inner2: { bar: 2, baz: [{ baz_category: 'barstools' }] }\n      expect(last_response.status).to eq(200)\n\n      get '/varying', a: 'y', inner2: { bar: 2, baz: [{ unrelated: 'yep' }] }\n      expect(last_response.status).to eq(400)\n\n      get '/varying', a: 'z', inner3: [{ bar: 3, baz: [{ baz_category: 'barstools' }] }]\n      expect(last_response.status).to eq(200)\n    end\n\n    it 'detect unmet nested dependency' do\n      subject.params do\n        requires :a, type: String, allow_blank: false, values: %w[x y z]\n        given a: ->(val) { val == 'z' } do\n          requires :inner3, type: Array, allow_blank: false do\n            requires :bar, type: String, allow_blank: false\n            given bar: ->(val) { val == 'b' } do\n              requires :baz, type: Array do\n                optional :baz_category, type: String\n              end\n            end\n            given bar: ->(val) { val == 'c' } do\n              requires :baz, type: Array do\n                requires :baz_category, type: String\n              end\n            end\n          end\n        end\n      end\n      subject.get('/nested-dependency') { declared(params).to_json }\n\n      get '/nested-dependency', a: 'z', inner3: [{ bar: 'c', baz: [{ unrelated: 'nope' }] }]\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq 'inner3[0][baz][0][baz_category] is missing'\n    end\n\n    context 'detect when json array' do\n      before do\n        subject.params do\n          requires :array, type: Array do\n            requires :a, type: String\n            given a: ->(val) { val == 'a' } do\n              requires :json, type: Hash do\n                requires :b, type: String\n              end\n            end\n          end\n        end\n\n        subject.post '/nested_array' do\n          'nested array works!'\n        end\n      end\n\n      it 'succeeds' do\n        params = {\n          array: [\n            {\n              a: 'a',\n              json: { b: 'b' }\n            },\n            {\n              a: 'b'\n            }\n          ]\n        }\n        post '/nested_array', params.to_json, 'CONTENT_TYPE' => 'application/json'\n\n        expect(last_response.status).to eq(201)\n      end\n\n      it 'fails' do\n        params = {\n          array: [\n            {\n              a: 'a',\n              json: { b: 'b' }\n            },\n            {\n              a: 'a'\n            }\n          ]\n        }\n        post '/nested_array', params.to_json, 'CONTENT_TYPE' => 'application/json'\n\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('array[1][json] is missing, array[1][json][b] is missing')\n      end\n    end\n\n    context 'array without given' do\n      before do\n        subject.params do\n          requires :array, type: Array do\n            requires :a, type: Integer\n            requires :b, type: Integer\n          end\n        end\n\n        subject.post '/array_without_given'\n      end\n\n      it 'fails' do\n        params = {\n          array: [\n            {\n              a: 1,\n              b: 2\n            },\n            {\n              a: 3\n            },\n            {\n              a: 5\n            }\n          ]\n        }\n        post '/array_without_given', params.to_json, 'CONTENT_TYPE' => 'application/json'\n        expect(last_response.body).to eq('array[1][b] is missing, array[2][b] is missing')\n        expect(last_response.status).to eq(400)\n      end\n    end\n\n    context 'array with given' do\n      before do\n        subject.params do\n          requires :array, type: Array do\n            requires :a, type: Integer\n            given a: lambda(&:odd?) do\n              requires :b, type: Integer\n            end\n          end\n        end\n\n        subject.post '/array_with_given'\n      end\n\n      it 'fails' do\n        params = {\n          array: [\n            {\n              a: 1,\n              b: 2\n            },\n            {\n              a: 3\n            },\n            {\n              a: 5\n            }\n          ]\n        }\n        post '/array_with_given', params.to_json, 'CONTENT_TYPE' => 'application/json'\n        expect(last_response.body).to eq('array[1][b] is missing, array[2][b] is missing')\n        expect(last_response.status).to eq(400)\n      end\n    end\n\n    context 'nested json array with given' do\n      before do\n        subject.params do\n          requires :workflow_nodes, type: Array do\n            requires :steps, type: Array do\n              requires :id, type: String\n              optional :type, type: String, values: %w[send_messsge assign_user assign_team tag_conversation snooze close add_commit]\n              given type: ->(val) { val == 'send_messsge' } do\n                requires :message, type: Hash do\n                  requires :content, type: String\n                end\n              end\n            end\n          end\n        end\n\n        subject.post '/nested_json_array_with_given'\n      end\n\n      it 'passes' do\n        params = {\n          workflow_nodes: [\n            {\n              id: 'eqibmvEzPo8hQOSt',\n              title: 'Node 1',\n              is_start: true,\n              steps: [\n                {\n                  id: 'DvdSZaIm1hEd5XO5',\n                  type: 'send_messsge',\n                  message: {\n                    content: '打击好',\n                    menus: []\n                  }\n                },\n                {\n                  id: 'VY6MIwycBw0b51Ib',\n                  type: 'add_commit',\n                  comment_content: '初来乍到'\n                }\n              ]\n            }\n          ]\n        }\n        post '/nested_json_array_with_given', params.to_json, 'CONTENT_TYPE' => 'application/json'\n        expect(last_response.status).to eq(201)\n      end\n    end\n\n    it 'includes the parameter within #declared(params)' do\n      get '/test', a: true, b: true\n\n      expect(JSON.parse(last_response.body)).to eq('a' => 'true', 'b' => 'true')\n    end\n\n    it 'returns a sensible error message within a nested context' do\n      subject.params do\n        requires :bar, type: Hash do\n          optional :a\n          given :a do\n            requires :b\n          end\n        end\n      end\n      subject.get('/nested') { 'worked' }\n\n      get '/nested', bar: { a: true }\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('bar[b] is missing')\n    end\n\n    it 'includes the nested parameter within #declared(params)' do\n      subject.params do\n        requires :bar, type: Hash do\n          optional :a\n          given :a do\n            requires :b\n          end\n        end\n      end\n      subject.get('/nested') { declared(params).to_json }\n\n      get '/nested', bar: { a: true, b: 'yes' }\n      expect(JSON.parse(last_response.body)).to eq('bar' => { 'a' => 'true', 'b' => 'yes' })\n    end\n\n    it 'includes level 2 nested parameters outside the given within #declared(params)' do\n      subject.params do\n        requires :bar, type: Hash do\n          optional :a\n          given :a do\n            requires :c, type: Hash do\n              requires :b\n            end\n          end\n        end\n      end\n      subject.get('/nested') { declared(params).to_json }\n\n      get '/nested', bar: { a: true, c: { b: 'yes' } }\n      expect(JSON.parse(last_response.body)).to eq('bar' => { 'a' => 'true', 'c' => { 'b' => 'yes' } })\n    end\n\n    context 'when the dependent parameter is not present #declared(params)' do\n      context 'lateral parameter' do\n        before do\n          [true, false].each do |evaluate_given|\n            subject.params do\n              optional :a\n              given :a do\n                optional :b\n              end\n            end\n            subject.get(\"/evaluate_given_#{evaluate_given}\") { declared(params, evaluate_given: evaluate_given).to_json }\n          end\n        end\n\n        it 'evaluate_given_false' do\n          get '/evaluate_given_false', b: 'b'\n          expect(JSON.parse(last_response.body)).to eq('a' => nil, 'b' => 'b')\n        end\n\n        it 'evaluate_given_true' do\n          get '/evaluate_given_true', b: 'b'\n          expect(JSON.parse(last_response.body)).to eq('a' => nil)\n        end\n      end\n\n      context 'lateral hash parameter' do\n        before do\n          [true, false].each do |evaluate_given|\n            subject.params do\n              optional :a, values: %w[x y]\n              given a: ->(a) { a == 'x' } do\n                optional :b, type: Hash do\n                  optional :c\n                end\n                optional :e\n              end\n              given a: ->(a) { a == 'y' } do\n                optional :b, type: Hash do\n                  optional :d\n                end\n                optional :f\n              end\n            end\n            subject.get(\"/evaluate_given_#{evaluate_given}\") { declared(params, evaluate_given: evaluate_given).to_json }\n          end\n        end\n\n        it 'evaluate_given_false' do\n          get '/evaluate_given_false', a: 'x'\n          expect(JSON.parse(last_response.body)).to eq('a' => 'x', 'b' => { 'd' => nil }, 'e' => nil, 'f' => nil)\n\n          get '/evaluate_given_false', a: 'y'\n          expect(JSON.parse(last_response.body)).to eq('a' => 'y', 'b' => { 'd' => nil }, 'e' => nil, 'f' => nil)\n        end\n\n        it 'evaluate_given_true' do\n          get '/evaluate_given_true', a: 'x'\n          expect(JSON.parse(last_response.body)).to eq('a' => 'x', 'b' => { 'c' => nil }, 'e' => nil)\n\n          get '/evaluate_given_true', a: 'y'\n          expect(JSON.parse(last_response.body)).to eq('a' => 'y', 'b' => { 'd' => nil }, 'f' => nil)\n        end\n      end\n\n      context 'lateral parameter within lateral hash parameter' do\n        before do\n          [true, false].each do |evaluate_given|\n            subject.params do\n              optional :a, values: %w[x y]\n              given a: ->(a) { a == 'x' } do\n                optional :b, type: Hash do\n                  optional :c\n                  given :c do\n                    optional :g\n                    optional :e, type: Hash do\n                      optional :h\n                    end\n                  end\n                end\n              end\n              given a: ->(a) { a == 'y' } do\n                optional :b, type: Hash do\n                  optional :d\n                  given :d do\n                    optional :f\n                    optional :e, type: Hash do\n                      optional :i\n                    end\n                  end\n                end\n              end\n            end\n            subject.get(\"/evaluate_given_#{evaluate_given}\") { declared(params, evaluate_given: evaluate_given).to_json }\n          end\n        end\n\n        it 'evaluate_given_false' do\n          get '/evaluate_given_false', a: 'x'\n          expect(JSON.parse(last_response.body)).to eq('a' => 'x', 'b' => { 'd' => nil, 'f' => nil, 'e' => { 'i' => nil } })\n\n          get '/evaluate_given_false', a: 'x', b: { c: 'c' }\n          expect(JSON.parse(last_response.body)).to eq('a' => 'x', 'b' => { 'd' => nil, 'f' => nil, 'e' => { 'i' => nil } })\n\n          get '/evaluate_given_false', a: 'y'\n          expect(JSON.parse(last_response.body)).to eq('a' => 'y', 'b' => { 'd' => nil, 'f' => nil, 'e' => { 'i' => nil } })\n\n          get '/evaluate_given_false', a: 'y', b: { d: 'd' }\n          expect(JSON.parse(last_response.body)).to eq('a' => 'y', 'b' => { 'd' => 'd', 'f' => nil, 'e' => { 'i' => nil } })\n        end\n\n        it 'evaluate_given_true' do\n          get '/evaluate_given_true', a: 'x'\n          expect(JSON.parse(last_response.body)).to eq('a' => 'x', 'b' => { 'c' => nil })\n\n          get '/evaluate_given_true', a: 'x', b: { c: 'c' }\n          expect(JSON.parse(last_response.body)).to eq('a' => 'x', 'b' => { 'c' => 'c', 'g' => nil, 'e' => { 'h' => nil } })\n\n          get '/evaluate_given_true', a: 'y'\n          expect(JSON.parse(last_response.body)).to eq('a' => 'y', 'b' => { 'd' => nil })\n\n          get '/evaluate_given_true', a: 'y', b: { d: 'd' }\n          expect(JSON.parse(last_response.body)).to eq('a' => 'y', 'b' => { 'd' => 'd', 'f' => nil, 'e' => { 'i' => nil } })\n        end\n      end\n\n      context 'lateral parameter within an array param' do\n        before do\n          [true, false].each do |evaluate_given|\n            subject.params do\n              optional :array, type: Array do\n                optional :a\n                given :a do\n                  optional :b\n                end\n              end\n            end\n            subject.post(\"/evaluate_given_#{evaluate_given}\") do\n              declared(params, evaluate_given: evaluate_given).to_json\n            end\n          end\n        end\n\n        it 'evaluate_given_false' do\n          post '/evaluate_given_false', { array: [{ b: 'b' }, { a: 'a', b: 'b' }] }.to_json, 'CONTENT_TYPE' => 'application/json'\n          expect(JSON.parse(last_response.body)).to eq('array' => [{ 'a' => nil, 'b' => 'b' }, { 'a' => 'a', 'b' => 'b' }])\n        end\n\n        it 'evaluate_given_true' do\n          post '/evaluate_given_true', { array: [{ b: 'b' }, { a: 'a', b: 'b' }] }.to_json, 'CONTENT_TYPE' => 'application/json'\n          expect(JSON.parse(last_response.body)).to eq('array' => [{ 'a' => nil }, { 'a' => 'a', 'b' => 'b' }])\n        end\n      end\n\n      context 'nested given parameter' do\n        before do\n          [true, false].each do |evaluate_given|\n            subject.params do\n              optional :a\n              optional :c\n              given :a do\n                given :c do\n                  optional :b\n                end\n              end\n            end\n            subject.post(\"/evaluate_given_#{evaluate_given}\") do\n              declared(params, evaluate_given: evaluate_given).to_json\n            end\n          end\n        end\n\n        it 'evaluate_given_false' do\n          post '/evaluate_given_false', { a: 'a', b: 'b' }.to_json, 'CONTENT_TYPE' => 'application/json'\n          expect(JSON.parse(last_response.body)).to eq('a' => 'a', 'b' => 'b', 'c' => nil)\n\n          post '/evaluate_given_false', { c: 'c', b: 'b' }.to_json, 'CONTENT_TYPE' => 'application/json'\n          expect(JSON.parse(last_response.body)).to eq('a' => nil, 'b' => 'b', 'c' => 'c')\n\n          post '/evaluate_given_false', { a: 'a', c: 'c', b: 'b' }.to_json, 'CONTENT_TYPE' => 'application/json'\n          expect(JSON.parse(last_response.body)).to eq('a' => 'a', 'b' => 'b', 'c' => 'c')\n        end\n\n        it 'evaluate_given_true' do\n          post '/evaluate_given_true', { a: 'a', b: 'b' }.to_json, 'CONTENT_TYPE' => 'application/json'\n          expect(JSON.parse(last_response.body)).to eq('a' => 'a', 'c' => nil)\n\n          post '/evaluate_given_true', { c: 'c', b: 'b' }.to_json, 'CONTENT_TYPE' => 'application/json'\n          expect(JSON.parse(last_response.body)).to eq('a' => nil, 'c' => 'c')\n\n          post '/evaluate_given_true', { a: 'a', c: 'c', b: 'b' }.to_json, 'CONTENT_TYPE' => 'application/json'\n          expect(JSON.parse(last_response.body)).to eq('a' => 'a', 'b' => 'b', 'c' => 'c')\n        end\n      end\n\n      context 'nested given parameter within an array param' do\n        before do\n          [true, false].each do |evaluate_given|\n            subject.params do\n              optional :array, type: Array do\n                optional :a\n                optional :c\n                given :a do\n                  given :c do\n                    optional :b\n                  end\n                end\n              end\n            end\n            subject.post(\"/evaluate_given_#{evaluate_given}\") do\n              declared(params, evaluate_given: evaluate_given).to_json\n            end\n          end\n        end\n\n        let :evaluate_given_params do\n          {\n            array: [\n              { a: 'a', b: 'b' },\n              { c: 'c', b: 'b' },\n              { a: 'a', c: 'c', b: 'b' }\n            ]\n          }\n        end\n\n        it 'evaluate_given_false' do\n          post '/evaluate_given_false', evaluate_given_params.to_json, 'CONTENT_TYPE' => 'application/json'\n          expect(JSON.parse(last_response.body)).to eq('array' => [{ 'a' => 'a', 'b' => 'b', 'c' => nil }, { 'a' => nil, 'b' => 'b', 'c' => 'c' }, { 'a' => 'a', 'b' => 'b', 'c' => 'c' }])\n        end\n\n        it 'evaluate_given_true' do\n          post '/evaluate_given_true', evaluate_given_params.to_json, 'CONTENT_TYPE' => 'application/json'\n          expect(JSON.parse(last_response.body)).to eq('array' => [{ 'a' => 'a', 'c' => nil }, { 'a' => nil, 'c' => 'c' }, { 'a' => 'a', 'b' => 'b', 'c' => 'c' }])\n        end\n      end\n\n      context 'nested given parameter within a nested given parameter within an array param' do\n        before do\n          [true, false].each do |evaluate_given|\n            subject.params do\n              optional :array, type: Array do\n                optional :a\n                optional :c\n                given :a do\n                  given :c do\n                    optional :array, type: Array do\n                      optional :a\n                      optional :c\n                      given :a do\n                        given :c do\n                          optional :b\n                        end\n                      end\n                    end\n                  end\n                end\n              end\n            end\n            subject.post(\"/evaluate_given_#{evaluate_given}\") do\n              declared(params, evaluate_given: evaluate_given).to_json\n            end\n          end\n        end\n\n        let :evaluate_given_params do\n          {\n            array: [{\n              a: 'a',\n              c: 'c',\n              array: [\n                { a: 'a', b: 'b' },\n                { c: 'c', b: 'b' },\n                { a: 'a', c: 'c', b: 'b' }\n              ]\n            }]\n          }\n        end\n\n        it 'evaluate_given_false' do\n          expected_response_hash = {\n            'array' => [{\n              'a' => 'a',\n              'c' => 'c',\n              'array' => [\n                { 'a' => 'a', 'b' => 'b', 'c' => nil },\n                { 'a' => nil, 'c' => 'c', 'b' => 'b' },\n                { 'a' => 'a', 'c' => 'c', 'b' => 'b' }\n              ]\n            }]\n          }\n          post '/evaluate_given_false', evaluate_given_params.to_json, 'CONTENT_TYPE' => 'application/json'\n          expect(JSON.parse(last_response.body)).to eq(expected_response_hash)\n        end\n\n        it 'evaluate_given_true' do\n          expected_response_hash = {\n            'array' => [{\n              'a' => 'a',\n              'c' => 'c',\n              'array' => [\n                { 'a' => 'a', 'c' => nil },\n                { 'a' => nil, 'c' => 'c' },\n                { 'a' => 'a', 'b' => 'b', 'c' => 'c' }\n              ]\n            }]\n          }\n          post '/evaluate_given_true', evaluate_given_params.to_json, 'CONTENT_TYPE' => 'application/json'\n          expect(JSON.parse(last_response.body)).to eq(expected_response_hash)\n        end\n      end\n    end\n  end\n\n  context 'default value in given block' do\n    before do\n      subject.params do\n        optional :a, values: %w[a b]\n        given a: ->(val) { val == 'a' } do\n          optional :b, default: 'default'\n        end\n      end\n      subject.get('/') { params.to_json }\n    end\n\n    context 'when dependency meets' do\n      it 'sets default value for dependent parameter' do\n        get '/', a: 'a'\n        expect(last_response.body).to eq({ a: 'a', b: 'default' }.to_json)\n      end\n    end\n\n    context 'when dependency does not meet' do\n      it 'does not set default value for dependent parameter' do\n        get '/', a: 'b'\n        expect(last_response.body).to eq({ a: 'b' }.to_json)\n      end\n    end\n  end\n\n  context 'when validations are dependent on a parameter within an array param' do\n    before do\n      subject.params do\n        requires :foos, type: Array do\n          optional :foo\n          given :foo do\n            requires :bar\n          end\n        end\n      end\n      subject.get('/test') { 'ok' }\n    end\n\n    it 'passes none Hash params' do\n      get '/test', foos: ['']\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('ok')\n    end\n  end\n\n  context 'when validations are dependent on a parameter within an array param within #declared(params).to_json' do\n    before do\n      subject.params do\n        requires :foos, type: Array do\n          optional :foo_type, :baz_type\n          given :foo_type do\n            requires :bar\n          end\n        end\n      end\n      subject.post('/test') { declared(params).to_json }\n    end\n\n    it 'applies the constraint within each value' do\n      post '/test',\n           { foos: [{ foo_type: 'a' }, { baz_type: 'c' }] }.to_json,\n           'CONTENT_TYPE' => 'application/json'\n\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('foos[0][bar] is missing')\n    end\n  end\n\n  context 'when validations are dependent on a parameter with specific value' do\n    # build test cases from all combinations of declarations and options\n    a_decls = %i[optional requires]\n    a_options = [{}, { values: %w[x y z] }]\n    b_options = [{}, { type: String }, { allow_blank: false }, { type: String, allow_blank: false }]\n    combinations = a_decls.product(a_options, b_options)\n    combinations.each_with_index do |(a_decl, a_opts, b_opts), i|\n      context \"(case #{i})\" do\n        before do\n          # puts \"a_decl: #{a_decl}, a_opts: #{a_opts}, b_opts: #{b_opts}\"\n          subject.params do\n            __send__ a_decl, :a, **a_opts\n            given(a: ->(val) { val == 'x' }) { requires :b, **b_opts }\n            given(a: ->(val) { val == 'y' }) { requires :c, **b_opts }\n          end\n          subject.get('/test') { declared(params).to_json }\n        end\n\n        if a_decl == :optional\n          it 'skips validation when base param is missing' do\n            get '/test'\n            expect(last_response.status).to eq(200)\n          end\n        end\n\n        it 'skips validation when base param does not have a specified value' do\n          get '/test', a: 'z'\n          expect(last_response.status).to eq(200)\n\n          get '/test', a: 'z', b: ''\n          expect(last_response.status).to eq(200)\n        end\n\n        it 'applies the validation when base param has the specific value' do\n          get '/test', a: 'x'\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to include('b is missing')\n\n          get '/test', a: 'x', b: true\n          expect(last_response.status).to eq(200)\n\n          get '/test', a: 'x', b: true, c: ''\n          expect(last_response.status).to eq(200)\n        end\n\n        it 'includes the parameter within #declared(params)' do\n          get '/test', a: 'x', b: true\n          expect(JSON.parse(last_response.body)).to eq('a' => 'x', 'b' => 'true', 'c' => nil)\n        end\n      end\n    end\n  end\n\n  it 'raises an error if the dependent parameter was never specified' do\n    expect do\n      subject.params do\n        given :c do\n        end\n      end\n    end.to raise_error(Grape::Exceptions::UnknownParameter)\n  end\n\n  it 'returns a sensible error message within a nested context' do\n    subject.params do\n      requires :bar, type: Hash do\n        optional :a\n        given a: ->(val) { val == 'x' } do\n          requires :b\n        end\n      end\n    end\n    subject.get('/nested') { 'worked' }\n\n    get '/nested', bar: { a: 'x' }\n    expect(last_response.status).to eq(400)\n    expect(last_response.body).to eq('bar[b] is missing')\n  end\n\n  it 'includes the nested parameter within #declared(params)' do\n    subject.params do\n      requires :bar, type: Hash do\n        optional :a\n        given a: ->(val) { val == 'x' } do\n          requires :b\n        end\n      end\n    end\n    subject.get('/nested') { declared(params).to_json }\n\n    get '/nested', bar: { a: 'x', b: 'yes' }\n    expect(JSON.parse(last_response.body)).to eq('bar' => { 'a' => 'x', 'b' => 'yes' })\n  end\n\n  it 'includes level 2 nested parameters outside the given within #declared(params)' do\n    subject.params do\n      requires :bar, type: Hash do\n        optional :a\n        given a: ->(val) { val == 'x' } do\n          requires :c, type: Hash do\n            requires :b\n          end\n        end\n      end\n    end\n    subject.get('/nested') { declared(params).to_json }\n\n    get '/nested', bar: { a: 'x', c: { b: 'yes' } }\n    expect(JSON.parse(last_response.body)).to eq('bar' => { 'a' => 'x', 'c' => { 'b' => 'yes' } })\n  end\n\n  it 'includes deeply nested parameters within #declared(params)' do\n    subject.params do\n      requires :arr1, type: Array do\n        requires :hash1, type: Hash do\n          requires :arr2, type: Array do\n            requires :hash2, type: Hash do\n              requires :something, type: String\n            end\n          end\n        end\n      end\n    end\n    subject.get('/nested_deep') { declared(params).to_json }\n\n    get '/nested_deep', arr1: [{ hash1: { arr2: [{ hash2: { something: 'value' } }] } }]\n    expect(last_response.status).to eq(200)\n    expect(JSON.parse(last_response.body)).to eq('arr1' => [{ 'hash1' => { 'arr2' => [{ 'hash2' => { 'something' => 'value' } }] } }])\n  end\n\n  context 'failing fast' do\n    context 'when fail_fast is not defined' do\n      it 'does not stop validation' do\n        subject.params do\n          requires :one\n          requires :two\n          requires :three\n        end\n        subject.get('/fail-fast') { declared(params).to_json }\n\n        get '/fail-fast'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('one is missing, two is missing, three is missing')\n      end\n    end\n\n    context 'when fail_fast is defined it stops the validation' do\n      it 'of other params' do\n        subject.params do\n          requires :one, fail_fast: true\n          requires :two\n        end\n        subject.get('/fail-fast') { declared(params).to_json }\n\n        get '/fail-fast'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('one is missing')\n      end\n\n      it 'for a single param' do\n        subject.params do\n          requires :one, allow_blank: false, regexp: /[0-9]+/, fail_fast: true\n        end\n        subject.get('/fail-fast') { declared(params).to_json }\n\n        get '/fail-fast', one: ''\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('one is empty')\n      end\n    end\n  end\n\n  context 'when params have group attributes' do\n    context 'with validations' do\n      before do\n        subject.params do\n          with(allow_blank: false) do\n            requires :id\n            optional :name\n            optional :address, allow_blank: true\n          end\n        end\n        subject.get('test')\n      end\n\n      context 'when data is invalid' do\n        before do\n          get 'test', id: '', name: ''\n        end\n\n        it 'returns a validation error' do\n          expect(last_response.status).to eq(400)\n        end\n\n        it 'applies group validations for every parameter' do\n          expect(last_response.body).to eq('id is empty, name is empty')\n        end\n      end\n\n      context 'when parameter has the same validator as a group' do\n        before do\n          get 'test', id: 'id', address: ''\n        end\n\n        it 'returns a successful response' do\n          expect(last_response.status).to eq(200)\n        end\n\n        it 'prioritizes parameter validation over group validation' do\n          expect(last_response.body).not_to include('address is empty')\n        end\n      end\n    end\n\n    context 'with types' do\n      before do\n        subject.params do\n          with(type: Date) do\n            requires :created_at\n          end\n        end\n        subject.get('test') { params[:created_at] }\n      end\n\n      context 'when invalid date provided' do\n        before do\n          get 'test', created_at: 'not_a_date'\n        end\n\n        it 'responds with HTTP error' do\n          expect(last_response.status).to eq(400)\n        end\n\n        it 'returns a validation error' do\n          expect(last_response.body).to eq('created_at is invalid')\n        end\n      end\n\n      context 'when created_at receives a valid date' do\n        before do\n          get 'test', created_at: '2016-01-01'\n        end\n\n        it 'returns a successful response' do\n          expect(last_response.status).to eq(200)\n        end\n\n        it 'returns a date' do\n          expect(last_response.body).to eq('2016-01-01')\n        end\n      end\n    end\n\n    context 'with several group attributes' do\n      before do\n        subject.params do\n          with(values: [1]) do\n            requires :id, type: Integer\n          end\n\n          with(allow_blank: false) do\n            optional :address, type: String\n          end\n\n          requires :name\n        end\n        subject.get('test')\n      end\n\n      context 'when data is invalid' do\n        before do\n          get 'test', id: 2, address: ''\n        end\n\n        it 'responds with HTTP error' do\n          expect(last_response.status).to eq(400)\n        end\n\n        it 'returns a validation error' do\n          expect(last_response.body).to eq('id does not have a valid value, address is empty, name is missing')\n        end\n      end\n\n      context 'when correct data is provided' do\n        before do\n          get 'test', id: 1, address: 'Some street', name: 'John'\n        end\n\n        it 'returns a successful response' do\n          expect(last_response.status).to eq(200)\n        end\n      end\n    end\n\n    context 'with nested groups' do\n      before do\n        subject.params do\n          with(type: Integer) do\n            requires :id\n\n            with(type: Date) do\n              requires :created_at\n              optional :updated_at\n            end\n          end\n        end\n        subject.get('test')\n      end\n\n      context 'when data is invalid' do\n        before do\n          get 'test', id: 'wrong', created_at: 'not_a_date', updated_at: '2016-01-01'\n        end\n\n        it 'responds with HTTP error' do\n          expect(last_response.status).to eq(400)\n        end\n\n        it 'returns a validation error' do\n          expect(last_response.body).to eq('id is invalid, created_at is invalid')\n        end\n      end\n\n      context 'when correct data is provided' do\n        before do\n          get 'test', id: 1, created_at: '2016-01-01'\n        end\n\n        it 'returns a successful response' do\n          expect(last_response.status).to eq(200)\n        end\n      end\n    end\n\n    context 'with many levels of nested groups' do\n      before do\n        subject.params do\n          requires :first_level, type: Hash do\n            with(type: Integer) do\n              requires :value\n              with(type: String) do\n                optional :second_level, type: Array do\n                  optional :name, type: String\n                  with(type: Integer) do\n                    optional :third_level, type: Array do\n                      requires :value, type: Integer\n                      optional :position\n                    end\n                  end\n                end\n              end\n              requires :id\n            end\n          end\n        end\n        subject.put('/nested') { declared(params).to_json }\n      end\n\n      context 'when data is valid' do\n        let(:request_params) do\n          {\n            first_level: {\n              value: '10',\n              second_level: [\n                {\n                  name: '13',\n                  third_level: [\n                    {\n                      value: '2',\n                      position: '1'\n                    }\n                  ]\n                }\n              ],\n              id: '20'\n            }\n          }\n        end\n\n        it 'validates and coerces correctly' do\n          put '/nested', request_params.to_json, 'CONTENT_TYPE' => 'application/json'\n\n          expect(last_response.status).to eq(200)\n          expect(JSON.parse(last_response.body, symbolize_names: true)).to eq(\n            first_level: {\n              value: 10,\n              second_level: [\n                { name: '13', third_level: [{ value: 2, position: 1 }] }\n              ],\n              id: 20\n            }\n          )\n        end\n      end\n\n      context 'when data is invalid' do\n        let(:request_params) do\n          {\n            first_level: {\n              value: 'wrong',\n              second_level: [\n                { name: 'name', third_level: [{ position: 'wrong' }] }\n              ]\n            }\n          }\n        end\n\n        it 'responds with HTTP error' do\n          put '/nested', request_params.to_json, 'CONTENT_TYPE' => 'application/json'\n          expect(last_response.status).to eq(400)\n        end\n\n        it 'responds with a validation error' do\n          put '/nested', request_params.to_json, 'CONTENT_TYPE' => 'application/json'\n\n          expect(last_response.body)\n            .to include('first_level[value] is invalid')\n            .and include('first_level[id] is missing')\n            .and include('first_level[second_level][0][third_level][0][value] is missing')\n            .and include('first_level[second_level][0][third_level][0][position] is invalid')\n        end\n      end\n    end\n  end\n\n  context 'with exactly_one_of validation for optional parameters within an Hash param' do\n    before do\n      subject.params do\n        optional :memo, type: Hash do\n          optional :text, type: String\n          optional :custom_body, type: Hash, coerce_with: JSON\n          exactly_one_of :text, :custom_body\n        end\n      end\n      subject.get('test')\n    end\n\n    context 'when correct data is provided' do\n      it 'returns a successful response' do\n        get 'test', memo: {}\n        expect(last_response.status).to eq(200)\n\n        get 'test', memo: { text: 'HOGEHOGE' }\n        expect(last_response.status).to eq(200)\n\n        get 'test', memo: { custom_body: '{ \"xxx\": \"yyy\" }' }\n        expect(last_response.status).to eq(200)\n      end\n    end\n\n    context 'when invalid data is provided' do\n      it 'returns a failure response' do\n        get 'test', memo: { text: 'HOGEHOGE', custom_body: '{ \"xxx\": \"yyy\" }' }\n        expect(last_response.status).to eq(400)\n\n        get 'test', memo: '{ \"custom_body\": \"HOGE\" }'\n        expect(last_response.status).to eq(400)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/single_attribute_iterator_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::SingleAttributeIterator do\n  describe '#each' do\n    subject(:iterator) { described_class.new(%i[first second], scope, params) }\n\n    let(:scope) { Grape::Validations::ParamsScope.new(api: Class.new(Grape::API)) }\n\n    context 'when params is a hash' do\n      let(:params) do\n        { first: 'string', second: 'string' }\n      end\n\n      it 'yields params and every single attribute from the list' do\n        expect { |b| iterator.each(&b) }\n          .to yield_successive_args([params, :first, false], [params, :second, false])\n      end\n    end\n\n    context 'when params is an array' do\n      let(:params) do\n        [{ first: 'string1', second: 'string1' }, { first: 'string2', second: 'string2' }]\n      end\n\n      it 'yields every single attribute from the list for each of the array elements' do\n        expect { |b| iterator.each(&b) }.to yield_successive_args(\n          [params[0], :first, false], [params[0], :second, false],\n          [params[1], :first, false], [params[1], :second, false]\n        )\n      end\n\n      context 'empty values' do\n        let(:params) { [{}, '', 10] }\n\n        it 'marks params with empty values' do\n          expect { |b| iterator.each(&b) }.to yield_successive_args(\n            [params[0], :first, true], [params[0], :second, true],\n            [params[1], :first, true], [params[1], :second, true],\n            [params[2], :first, false], [params[2], :second, false]\n          )\n        end\n      end\n\n      context 'when missing optional value' do\n        let(:params) { [Grape::DSL::Parameters::EmptyOptionalValue, 10] }\n\n        it 'does not yield skipped values' do\n          expect { |b| iterator.each(&b) }.to yield_successive_args(\n            [params[1], :first, false], [params[1], :second, false]\n          )\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/types/array_coercer_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Types::ArrayCoercer do\n  subject { described_class.new(type) }\n\n  describe '#call' do\n    context 'an array of primitives' do\n      let(:type) { Array[String] }\n\n      it 'coerces elements in the array' do\n        expect(subject.call([10, 20])).to eq(%w[10 20])\n      end\n    end\n\n    context 'an array of arrays' do\n      let(:type) { Array[Array[Integer]] }\n\n      it 'coerces elements in the nested array' do\n        expect(subject.call([%w[10 20]])).to eq([[10, 20]])\n        expect(subject.call([['10'], ['20']])).to eq([[10], [20]])\n      end\n    end\n\n    context 'an array of sets' do\n      let(:type) { Array[Set[Integer]] }\n\n      it 'coerces elements in the nested set' do\n        expect(subject.call([%w[10 20]])).to eq([Set[10, 20]])\n        expect(subject.call([['10'], ['20']])).to eq([Set[10], Set[20]])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/types/primitive_coercer_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Types::PrimitiveCoercer do\n  subject { described_class.new(type, strict) }\n\n  let(:strict) { false }\n\n  describe '#call' do\n    context 'BigDecimal' do\n      let(:type) { BigDecimal }\n\n      it 'coerces to BigDecimal' do\n        expect(subject.call(5)).to eq(BigDecimal('5'))\n      end\n\n      it 'coerces an empty string to nil' do\n        expect(subject.call('')).to be_nil\n      end\n    end\n\n    context 'Boolean' do\n      let(:type) { Grape::API::Boolean }\n\n      [true, 'true', 1].each do |val|\n        it \"coerces '#{val}' to true\" do\n          expect(subject.call(val)).to be(true)\n        end\n      end\n\n      [false, 'false', 0].each do |val|\n        it \"coerces '#{val}' to false\" do\n          expect(subject.call(val)).to be(false)\n        end\n      end\n\n      it 'returns an error when the given value cannot be coerced' do\n        expect(subject.call(123)).to be_instance_of(Grape::Validations::Types::InvalidValue)\n      end\n\n      it 'coerces an empty string to nil' do\n        expect(subject.call('')).to be_nil\n      end\n    end\n\n    context 'DateTime' do\n      let(:type) { DateTime }\n\n      it 'coerces an empty string to nil' do\n        expect(subject.call('')).to be_nil\n      end\n    end\n\n    context 'Float' do\n      let(:type) { Float }\n\n      it 'coerces an empty string to nil' do\n        expect(subject.call('')).to be_nil\n      end\n    end\n\n    context 'Integer' do\n      let(:type) { Integer }\n\n      it 'coerces an empty string to nil' do\n        expect(subject.call('')).to be_nil\n      end\n\n      it 'accepts non-nil value' do\n        expect(subject.call(42)).to be_a(Integer)\n      end\n    end\n\n    context 'Numeric' do\n      let(:type) { Numeric }\n\n      it 'coerces an empty string to nil' do\n        expect(subject.call('')).to be_nil\n      end\n\n      it 'accepts a non-nil value' do\n        expect(subject.call(42)).to be_a(Numeric) # in fact Integer\n      end\n    end\n\n    context 'Time' do\n      let(:type) { Time }\n\n      it 'coerces an empty string to nil' do\n        expect(subject.call('')).to be_nil\n      end\n    end\n\n    context 'String' do\n      let(:type) { String }\n\n      it 'coerces to String' do\n        expect(subject.call(10)).to eq('10')\n      end\n\n      it 'does not coerce an empty string to nil' do\n        expect(subject.call('')).to eq('')\n      end\n    end\n\n    context 'Symbol' do\n      let(:type) { Symbol }\n\n      it 'coerces an empty string to nil' do\n        expect(subject.call('')).to be_nil\n      end\n    end\n\n    context 'a type unknown in Dry-types' do\n      let(:type) { Complex }\n\n      it 'raises error on init' do\n        expect(Grape::DryTypes::Params.constants).not_to include(type.name.to_sym)\n        expect { subject }.to raise_error(/type Complex should support coercion/)\n      end\n    end\n\n    context 'the strict mode' do\n      let(:strict) { true }\n\n      context 'Boolean' do\n        let(:type) { Grape::API::Boolean }\n\n        it 'returns an error when the given value is not Boolean' do\n          expect(subject.call(1)).to be_instance_of(Grape::Validations::Types::InvalidValue)\n        end\n\n        it 'returns a value as it is when the given value is Boolean' do\n          expect(subject.call(true)).to be(true)\n        end\n      end\n\n      context 'BigDecimal' do\n        let(:type) { BigDecimal }\n\n        it 'returns an error when the given value is not BigDecimal' do\n          expect(subject.call(1)).to be_instance_of(Grape::Validations::Types::InvalidValue)\n        end\n\n        it 'returns a value as it is when the given value is BigDecimal' do\n          expect(subject.call(BigDecimal('0'))).to eq(BigDecimal('0'))\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/types/set_coercer_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Types::SetCoercer do\n  subject { described_class.new(type) }\n\n  describe '#call' do\n    context 'a set of primitives' do\n      let(:type) { Set[String] }\n\n      it 'coerces elements to the set' do\n        expect(subject.call([10, 20])).to eq(Set['10', '20'])\n      end\n    end\n\n    context 'a set of sets' do\n      let(:type) { Set[Set[Integer]] }\n\n      it 'coerces elements in the nested set' do\n        expect(subject.call([%w[10 20]])).to eq(Set[Set[10, 20]])\n        expect(subject.call([['10'], ['20']])).to eq(Set[Set[10], Set[20]])\n      end\n    end\n\n    context 'a set of sets of arrays' do\n      let(:type) { Set[Set[Array[Integer]]] }\n\n      it 'coerces elements in the nested set' do\n        expect(subject.call([[['10'], ['20']]])).to eq(Set[Set[Array[10], Array[20]]])\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/types_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Types do\n  let(:foo_type) do\n    Class.new do\n      def self.parse(_); end\n    end\n  end\n  let(:bar_type) do\n    Class.new do\n      def self.parse; end\n    end\n  end\n\n  describe '::primitive?' do\n    [\n      Integer, Float, Numeric, BigDecimal,\n      Grape::API::Boolean, String, Symbol,\n      Date, DateTime, Time\n    ].each do |type|\n      it \"recognizes #{type} as a primitive\" do\n        expect(described_class).to be_primitive(type)\n      end\n    end\n\n    it 'identifies unknown types' do\n      expect(described_class).not_to be_primitive(Object)\n      expect(described_class).not_to be_primitive(foo_type)\n    end\n  end\n\n  describe '::structure?' do\n    [\n      Hash, Array, Set\n    ].each do |type|\n      it \"recognizes #{type} as a structure\" do\n        expect(described_class).to be_structure(type)\n      end\n    end\n  end\n\n  describe '::special?' do\n    [\n      JSON, Array[JSON], File, Rack::Multipart::UploadedFile\n    ].each do |type|\n      it \"provides special handling for #{type.inspect}\" do\n        expect(described_class).to be_special(type)\n      end\n    end\n  end\n\n  describe 'special types' do\n    subject { described_class::SPECIAL[type] }\n\n    context 'when JSON' do\n      let(:type) { JSON }\n\n      it { is_expected.to eq(Grape::Validations::Types::Json) }\n    end\n\n    context 'when Array[JSON]' do\n      let(:type) { Array[JSON] }\n\n      it { is_expected.to eq(Grape::Validations::Types::JsonArray) }\n    end\n\n    context 'when File' do\n      let(:type) { File }\n\n      it { is_expected.to eq(Grape::Validations::Types::File) }\n    end\n\n    context 'when Rack::Multipart::UploadedFile' do\n      let(:type) { Rack::Multipart::UploadedFile }\n\n      it { is_expected.to eq(Grape::Validations::Types::File) }\n    end\n  end\n\n  describe '::custom?' do\n    it 'returns false if the type does not respond to :parse' do\n      expect(described_class).not_to be_custom(Object)\n    end\n\n    it 'returns true if the type responds to :parse with one argument' do\n      expect(described_class).to be_custom(foo_type)\n    end\n\n    it 'returns false if the type\\'s #parse method takes other than one argument' do\n      expect(described_class).not_to be_custom(bar_type)\n    end\n  end\n\n  describe '::build_coercer' do\n    it 'caches the result of the build_coercer method' do\n      a_coercer = described_class.build_coercer(Array[String])\n      b_coercer = described_class.build_coercer(Array[String])\n      expect(a_coercer.object_id).to eq(b_coercer.object_id)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/validators/all_or_none_validator_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Validators::AllOrNoneOfValidator do\n  describe '#validate!' do\n    subject(:validate) { post path, params }\n\n    describe '/' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            optional :beer, :wine, type: Grape::API::Boolean\n            all_or_none_of :beer, :wine\n          end\n          post do\n          end\n        end\n      end\n\n      context 'when all restricted params are present' do\n        let(:path) { '/' }\n        let(:params) { { beer: true, wine: true } }\n\n        it 'does not return a validation error' do\n          validate\n          expect(last_response.status).to eq 201\n        end\n      end\n\n      context 'when a subset of restricted params are present' do\n        let(:path) { '/' }\n        let(:params) { { beer: true } }\n\n        it 'returns a validation error' do\n          validate\n          expect(last_response.status).to eq 400\n          expect(JSON.parse(last_response.body)).to eq(\n            'beer,wine' => ['provide all or none of parameters']\n          )\n        end\n      end\n\n      context 'when no restricted params are present' do\n        let(:path) { '/' }\n        let(:params) { { somethingelse: true } }\n\n        it 'does not return a validation error' do\n          validate\n          expect(last_response.status).to eq 201\n        end\n      end\n    end\n\n    describe '/mixed-params' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            optional :beer, :wine, :other, type: Grape::API::Boolean\n            all_or_none_of :beer, :wine\n          end\n          post 'mixed-params' do\n          end\n        end\n      end\n\n      let(:path) { '/mixed-params' }\n      let(:params) { { beer: true, wine: true, other: true } }\n\n      it 'does not return a validation error' do\n        validate\n        expect(last_response.status).to eq 201\n      end\n    end\n\n    describe '/custom-message' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            optional :beer, :wine, type: Grape::API::Boolean\n            all_or_none_of :beer, :wine, message: 'choose all or none'\n          end\n          post '/custom-message' do\n          end\n        end\n      end\n\n      let(:path) { '/custom-message' }\n      let(:params) { { beer: true } }\n\n      it 'returns a validation error' do\n        validate\n        expect(last_response.status).to eq 400\n        expect(JSON.parse(last_response.body)).to eq(\n          'beer,wine' => ['choose all or none']\n        )\n      end\n    end\n\n    describe '/nested-hash' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            requires :item, type: Hash do\n              optional :beer, :wine, type: Grape::API::Boolean\n              all_or_none_of :beer, :wine\n            end\n          end\n          post '/nested-hash' do\n          end\n        end\n      end\n\n      let(:path) { '/nested-hash' }\n      let(:params) { { item: { beer: true } } }\n\n      it 'returns a validation error with full names of the params' do\n        validate\n        expect(last_response.status).to eq 400\n        expect(JSON.parse(last_response.body)).to eq(\n          'item[beer],item[wine]' => ['provide all or none of parameters']\n        )\n      end\n    end\n\n    describe '/nested-array' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            requires :items, type: Array do\n              optional :beer, :wine, type: Grape::API::Boolean\n              all_or_none_of :beer, :wine\n            end\n          end\n          post '/nested-array' do\n          end\n        end\n      end\n\n      let(:path) { '/nested-array' }\n      let(:params) { { items: [{ beer: true, wine: true }, { wine: true }] } }\n\n      it 'returns a validation error with full names of the params' do\n        validate\n        expect(last_response.status).to eq 400\n        expect(JSON.parse(last_response.body)).to eq(\n          'items[1][beer],items[1][wine]' => ['provide all or none of parameters']\n        )\n      end\n    end\n\n    describe '/deeply-nested-array' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            requires :items, type: Array do\n              requires :nested_items, type: Array do\n                optional :beer, :wine, type: Grape::API::Boolean\n                all_or_none_of :beer, :wine\n              end\n            end\n          end\n          post '/deeply-nested-array' do\n          end\n        end\n      end\n\n      let(:path) { '/deeply-nested-array' }\n      let(:params) { { items: [{ nested_items: [{ beer: true }] }] } }\n\n      it 'returns a validation error with full names of the params' do\n        validate\n        expect(last_response.status).to eq 400\n        expect(JSON.parse(last_response.body)).to eq(\n          'items[0][nested_items][0][beer],items[0][nested_items][0][wine]' => [\n            'provide all or none of parameters'\n          ]\n        )\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/validators/allow_blank_validator_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Validators::AllowBlankValidator do\n  describe 'bad encoding' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :name, type: String, allow_blank: false\n        end\n        get '/bad_encoding'\n      end\n    end\n\n    context 'when value has bad encoding' do\n      it 'does not raise an error' do\n        expect { get('/bad_encoding', { name: \"Hello \\x80\" }) }.not_to raise_error\n      end\n    end\n  end\n\n  describe '/disallow_blank' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :name, allow_blank: false\n        end\n        get '/disallow_blank'\n      end\n    end\n\n    it 'refuses empty string' do\n      get '/disallow_blank', name: ''\n      expect(last_response.status).to eq(400)\n    end\n\n    it 'refuses only whitespaces' do\n      get '/disallow_blank', name: '   '\n      expect(last_response.status).to eq(400)\n\n      get '/disallow_blank', name: \"  \\n \"\n      expect(last_response.status).to eq(400)\n\n      get '/disallow_blank', name: \"\\n\"\n      expect(last_response.status).to eq(400)\n    end\n\n    it 'refuses nil' do\n      get '/disallow_blank', name: nil\n      expect(last_response.status).to eq(400)\n    end\n\n    it 'refuses missing' do\n      get '/disallow_blank'\n      expect(last_response.status).to eq(400)\n    end\n\n    it 'accepts valid input' do\n      get '/disallow_blank', name: 'bob'\n      expect(last_response.status).to eq(200)\n    end\n  end\n\n  describe '/opt_disallow_string_blank' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          optional :name, type: String, allow_blank: false\n        end\n        get '/opt_disallow_string_blank'\n      end\n    end\n\n    it 'allows missing optional strings' do\n      get 'opt_disallow_string_blank'\n      expect(last_response.status).to eq(200)\n    end\n  end\n\n  describe '/allow_blank' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :name, allow_blank: true\n        end\n        get '/allow_blank'\n      end\n    end\n\n    it 'accepts empty input when allow_blank is true' do\n      get '/allow_blank', name: ''\n      expect(last_response.status).to eq(200)\n    end\n  end\n\n  describe 'type-specific blanks' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :val, type: DateTime, allow_blank: true\n        end\n        get '/allow_datetime_blank'\n\n        params do\n          requires :val, type: DateTime, allow_blank: false\n        end\n        get '/disallow_datetime_blank'\n\n        params do\n          requires :val, type: DateTime\n        end\n        get '/default_allow_datetime_blank'\n\n        params do\n          requires :val, type: Date, allow_blank: true\n        end\n        get '/allow_date_blank'\n\n        params do\n          requires :val, type: Integer, allow_blank: true\n        end\n        get '/allow_integer_blank'\n\n        params do\n          requires :val, type: Float, allow_blank: true\n        end\n        get '/allow_float_blank'\n\n        params do\n          requires :val, type: Symbol, allow_blank: true\n        end\n        get '/allow_symbol_blank'\n\n        params do\n          requires :val, type: Grape::API::Boolean, allow_blank: true\n        end\n        get '/allow_boolean_blank'\n\n        params do\n          requires :val, type: Grape::API::Boolean, allow_blank: false\n        end\n        get '/disallow_boolean_blank'\n      end\n    end\n\n    it 'refuses empty string for disallow_datetime_blank' do\n      get '/disallow_datetime_blank', val: ''\n      expect(last_response.status).to eq(400)\n    end\n\n    it 'accepts value when time allow_blank' do\n      get '/disallow_datetime_blank', val: Time.now\n      expect(last_response.status).to eq(200)\n    end\n\n    it 'accepts empty when datetime allow_blank' do\n      get '/allow_datetime_blank', val: ''\n      expect(last_response.status).to eq(200)\n    end\n\n    it 'accepts empty input' do\n      get '/default_allow_datetime_blank', val: ''\n      expect(last_response.status).to eq(200)\n    end\n\n    it 'accepts empty when date allow_blank' do\n      get '/allow_date_blank', val: ''\n      expect(last_response.status).to eq(200)\n    end\n\n    context 'allow_blank when Numeric' do\n      it 'accepts empty when integer allow_blank' do\n        get '/allow_integer_blank', val: ''\n        expect(last_response.status).to eq(200)\n      end\n\n      it 'accepts empty when float allow_blank' do\n        get '/allow_float_blank', val: ''\n        expect(last_response.status).to eq(200)\n      end\n    end\n\n    it 'accepts empty when symbol allow_blank' do\n      get '/allow_symbol_blank', val: ''\n      expect(last_response.status).to eq(200)\n    end\n\n    it 'accepts empty when boolean allow_blank' do\n      get '/allow_boolean_blank', val: ''\n      expect(last_response.status).to eq(200)\n    end\n\n    it 'accepts false when boolean allow_blank' do\n      get '/disallow_boolean_blank', val: false\n      expect(last_response.status).to eq(200)\n    end\n  end\n\n  describe 'in an optional group' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          optional :user, type: Hash do\n            requires :name, allow_blank: false\n          end\n        end\n        get '/disallow_blank_required_param_in_an_optional_group'\n\n        params do\n          optional :user, type: Hash do\n            requires :name, type: Date, allow_blank: true\n          end\n        end\n        get '/allow_blank_date_param_in_an_optional_group'\n\n        params do\n          optional :user, type: Hash do\n            optional :name, allow_blank: false\n            requires :age\n          end\n        end\n        get '/disallow_blank_optional_param_in_an_optional_group'\n      end\n    end\n\n    context 'as a required param' do\n      it 'accepts a missing group, even with a disallwed blank param' do\n        get '/disallow_blank_required_param_in_an_optional_group'\n        expect(last_response.status).to eq(200)\n      end\n\n      it 'accepts a nested missing date value' do\n        get '/allow_blank_date_param_in_an_optional_group', user: { name: '' }\n        expect(last_response.status).to eq(200)\n      end\n\n      it 'refuses a blank value in an existing group' do\n        get '/disallow_blank_required_param_in_an_optional_group', user: { name: '' }\n        expect(last_response.status).to eq(400)\n      end\n    end\n\n    context 'as an optional param' do\n      it 'accepts a missing group, even with a disallwed blank param' do\n        get '/disallow_blank_optional_param_in_an_optional_group'\n        expect(last_response.status).to eq(200)\n      end\n\n      it 'accepts a nested missing optional value' do\n        get '/disallow_blank_optional_param_in_an_optional_group', user: { age: '29' }\n        expect(last_response.status).to eq(200)\n      end\n\n      it 'refuses a blank existing value in an existing scope' do\n        get '/disallow_blank_optional_param_in_an_optional_group', user: { age: '29', name: '' }\n        expect(last_response.status).to eq(400)\n      end\n    end\n  end\n\n  describe 'in a required group' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :user, type: Hash do\n            requires :name, allow_blank: false\n          end\n        end\n        get '/disallow_blank_required_param_in_a_required_group'\n\n        params do\n          requires :user, type: Hash do\n            requires :name, allow_blank: false\n          end\n        end\n        get '/disallow_string_value_in_a_required_hash_group'\n\n        params do\n          requires :user, type: Hash do\n            optional :name, allow_blank: false\n          end\n        end\n        get '/disallow_blank_optional_param_in_a_required_group'\n\n        params do\n          optional :user, type: Hash do\n            optional :name, allow_blank: false\n          end\n        end\n        get '/disallow_string_value_in_an_optional_hash_group'\n      end\n    end\n\n    context 'as a required param' do\n      it 'refuses a blank value in a required existing group' do\n        get '/disallow_blank_required_param_in_a_required_group', user: { name: '' }\n        expect(last_response.status).to eq(400)\n      end\n\n      it 'refuses a string value in a required hash group' do\n        get '/disallow_string_value_in_a_required_hash_group', user: ''\n        expect(last_response.status).to eq(400)\n      end\n    end\n\n    context 'as an optional param' do\n      it 'accepts a nested missing value' do\n        get '/disallow_blank_optional_param_in_a_required_group', user: { age: '29' }\n        expect(last_response.status).to eq(200)\n      end\n\n      it 'refuses a blank existing value in an existing scope' do\n        get '/disallow_blank_optional_param_in_a_required_group', user: { age: '29', name: '' }\n        expect(last_response.status).to eq(400)\n      end\n\n      it 'refuses a string value in an optional hash group' do\n        get '/disallow_string_value_in_an_optional_hash_group', user: ''\n        expect(last_response.status).to eq(400)\n      end\n    end\n  end\n\n  describe 'custom message' do\n    context 'GET /custom_message' do\n      let(:app) do\n        Class.new(Grape::API) do\n          default_format :json\n\n          params do\n            requires :name, allow_blank: { value: false, message: 'has no value' }\n          end\n          get '/custom_message'\n        end\n      end\n\n      it 'refuses empty string' do\n        get '/custom_message', name: ''\n        expect(last_response.body).to eq('{\"error\":\"name has no value\"}')\n      end\n\n      it 'refuses only whitespaces' do\n        get '/custom_message', name: '   '\n        expect(last_response.body).to eq('{\"error\":\"name has no value\"}')\n\n        get '/custom_message', name: \"  \\n \"\n        expect(last_response.body).to eq('{\"error\":\"name has no value\"}')\n\n        get '/custom_message', name: \"\\n\"\n        expect(last_response.body).to eq('{\"error\":\"name has no value\"}')\n      end\n\n      it 'refuses nil' do\n        get '/custom_message', name: nil\n        expect(last_response.body).to eq('{\"error\":\"name has no value\"}')\n      end\n\n      it 'accepts valid input' do\n        get '/custom_message', name: 'bob'\n        expect(last_response.status).to eq(200)\n      end\n    end\n\n    context 'GET /custom_message/disallow_blank_optional_param' do\n      let(:app) do\n        Class.new(Grape::API) do\n          default_format :json\n\n          params do\n            optional :name, allow_blank: { value: false, message: 'has no value' }\n          end\n          get '/custom_message/disallow_blank_optional_param'\n        end\n      end\n\n      it 'refuses empty string for an optional param' do\n        get '/custom_message/disallow_blank_optional_param', name: ''\n        expect(last_response.body).to eq('{\"error\":\"name has no value\"}')\n      end\n    end\n\n    context 'GET /custom_message/allow_blank' do\n      let(:app) do\n        Class.new(Grape::API) do\n          default_format :json\n\n          params do\n            requires :name, allow_blank: true\n          end\n          get '/custom_message/allow_blank'\n        end\n      end\n\n      it 'accepts empty input when allow_blank is true' do\n        get '/custom_message/allow_blank', name: ''\n        expect(last_response.status).to eq(200)\n      end\n    end\n\n    context 'GET /custom_message/allow_datetime_blank' do\n      let(:app) do\n        Class.new(Grape::API) do\n          default_format :json\n\n          params do\n            requires :val, type: DateTime, allow_blank: true\n          end\n          get '/custom_message/allow_datetime_blank'\n        end\n      end\n\n      it 'accepts empty when datetime allow_blank' do\n        get '/custom_message/allow_datetime_blank', val: ''\n        expect(last_response.status).to eq(200)\n      end\n    end\n\n    context 'GET /custom_message/default_allow_datetime_blank' do\n      let(:app) do\n        Class.new(Grape::API) do\n          default_format :json\n\n          params do\n            requires :val, type: DateTime\n          end\n          get '/custom_message/default_allow_datetime_blank'\n        end\n      end\n\n      it 'accepts empty input' do\n        get '/custom_message/default_allow_datetime_blank', val: ''\n        expect(last_response.status).to eq(200)\n      end\n    end\n\n    context 'GET /custom_message/allow_date_blank' do\n      let(:app) do\n        Class.new(Grape::API) do\n          default_format :json\n\n          params do\n            requires :val, type: Date, allow_blank: true\n          end\n          get '/custom_message/allow_date_blank'\n        end\n      end\n\n      it 'accepts empty when date allow_blank' do\n        get '/custom_message/allow_date_blank', val: ''\n        expect(last_response.status).to eq(200)\n      end\n    end\n\n    context 'allow_blank when Numeric' do\n      context 'GET /custom_message/allow_integer_blank' do\n        let(:app) do\n          Class.new(Grape::API) do\n            default_format :json\n\n            params do\n              requires :val, type: Integer, allow_blank: true\n            end\n            get '/custom_message/allow_integer_blank'\n          end\n        end\n\n        it 'accepts empty when integer allow_blank' do\n          get '/custom_message/allow_integer_blank', val: ''\n          expect(last_response.status).to eq(200)\n        end\n      end\n\n      context 'GET /custom_message/allow_float_blank' do\n        let(:app) do\n          Class.new(Grape::API) do\n            default_format :json\n\n            params do\n              requires :val, type: Float, allow_blank: true\n            end\n            get '/custom_message/allow_float_blank'\n          end\n        end\n\n        it 'accepts empty when float allow_blank' do\n          get '/custom_message/allow_float_blank', val: ''\n          expect(last_response.status).to eq(200)\n        end\n      end\n    end\n\n    context 'GET /custom_message/allow_symbol_blank' do\n      let(:app) do\n        Class.new(Grape::API) do\n          default_format :json\n\n          params do\n            requires :val, type: Symbol, allow_blank: true\n          end\n          get '/custom_message/allow_symbol_blank'\n        end\n      end\n\n      it 'accepts empty when symbol allow_blank' do\n        get '/custom_message/allow_symbol_blank', val: ''\n        expect(last_response.status).to eq(200)\n      end\n    end\n\n    context 'GET /custom_message/allow_boolean_blank' do\n      let(:app) do\n        Class.new(Grape::API) do\n          default_format :json\n\n          params do\n            requires :val, type: Grape::API::Boolean, allow_blank: true\n          end\n          get '/custom_message/allow_boolean_blank'\n        end\n      end\n\n      it 'accepts empty when boolean allow_blank' do\n        get '/custom_message/allow_boolean_blank', val: ''\n        expect(last_response.status).to eq(200)\n      end\n    end\n\n    context 'GET /custom_message/disallow_boolean_blank' do\n      let(:app) do\n        Class.new(Grape::API) do\n          default_format :json\n\n          params do\n            requires :val, type: Grape::API::Boolean, allow_blank: { value: false, message: 'has no value' }\n          end\n          get '/custom_message/disallow_boolean_blank'\n        end\n      end\n\n      it 'accepts false when boolean disallow_blank' do\n        get '/custom_message/disallow_boolean_blank', val: false\n        expect(last_response.status).to eq(200)\n      end\n    end\n\n    context 'in an optional group' do\n      context 'GET /custom_message/disallow_blank_required_param_in_an_optional_group' do\n        let(:app) do\n          Class.new(Grape::API) do\n            default_format :json\n\n            params do\n              optional :user, type: Hash do\n                requires :name, allow_blank: { value: false, message: 'has no value' }\n              end\n            end\n            get '/custom_message/disallow_blank_required_param_in_an_optional_group'\n          end\n        end\n\n        it 'accepts a missing group, even with a disallwed blank param' do\n          get '/custom_message/disallow_blank_required_param_in_an_optional_group'\n          expect(last_response.status).to eq(200)\n        end\n\n        it 'refuses a blank value in an existing group' do\n          get '/custom_message/disallow_blank_required_param_in_an_optional_group', user: { name: '' }\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq('{\"error\":\"user[name] has no value\"}')\n        end\n      end\n\n      context 'GET /custom_message/allow_blank_date_param_in_an_optional_group' do\n        let(:app) do\n          Class.new(Grape::API) do\n            default_format :json\n\n            params do\n              optional :user, type: Hash do\n                requires :name, type: Date, allow_blank: true\n              end\n            end\n            get '/custom_message/allow_blank_date_param_in_an_optional_group'\n          end\n        end\n\n        it 'accepts a nested missing date value' do\n          get '/custom_message/allow_blank_date_param_in_an_optional_group', user: { name: '' }\n          expect(last_response.status).to eq(200)\n        end\n      end\n\n      context 'GET /custom_message/disallow_blank_optional_param_in_an_optional_group' do\n        let(:app) do\n          Class.new(Grape::API) do\n            default_format :json\n\n            params do\n              optional :user, type: Hash do\n                optional :name, allow_blank: { value: false, message: 'has no value' }\n                requires :age\n              end\n            end\n            get '/custom_message/disallow_blank_optional_param_in_an_optional_group'\n          end\n        end\n\n        it 'accepts a missing group, even with a disallwed blank param' do\n          get '/custom_message/disallow_blank_optional_param_in_an_optional_group'\n          expect(last_response.status).to eq(200)\n        end\n\n        it 'accepts a nested missing optional value' do\n          get '/custom_message/disallow_blank_optional_param_in_an_optional_group', user: { age: '29' }\n          expect(last_response.status).to eq(200)\n        end\n\n        it 'refuses a blank existing value in an existing scope' do\n          get '/custom_message/disallow_blank_optional_param_in_an_optional_group', user: { age: '29', name: '' }\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq('{\"error\":\"user[name] has no value\"}')\n        end\n      end\n    end\n\n    context 'in a required group' do\n      context 'GET /custom_message/disallow_blank_required_param_in_a_required_group' do\n        let(:app) do\n          Class.new(Grape::API) do\n            default_format :json\n\n            params do\n              requires :user, type: Hash do\n                requires :name, allow_blank: { value: false, message: 'has no value' }\n              end\n            end\n            get '/custom_message/disallow_blank_required_param_in_a_required_group'\n          end\n        end\n\n        it 'refuses a blank value in a required existing group' do\n          get '/custom_message/disallow_blank_required_param_in_a_required_group', user: { name: '' }\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq('{\"error\":\"user[name] has no value\"}')\n        end\n      end\n\n      context 'GET /custom_message/disallow_string_value_in_a_required_hash_group' do\n        let(:app) do\n          Class.new(Grape::API) do\n            default_format :json\n\n            params do\n              requires :user, type: Hash do\n                requires :name, allow_blank: { value: false, message: 'has no value' }\n              end\n            end\n            get '/custom_message/disallow_string_value_in_a_required_hash_group'\n          end\n        end\n\n        it 'refuses a string value in a required hash group' do\n          get '/custom_message/disallow_string_value_in_a_required_hash_group', user: ''\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq('{\"error\":\"user is invalid, user[name] is missing\"}')\n        end\n      end\n\n      context 'GET /custom_message/disallow_blank_optional_param_in_a_required_group' do\n        let(:app) do\n          Class.new(Grape::API) do\n            default_format :json\n\n            params do\n              requires :user, type: Hash do\n                optional :name, allow_blank: { value: false, message: 'has no value' }\n              end\n            end\n            get '/custom_message/disallow_blank_optional_param_in_a_required_group'\n          end\n        end\n\n        it 'accepts a nested missing value' do\n          get '/custom_message/disallow_blank_optional_param_in_a_required_group', user: { age: '29' }\n          expect(last_response.status).to eq(200)\n        end\n\n        it 'refuses a blank existing value in an existing scope' do\n          get '/custom_message/disallow_blank_optional_param_in_a_required_group', user: { age: '29', name: '' }\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq('{\"error\":\"user[name] has no value\"}')\n        end\n      end\n\n      context 'GET /custom_message/disallow_string_value_in_an_optional_hash_group' do\n        let(:app) do\n          Class.new(Grape::API) do\n            default_format :json\n\n            params do\n              optional :user, type: Hash do\n                optional :name, allow_blank: { value: false, message: 'has no value' }\n              end\n            end\n            get '/custom_message/disallow_string_value_in_an_optional_hash_group'\n          end\n        end\n\n        it 'refuses a string value in an optional hash group' do\n          get '/custom_message/disallow_string_value_in_an_optional_hash_group', user: ''\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq('{\"error\":\"user is invalid\"}')\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/validators/at_least_one_of_validator_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Validators::AtLeastOneOfValidator do\n  describe '#validate!' do\n    subject(:validate) { post path, params }\n\n    describe '/' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            optional :beer, :wine, :grapefruit\n            at_least_one_of :beer, :wine, :grapefruit\n          end\n          post do\n          end\n        end\n      end\n\n      context 'when all restricted params are present' do\n        let(:path) { '/' }\n        let(:params) { { beer: true, wine: true, grapefruit: true } }\n\n        it 'does not return a validation error' do\n          validate\n          expect(last_response.status).to eq 201\n        end\n      end\n\n      context 'when a subset of restricted params are present' do\n        let(:path) { '/' }\n        let(:params) { { beer: true, grapefruit: true } }\n\n        it 'does not return a validation error' do\n          validate\n          expect(last_response.status).to eq 201\n        end\n      end\n\n      context 'when none of the restricted params is selected' do\n        let(:path) { '/' }\n        let(:params) { { other: true } }\n\n        it 'returns a validation error' do\n          validate\n          expect(last_response.status).to eq 400\n          expect(JSON.parse(last_response.body)).to eq(\n            'beer,wine,grapefruit' => ['are missing, at least one parameter must be provided']\n          )\n        end\n      end\n\n      context 'when exactly one of the restricted params is selected' do\n        let(:path) { '/' }\n        let(:params) { { beer: true } }\n\n        it 'does not return a validation error' do\n          validate\n          expect(last_response.status).to eq 201\n        end\n      end\n    end\n\n    describe '/mixed-params' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            optional :beer, :wine, :grapefruit, :other\n            at_least_one_of :beer, :wine, :grapefruit\n          end\n          post 'mixed-params' do\n          end\n        end\n      end\n\n      let(:path) { '/mixed-params' }\n      let(:params) { { beer: true, wine: true, grapefruit: true, other: true } }\n\n      it 'does not return a validation error' do\n        validate\n        expect(last_response.status).to eq 201\n      end\n    end\n\n    describe '/custom-message' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            optional :beer, :wine, :grapefruit\n            at_least_one_of :beer, :wine, :grapefruit, message: 'you should choose something'\n          end\n          post '/custom-message' do\n          end\n        end\n      end\n\n      let(:path) { '/custom-message' }\n      let(:params) { { other: true } }\n\n      it 'returns a validation error' do\n        validate\n        expect(last_response.status).to eq 400\n        expect(JSON.parse(last_response.body)).to eq(\n          'beer,wine,grapefruit' => ['you should choose something']\n        )\n      end\n    end\n\n    describe '/nested-hash' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            requires :item, type: Hash do\n              optional :beer, :wine, :grapefruit\n              at_least_one_of :beer, :wine, :grapefruit, message: 'fail'\n            end\n          end\n          post '/nested-hash' do\n          end\n        end\n      end\n\n      let(:path) { '/nested-hash' }\n\n      context 'when at least one of them is present' do\n        let(:params) { { item: { beer: true, wine: true } } }\n\n        it 'does not return a validation error' do\n          validate\n          expect(last_response.status).to eq 201\n        end\n      end\n\n      context 'when none of them are present' do\n        let(:params) { { item: { other: true } } }\n\n        it 'returns a validation error with full names of the params' do\n          validate\n          expect(last_response.status).to eq 400\n          expect(JSON.parse(last_response.body)).to eq(\n            'item[beer],item[wine],item[grapefruit]' => ['fail']\n          )\n        end\n      end\n    end\n\n    describe '/nested-array' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            requires :items, type: Array do\n              optional :beer, :wine, :grapefruit\n              at_least_one_of :beer, :wine, :grapefruit, message: 'fail'\n            end\n          end\n          post '/nested-array' do\n          end\n        end\n      end\n\n      let(:path) { '/nested-array' }\n\n      context 'when at least one of them is present' do\n        let(:params) { { items: [{ beer: true, wine: true }, { grapefruit: true }] } }\n\n        it 'does not return a validation error' do\n          validate\n          expect(last_response.status).to eq 201\n        end\n      end\n\n      context 'when none of them are present' do\n        let(:params) { { items: [{ beer: true, other: true }, { other: true }] } }\n\n        it 'returns a validation error with full names of the params' do\n          validate\n          expect(last_response.status).to eq 400\n          expect(JSON.parse(last_response.body)).to eq(\n            'items[1][beer],items[1][wine],items[1][grapefruit]' => ['fail']\n          )\n        end\n      end\n    end\n\n    describe '/deeply-nested-array' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            requires :items, type: Array do\n              requires :nested_items, type: Array do\n                optional :beer, :wine, :grapefruit\n                at_least_one_of :beer, :wine, :grapefruit, message: 'fail'\n              end\n            end\n          end\n          post '/deeply-nested-array' do\n          end\n        end\n      end\n\n      let(:path) { '/deeply-nested-array' }\n\n      context 'when at least one of them is present' do\n        let(:params) { { items: [{ nested_items: [{ wine: true }] }] } }\n\n        it 'does not return a validation error' do\n          validate\n          expect(last_response.status).to eq 201\n        end\n      end\n\n      context 'when none of them are present' do\n        let(:params) { { items: [{ nested_items: [{ other: true }] }] } }\n\n        it 'returns a validation error with full names of the params' do\n          validate\n          expect(last_response.status).to eq 400\n          expect(JSON.parse(last_response.body)).to eq(\n            'items[0][nested_items][0][beer],items[0][nested_items][0][wine],items[0][nested_items][0][grapefruit]' => ['fail']\n          )\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/validators/base_i18n_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Validators::Base do\n  describe 'i18n' do\n    subject { Class.new(Grape::API) }\n\n    let(:app) { subject }\n\n    let(:custom_i18n_validator) do\n      Class.new(Grape::Validations::Validators::Base) do\n        def validate_param!(attr_name, params)\n          return if params.respond_to?(:key?) && params[attr_name] == 'accept'\n\n          raise Grape::Exceptions::Validation.new(\n            params: @scope.full_name(attr_name),\n            message: message(:custom_i18n_test)\n          )\n        end\n      end\n    end\n\n    around do |example|\n      I18n.available_locales = %i[en zh-CN]\n      I18n.backend.store_translations(:en, grape: { errors: { messages: { custom_i18n_test: 'custom validation failed (en)' } } })\n      I18n.backend.store_translations(:'zh-CN', grape: { errors: { format: '%<attributes>s %<message>s',\n                                                                   messages: { custom_i18n_test: '自定义校验失败 (zh-CN)' } } })\n      example.run\n    ensure\n      I18n.available_locales = %i[en]\n      I18n.reload!\n    end\n\n    before do\n      stub_const('CustomI18nValidator', custom_i18n_validator)\n      Grape::Validations.register(CustomI18nValidator)\n    end\n\n    after { Grape::Validations.deregister('custom_i18n') }\n\n    it 'uses the request-time locale regardless of the locale active at definition time' do\n      # Define the API while zh-CN is the active locale\n      I18n.with_locale(:'zh-CN') do\n        subject.params do\n          requires :token, custom_i18n: true\n        end\n        subject.post do\n        end\n      end\n      # Switch to English before making the request\n      I18n.with_locale(:en) do\n        post '/', token: 'reject'\n      end\n\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('token custom validation failed (en)')\n    end\n\n    it 'uses zh-CN message when request is made with zh-CN locale' do\n      I18n.with_locale(:en) do\n        subject.params do\n          requires :token, custom_i18n: true\n        end\n        subject.post do\n        end\n      end\n\n      I18n.with_locale(:'zh-CN') do\n        post '/', token: 'reject'\n      end\n\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('token 自定义校验失败 (zh-CN)')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/validators/coerce_validator_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Validators::CoerceValidator do\n  subject { Class.new(Grape::API) }\n\n  let(:app) { subject }\n\n  describe 'coerce' do\n    let(:secure_uri_only) do\n      Class.new do\n        def self.parse(value)\n          URI.parse(value)\n        end\n\n        def self.parsed?(value)\n          value.is_a? URI::HTTPS\n        end\n      end\n    end\n\n    context 'i18n' do\n      before do\n        I18n.available_locales = %i[en zh-CN]\n        zh_cn = { grape: { errors: { format: '%<attributes>s%<message>s',\n                                     attributes: { age: '年龄' },\n                                     messages: { coerce: '格式不正确' } } } }\n        I18n.backend.store_translations(:'zh-CN', zh_cn)\n      end\n\n      after do\n        I18n.available_locales = %i[en]\n        I18n.reload!\n      end\n\n      it 'i18n error on malformed input' do\n        subject.params do\n          requires :age, type: Integer\n        end\n        subject.get '/single' do\n          'int works'\n        end\n\n        I18n.with_locale(:'zh-CN') { get '/single', age: '43a' }\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('年龄格式不正确')\n      end\n\n      context 'when the locale has no translation for the message' do\n        before { I18n.available_locales = %i[en pt-BR] }\n\n        it 'gives an english fallback error' do\n          subject.params do\n            requires :age, type: Integer\n          end\n          subject.get '/single' do\n            'int works'\n          end\n\n          I18n.with_locale(:'pt-BR') { get '/single', age: '43a' }\n          expect(last_response).to be_bad_request\n          expect(last_response.body).to eq('age is invalid')\n        end\n      end\n    end\n\n    context 'with a custom validation message' do\n      it 'errors on malformed input' do\n        subject.params do\n          requires :int, type: { value: Integer, message: 'type cast is invalid' }\n        end\n        subject.get '/single' do\n          'int works'\n        end\n\n        get '/single', int: '43a'\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('int type cast is invalid')\n\n        get '/single', int: '43'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('int works')\n      end\n\n      context 'on custom coercion rules' do\n        before do\n          subject.params do\n            requires :a, types: { value: [Grape::API::Boolean, String], message: 'type cast is invalid' }, coerce_with: (lambda do |val|\n              case val\n              when 'yup'\n                true\n              when 'false'\n                0\n              else\n                val\n              end\n            end)\n          end\n          subject.get '/' do\n            params[:a].class.to_s\n          end\n        end\n\n        it 'respects :coerce_with' do\n          get '/', a: 'yup'\n\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('TrueClass')\n        end\n\n        it 'still validates type' do\n          get '/', a: 'false'\n          expect(last_response).to be_bad_request\n          expect(last_response.body).to eq('a type cast is invalid')\n        end\n\n        it 'performs no additional coercion' do\n          get '/', a: 'true'\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('String')\n        end\n      end\n    end\n\n    it 'error on malformed input' do\n      subject.params do\n        requires :int, type: Integer\n      end\n      subject.get '/single' do\n        'int works'\n      end\n\n      get '/single', int: '43a'\n      expect(last_response).to be_bad_request\n      expect(last_response.body).to eq('int is invalid')\n\n      get '/single', int: '43'\n      expect(last_response).to be_successful\n      expect(last_response.body).to eq('int works')\n    end\n\n    it 'error on malformed input (Array)' do\n      subject.params do\n        requires :ids, type: Array[Integer]\n      end\n      subject.get '/array' do\n        'array int works'\n      end\n\n      get 'array', ids: %w[1 2 az]\n      expect(last_response).to be_bad_request\n      expect(last_response.body).to eq('ids is invalid')\n\n      get 'array', ids: %w[1 2 890]\n      expect(last_response).to be_successful\n      expect(last_response.body).to eq('array int works')\n    end\n\n    context 'coerces' do\n      context 'json' do\n        let(:headers) { { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' } }\n\n        it 'BigDecimal' do\n          subject.params do\n            requires :bigdecimal, type: BigDecimal\n          end\n          subject.post '/bigdecimal' do\n            \"#{params[:bigdecimal].class} #{params[:bigdecimal].to_f}\"\n          end\n\n          post '/bigdecimal', { bigdecimal: 45.1 }.to_json, headers\n          expect(last_response).to be_created\n          expect(last_response.body).to eq('BigDecimal 45.1')\n        end\n\n        it 'Grape::API::Boolean' do\n          subject.params do\n            requires :boolean, type: Grape::API::Boolean\n          end\n          subject.post '/boolean' do\n            params[:boolean]\n          end\n\n          post '/boolean', { boolean: 'true' }.to_json, headers\n          expect(last_response).to be_created\n          expect(last_response.body).to eq('true')\n        end\n      end\n\n      it 'BigDecimal' do\n        subject.params do\n          requires :bigdecimal, coerce: BigDecimal\n        end\n        subject.get '/bigdecimal' do\n          params[:bigdecimal].class\n        end\n\n        get '/bigdecimal', bigdecimal: '45'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('BigDecimal')\n      end\n\n      it 'Integer' do\n        subject.params do\n          requires :int, coerce: Integer\n        end\n        subject.get '/int' do\n          params[:int].class\n        end\n\n        get '/int', int: '45'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq(integer_class_name)\n      end\n\n      it 'String' do\n        subject.params do\n          requires :string, coerce: String\n        end\n        subject.get '/string' do\n          params[:string].class\n        end\n\n        get '/string', string: 45\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('String')\n\n        get '/string', string: nil\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('NilClass')\n      end\n\n      context 'a custom type' do\n        it 'coerces the given value' do\n          context = self\n          subject.params do\n            requires :uri, coerce: context.secure_uri_only\n          end\n          subject.get '/secure_uri' do\n            params[:uri].class\n          end\n\n          get 'secure_uri', uri: 'https://www.example.com'\n\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('URI::HTTPS')\n\n          get 'secure_uri', uri: 'http://www.example.com'\n\n          expect(last_response).to be_bad_request\n          expect(last_response.body).to eq('uri is invalid')\n        end\n\n        context 'returning the InvalidValue instance when invalid' do\n          let(:custom_type) do\n            Class.new do\n              def self.parse(_val)\n                Grape::Validations::Types::InvalidValue.new('must be unique')\n              end\n            end\n          end\n\n          it 'uses a custom message added to the invalid value' do\n            type = custom_type\n\n            subject.params do\n              requires :name, type: type\n            end\n            subject.get '/whatever' do\n              params[:name].class\n            end\n\n            get 'whatever', name: 'Bob'\n\n            expect(last_response).to be_bad_request\n            expect(last_response.body).to eq('name must be unique')\n          end\n        end\n      end\n\n      context 'Array' do\n        it 'Array of Integers' do\n          subject.params do\n            requires :arry, coerce: Array[Integer]\n          end\n          subject.get '/array' do\n            params[:arry][0].class\n          end\n\n          get '/array', arry: %w[1 2 3]\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq(integer_class_name)\n        end\n\n        it 'Array of Bools' do\n          subject.params do\n            requires :arry, coerce: Array[Grape::API::Boolean]\n          end\n          subject.get '/array' do\n            params[:arry][0].class\n          end\n\n          get 'array', arry: [1, 0]\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('TrueClass')\n        end\n\n        it 'Array of type implementing parse' do\n          subject.params do\n            requires :uri, type: Array[URI]\n          end\n          subject.get '/uri_array' do\n            params[:uri][0].class\n          end\n          get 'uri_array', uri: ['http://www.google.com']\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('URI::HTTP')\n        end\n\n        it 'Set of type implementing parse' do\n          subject.params do\n            requires :uri, type: Set[URI]\n          end\n          subject.get '/uri_array' do\n            \"#{params[:uri].class},#{params[:uri].first.class},#{params[:uri].size}\"\n          end\n          get 'uri_array', uri: Array.new(2) { 'http://www.example.com' }\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('Set,URI::HTTP,1')\n        end\n\n        it 'Array of a custom type' do\n          context = self\n          subject.params do\n            requires :uri, type: Array[context.secure_uri_only]\n          end\n          subject.get '/secure_uris' do\n            params[:uri].first.class\n          end\n          get 'secure_uris', uri: ['https://www.example.com']\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('URI::HTTPS')\n          get 'secure_uris', uri: ['https://www.example.com', 'http://www.example.com']\n          expect(last_response).to be_bad_request\n          expect(last_response.body).to eq('uri is invalid')\n        end\n      end\n\n      context 'Set' do\n        it 'Set of Integers' do\n          subject.params do\n            requires :set, coerce: Set[Integer]\n          end\n          subject.get '/set' do\n            params[:set].first.class\n          end\n\n          get '/set', set: Set.new([1, 2, 3, 4]).to_a\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq(integer_class_name)\n        end\n\n        it 'Set of Bools' do\n          subject.params do\n            requires :set, coerce: Set[Grape::API::Boolean]\n          end\n          subject.get '/set' do\n            params[:set].first.class\n          end\n\n          get '/set', set: Set.new([1, 0]).to_a\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('TrueClass')\n        end\n      end\n\n      it 'Grape::API::Boolean' do\n        subject.params do\n          requires :boolean, type: Grape::API::Boolean\n        end\n        subject.get '/boolean' do\n          params[:boolean].class\n        end\n\n        get '/boolean', boolean: 1\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('TrueClass')\n      end\n\n      context 'File' do\n        let(:file) { Rack::Test::UploadedFile.new(__FILE__) }\n        let(:filename) { File.basename(__FILE__).to_s }\n\n        it 'Rack::Multipart::UploadedFile' do\n          subject.params do\n            requires :file, type: Rack::Multipart::UploadedFile\n          end\n          subject.post '/upload' do\n            params[:file][:filename]\n          end\n\n          post '/upload', file: file\n          expect(last_response).to be_created\n          expect(last_response.body).to eq(filename)\n\n          post '/upload', file: 'not a file'\n          expect(last_response).to be_bad_request\n          expect(last_response.body).to eq('file is invalid')\n        end\n\n        it 'File' do\n          subject.params do\n            requires :file, coerce: File\n          end\n          subject.post '/upload' do\n            params[:file][:filename]\n          end\n\n          post '/upload', file: file\n          expect(last_response).to be_created\n          expect(last_response.body).to eq(filename)\n\n          post '/upload', file: 'not a file'\n          expect(last_response).to be_bad_request\n          expect(last_response.body).to eq('file is invalid')\n\n          post '/upload', file: { filename: 'fake file', tempfile: '/etc/passwd' }\n          expect(last_response).to be_bad_request\n          expect(last_response.body).to eq('file is invalid')\n        end\n\n        it 'collection' do\n          subject.params do\n            requires :files, type: Array[File]\n          end\n          subject.post '/upload' do\n            params[:files].first[:filename]\n          end\n\n          post '/upload', files: [file]\n          expect(last_response).to be_created\n          expect(last_response.body).to eq(filename)\n        end\n      end\n\n      it 'Nests integers' do\n        subject.params do\n          requires :integers, type: Hash do\n            requires :int, coerce: Integer\n          end\n        end\n        subject.get '/int' do\n          params[:integers][:int].class\n        end\n\n        get '/int', integers: { int: '45' }\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq(integer_class_name)\n      end\n\n      context 'nil values' do\n        context 'primitive types' do\n          Grape::Validations::Types::PRIMITIVES.each do |type|\n            it 'respects the nil value' do\n              subject.params do\n                requires :param, type: type\n              end\n              subject.get '/nil_value' do\n                params[:param].class\n              end\n\n              get '/nil_value', param: nil\n              expect(last_response).to be_successful\n              expect(last_response.body).to eq('NilClass')\n            end\n          end\n        end\n\n        context 'structures types' do\n          Grape::Validations::Types::STRUCTURES.each do |type|\n            it 'respects the nil value' do\n              subject.params do\n                requires :param, type: type\n              end\n              subject.get '/nil_value' do\n                params[:param].class\n              end\n\n              get '/nil_value', param: nil\n              expect(last_response).to be_successful\n              expect(last_response.body).to eq('NilClass')\n            end\n          end\n        end\n\n        context 'special types' do\n          Grape::Validations::Types::SPECIAL.each_key do |type|\n            it 'respects the nil value' do\n              subject.params do\n                requires :param, type: type\n              end\n              subject.get '/nil_value' do\n                params[:param].class\n              end\n\n              get '/nil_value', param: nil\n              expect(last_response).to be_successful\n              expect(last_response.body).to eq('NilClass')\n            end\n          end\n\n          context 'variant-member-type collections' do\n            [\n              Array[Integer, String],\n              [Integer, String, Array[Integer, String]]\n            ].each do |type|\n              it 'respects the nil value' do\n                subject.params do\n                  requires :param, type: type\n                end\n                subject.get '/nil_value' do\n                  params[:param].class\n                end\n\n                get '/nil_value', param: nil\n                expect(last_response).to be_successful\n                expect(last_response.body).to eq('NilClass')\n              end\n            end\n          end\n        end\n      end\n\n      context 'empty string' do\n        context 'primitive types' do\n          (Grape::Validations::Types::PRIMITIVES - [String]).each do |type|\n            it \"is coerced to nil for type #{type}\" do\n              subject.params do\n                requires :param, type: type\n              end\n              subject.get '/empty_string' do\n                params[:param].class\n              end\n\n              get '/empty_string', param: ''\n              expect(last_response).to be_successful\n              expect(last_response.body).to eq('NilClass')\n            end\n          end\n\n          it 'is not coerced to nil for type String' do\n            subject.params do\n              requires :param, type: String\n            end\n            subject.get '/empty_string' do\n              params[:param].class\n            end\n\n            get '/empty_string', param: ''\n            expect(last_response).to be_successful\n            expect(last_response.body).to eq('String')\n          end\n        end\n\n        context 'structures types' do\n          (Grape::Validations::Types::STRUCTURES - [Hash]).each do |type|\n            it \"is coerced to nil for type #{type}\" do\n              subject.params do\n                requires :param, type: type\n              end\n              subject.get '/empty_string' do\n                params[:param].class\n              end\n\n              get '/empty_string', param: ''\n              expect(last_response).to be_successful\n              expect(last_response.body).to eq('NilClass')\n            end\n          end\n        end\n\n        context 'special types' do\n          (Grape::Validations::Types::SPECIAL.keys - [File, Rack::Multipart::UploadedFile]).each do |type|\n            it \"is coerced to nil for type #{type}\" do\n              subject.params do\n                requires :param, type: type\n              end\n              subject.get '/empty_string' do\n                params[:param].class\n              end\n\n              get '/empty_string', param: ''\n              expect(last_response).to be_successful\n              expect(last_response.body).to eq('NilClass')\n            end\n          end\n\n          context 'variant-member-type collections' do\n            [\n              Array[Integer, String],\n              [Integer, String, Array[Integer, String]]\n            ].each do |type|\n              it \"is coerced to nil for type #{type}\" do\n                subject.params do\n                  requires :param, type: type\n                end\n                subject.get '/empty_string' do\n                  params[:param].class\n                end\n\n                get '/empty_string', param: ''\n                expect(last_response).to be_successful\n                expect(last_response.body).to eq('NilClass')\n              end\n            end\n          end\n        end\n      end\n    end\n\n    context 'using coerce_with' do\n      it 'parses parameters with Array type' do\n        subject.params do\n          requires :values, type: Array, coerce_with: ->(val) { val.split(/\\s+/).map(&:to_i) }\n        end\n        subject.get '/ints' do\n          params[:values]\n        end\n\n        get '/ints', values: '1 2 3 4'\n        expect(last_response).to be_successful\n        expect(JSON.parse(last_response.body)).to eq([1, 2, 3, 4])\n\n        get '/ints', values: 'a b c d'\n        expect(last_response).to be_successful\n        expect(JSON.parse(last_response.body)).to eq([0, 0, 0, 0])\n      end\n\n      it 'parses parameters with Array[String] type' do\n        subject.params do\n          requires :values, type: Array[String], coerce_with: ->(val) { val.split(/\\s+/) }\n        end\n        subject.get '/strings' do\n          params[:values]\n        end\n\n        get '/strings', values: '1 2 3 4'\n        expect(last_response).to be_successful\n        expect(JSON.parse(last_response.body)).to eq(%w[1 2 3 4])\n\n        get '/strings', values: 'a b c d'\n        expect(last_response).to be_successful\n        expect(JSON.parse(last_response.body)).to eq(%w[a b c d])\n      end\n\n      it 'parses parameters with Array[Array[String]] type and coerce_with' do\n        subject.params do\n          requires :values, type: Array[Array[String]], coerce_with: ->(val) { val.is_a?(String) ? [val.split(',').map(&:strip)] : val }\n        end\n        subject.post '/coerce_nested_strings' do\n          params[:values]\n        end\n\n        post '/coerce_nested_strings', Grape::Json.dump(values: 'a,b,c,d'), 'CONTENT_TYPE' => 'application/json'\n        expect(last_response).to be_created\n        expect(JSON.parse(last_response.body)).to eq([%w[a b c d]])\n\n        post '/coerce_nested_strings', Grape::Json.dump(values: [%w[a c], %w[b]]), 'CONTENT_TYPE' => 'application/json'\n        expect(last_response).to be_created\n        expect(JSON.parse(last_response.body)).to eq([%w[a c], %w[b]])\n\n        post '/coerce_nested_strings', Grape::Json.dump(values: [[]]), 'CONTENT_TYPE' => 'application/json'\n        expect(last_response).to be_created\n        expect(JSON.parse(last_response.body)).to eq([[]])\n\n        post '/coerce_nested_strings', Grape::Json.dump(values: [['a', { bar: 0 }], ['b']]), 'CONTENT_TYPE' => 'application/json'\n        expect(last_response).to be_bad_request\n      end\n\n      it 'parses parameters with Array[Integer] type' do\n        subject.params do\n          requires :values, type: Array[Integer], coerce_with: ->(val) { val.split(/\\s+/).map(&:to_i) }\n        end\n        subject.get '/ints' do\n          params[:values]\n        end\n\n        get '/ints', values: '1 2 3 4'\n        expect(last_response).to be_successful\n        expect(JSON.parse(last_response.body)).to eq([1, 2, 3, 4])\n\n        get '/ints', values: 'a b c d'\n        expect(last_response).to be_successful\n        expect(JSON.parse(last_response.body)).to eq([0, 0, 0, 0])\n      end\n\n      it 'parses parameters even if type is valid' do\n        subject.params do\n          requires :values, type: Array, coerce_with: ->(array) { array.map { |val| val.to_i + 1 } }\n        end\n        subject.get '/ints' do\n          params[:values]\n        end\n\n        get '/ints', values: [1, 2, 3, 4]\n        expect(last_response).to be_successful\n        expect(JSON.parse(last_response.body)).to eq([2, 3, 4, 5])\n\n        get '/ints', values: %w[a b c d]\n        expect(last_response).to be_successful\n        expect(JSON.parse(last_response.body)).to eq([1, 1, 1, 1])\n      end\n\n      context 'Array type and coerce_with should' do\n        before do\n          subject.params do\n            optional :arr, type: Array, coerce_with: (lambda do |val|\n              if val.nil?\n                []\n              else\n                val\n              end\n            end)\n          end\n          subject.get '/' do\n            params[:arr].class.to_s\n          end\n        end\n\n        it 'coerce nil value to array' do\n          get '/', arr: nil\n\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('Array')\n        end\n\n        it 'not coerce missing field' do\n          get '/'\n\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('NilClass')\n        end\n\n        it 'coerce array as array' do\n          get '/', arr: []\n\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('Array')\n        end\n      end\n\n      it 'uses parse where available' do\n        subject.params do\n          requires :ints, type: Array, coerce_with: JSON do\n            requires :i, type: Integer\n            requires :j\n          end\n        end\n        subject.get '/ints' do\n          ints = params[:ints].first\n          'coercion works' if ints[:i] == 1 && ints[:j] == '2'\n        end\n\n        get '/ints', ints: [{ i: 1, j: '2' }]\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('ints is invalid')\n\n        get '/ints', ints: '{\"i\":1,\"j\":\"2\"}'\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('ints is invalid')\n\n        get '/ints', ints: '[{\"i\":\"1\",\"j\":\"2\"}]'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('coercion works')\n      end\n\n      it 'accepts any callable' do\n        subject.params do\n          requires :ints, type: Hash, coerce_with: JSON.method(:parse) do\n            requires :int, type: Integer, coerce_with: ->(val) { val == 'three' ? 3 : val }\n          end\n        end\n        subject.get '/ints' do\n          params[:ints][:int]\n        end\n\n        get '/ints', ints: '{\"int\":\"3\"}'\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('ints[int] is invalid')\n\n        get '/ints', ints: '{\"int\":\"three\"}'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('3')\n\n        get '/ints', ints: '{\"int\":3}'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('3')\n      end\n\n      context 'Integer type and coerce_with should' do\n        before do\n          subject.params do\n            optional :int, type: Integer, coerce_with: (lambda do |val|\n              if val.nil?\n                0\n              else\n                val.to_i\n              end\n            end)\n          end\n          subject.get '/' do\n            params[:int].class.to_s\n          end\n        end\n\n        it 'coerce nil value to integer' do\n          get '/', int: nil\n\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('Integer')\n        end\n\n        it 'not coerce missing field' do\n          get '/'\n\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('NilClass')\n        end\n\n        it 'coerce integer as integer' do\n          get '/', int: 1\n\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('Integer')\n        end\n      end\n\n      context 'Integer type and coerce_with potentially returning nil' do\n        before do\n          subject.params do\n            requires :int, type: Integer, coerce_with: (lambda do |val|\n              case val\n              when '0'\n                nil\n              when /^-?\\d+$/\n                val.to_i\n              else\n                val\n              end\n            end)\n          end\n          subject.get '/' do\n            params[:int].class.to_s\n          end\n        end\n\n        it 'accepts value that coerces to nil' do\n          get '/', int: '0'\n\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('NilClass')\n        end\n\n        it 'coerces to Integer' do\n          get '/', int: '1'\n\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('Integer')\n        end\n\n        it 'returns invalid value if coercion returns a wrong type' do\n          get '/', int: 'lol'\n\n          expect(last_response).to be_bad_request\n          expect(last_response.body).to eq('int is invalid')\n        end\n      end\n\n      it 'must be supplied with :type or :coerce' do\n        expect do\n          subject.params do\n            requires :ints, coerce_with: JSON\n          end\n        end.to raise_error(ArgumentError)\n      end\n    end\n\n    context 'first-class JSON' do\n      it 'parses objects, hashes, and arrays' do\n        subject.params do\n          requires :splines, type: JSON do\n            requires :x, type: Integer, values: [1, 2, 3]\n            optional :ints, type: Array[Integer]\n            optional :obj, type: Hash do\n              optional :y\n            end\n          end\n        end\n        subject.get '/' do\n          if params[:splines].is_a? Hash\n            params[:splines][:obj][:y]\n          elsif params[:splines].any? { |s| s.key? :obj }\n            'arrays work'\n          end\n        end\n\n        get '/', splines: '{\"x\":1,\"ints\":[1,2,3],\"obj\":{\"y\":\"woof\"}}'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('woof')\n\n        get '/', splines: { x: 1, ints: [1, 2, 3], obj: { y: 'woof' } }\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('woof')\n\n        get '/', splines: '[{\"x\":2,\"ints\":[]},{\"x\":3,\"ints\":[4],\"obj\":{\"y\":\"quack\"}}]'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('arrays work')\n\n        get '/', splines: [{ x: 2, ints: [5] }, { x: 3, ints: [4], obj: { y: 'quack' } }]\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('arrays work')\n\n        get '/', splines: '{\"x\":4,\"ints\":[2]}'\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('splines[x] does not have a valid value')\n\n        get '/', splines: { x: 4, ints: [2] }\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('splines[x] does not have a valid value')\n\n        get '/', splines: '[{\"x\":1,\"ints\":[]},{\"x\":4,\"ints\":[]}]'\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('splines[x] does not have a valid value')\n\n        get '/', splines: [{ x: 1, ints: [5] }, { x: 4, ints: [6] }]\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('splines[x] does not have a valid value')\n      end\n\n      it 'works when declared optional' do\n        subject.params do\n          optional :splines, type: JSON do\n            requires :x, type: Integer, values: [1, 2, 3]\n            optional :ints, type: Array[Integer]\n            optional :obj, type: Hash do\n              optional :y\n            end\n          end\n        end\n        subject.get '/' do\n          if params[:splines].is_a? Hash\n            params[:splines][:obj][:y]\n          elsif params[:splines].any? { |s| s.key? :obj }\n            'arrays work'\n          end\n        end\n\n        get '/', splines: '{\"x\":1,\"ints\":[1,2,3],\"obj\":{\"y\":\"woof\"}}'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('woof')\n\n        get '/', splines: '[{\"x\":2,\"ints\":[]},{\"x\":3,\"ints\":[4],\"obj\":{\"y\":\"quack\"}}]'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('arrays work')\n\n        get '/', splines: '{\"x\":4,\"ints\":[2]}'\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('splines[x] does not have a valid value')\n\n        get '/', splines: '[{\"x\":1,\"ints\":[]},{\"x\":4,\"ints\":[]}]'\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('splines[x] does not have a valid value')\n      end\n\n      it 'accepts Array[JSON] shorthand' do\n        subject.params do\n          requires :splines, type: Array[JSON] do\n            requires :x, type: Integer, values: [1, 2, 3]\n            requires :y\n          end\n        end\n        subject.get '/' do\n          params[:splines].first[:y].class.to_s\n          spline = params[:splines].first\n          \"#{spline[:x].class}.#{spline[:y].class}\"\n        end\n\n        get '/', splines: '{\"x\":\"1\",\"y\":\"woof\"}'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq(\"#{integer_class_name}.String\")\n\n        get '/', splines: '[{\"x\":1,\"y\":2},{\"x\":1,\"y\":\"quack\"}]'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq(\"#{integer_class_name}.#{integer_class_name}\")\n\n        get '/', splines: '{\"x\":\"4\",\"y\":\"woof\"}'\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('splines[x] does not have a valid value')\n\n        get '/', splines: '[{\"x\":\"4\",\"y\":\"woof\"}]'\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('splines[x] does not have a valid value')\n      end\n\n      it \"doesn't make sense using coerce_with\" do\n        expect do\n          subject.params do\n            requires :bad, type: JSON, coerce_with: JSON do\n              requires :x\n            end\n          end\n        end.to raise_error(ArgumentError)\n\n        expect do\n          subject.params do\n            requires :bad, type: Array[JSON], coerce_with: JSON do\n              requires :x\n            end\n          end\n        end.to raise_error(ArgumentError)\n      end\n    end\n\n    context 'multiple types' do\n      it 'coerces to first possible type' do\n        subject.params do\n          requires :a, types: [Grape::API::Boolean, Integer, String]\n        end\n        subject.get '/' do\n          params[:a].class.to_s\n        end\n\n        get '/', a: 'true'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('TrueClass')\n\n        get '/', a: '5'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq(integer_class_name)\n\n        get '/', a: 'anything else'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('String')\n      end\n\n      it 'fails when no coercion is possible' do\n        subject.params do\n          requires :a, types: [Grape::API::Boolean, Integer]\n        end\n        subject.get '/' do\n          params[:a].class.to_s\n        end\n\n        get '/', a: true\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('TrueClass')\n\n        get '/', a: 'not good'\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('a is invalid')\n      end\n\n      context 'for primitive collections' do\n        before do\n          subject.params do\n            optional :a, types: [String, Array[String]]\n            optional :b, types: [Array[Integer], Array[String]]\n            optional :c, type: Array[Integer, String]\n            optional :d, types: [Integer, String, Set[Integer, String]]\n          end\n          subject.get '/' do\n            (\n              params[:a] ||\n              params[:b] ||\n              params[:c] ||\n              params[:d]\n            ).inspect\n          end\n        end\n\n        it 'allows singular form declaration' do\n          get '/', a: 'one way'\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('\"one way\"')\n\n          get '/', a: %w[the other]\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('[\"the\", \"other\"]')\n\n          get '/', a: { a: 1, b: 2 }\n          expect(last_response).to be_bad_request\n          expect(last_response.body).to eq('a is invalid')\n\n          get '/', a: [1, 2, 3]\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('[\"1\", \"2\", \"3\"]')\n        end\n\n        it 'allows multiple collection types' do\n          get '/', b: [1, 2, 3]\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('[1, 2, 3]')\n\n          get '/', b: %w[1 2 3]\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('[1, 2, 3]')\n\n          get '/', b: [1, true, 'three']\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('[\"1\", \"true\", \"three\"]')\n        end\n\n        it 'allows collections with multiple types' do\n          get '/', c: [1, '2', true, 'three']\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('[1, 2, \"true\", \"three\"]')\n\n          get '/', d: '1'\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('1')\n\n          get '/', d: 'one'\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('\"one\"')\n\n          get '/', d: %w[1 two]\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq([1, 'two'].to_set.to_s)\n        end\n      end\n\n      context 'custom coercion rules' do\n        before do\n          subject.params do\n            requires :a, types: [Grape::API::Boolean, String], coerce_with: (lambda do |val|\n              case val\n              when 'yup'\n                true\n              when 'false'\n                0\n              else\n                val\n              end\n            end)\n          end\n          subject.get '/' do\n            params[:a].class.to_s\n          end\n        end\n\n        it 'respects :coerce_with' do\n          get '/', a: 'yup'\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('TrueClass')\n        end\n\n        it 'still validates type' do\n          get '/', a: 'false'\n          expect(last_response).to be_bad_request\n          expect(last_response.body).to eq('a is invalid')\n        end\n\n        it 'performs no additional coercion' do\n          get '/', a: 'true'\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('String')\n        end\n      end\n\n      it 'may not be supplied together with a single type' do\n        expect do\n          subject.params do\n            requires :a, type: Integer, types: [Integer, String]\n          end\n        end.to raise_exception ArgumentError\n      end\n    end\n\n    context 'converter' do\n      it 'does not build a coercer multiple times' do\n        subject.params do\n          requires :something, type: Array[String]\n        end\n        subject.get do\n        end\n\n        expect(Grape::Validations::Types::ArrayCoercer).to(\n          receive(:new).at_most(:once).and_call_original\n        )\n\n        10.times { get '/' }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/validators/contract_scope_validator_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Validators::ContractScopeValidator do\n  describe '.inherits' do\n    subject { described_class }\n\n    it { is_expected.to be < Grape::Validations::Validators::Base }\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/validators/default_validator_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Validators::DefaultValidator do\n  describe '/' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          optional :id\n          optional :type, default: 'default-type'\n        end\n        get '/' do\n          { id: params[:id], type: params[:type] }\n        end\n      end\n    end\n\n    it 'set default value for optional param' do\n      get('/')\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq({ id: nil, type: 'default-type' }.to_json)\n    end\n  end\n\n  describe '/user' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          optional :type1, default: 'default-type1'\n          optional :type2, default: 'default-type2'\n        end\n        get '/user' do\n          { type1: params[:type1], type2: params[:type2] }\n        end\n      end\n    end\n\n    it 'set default values for optional params' do\n      get('/user')\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq({ type1: 'default-type1', type2: 'default-type2' }.to_json)\n    end\n\n    it 'set default values for missing params in the request' do\n      get('/user?type2=value2')\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq({ type1: 'default-type1', type2: 'value2' }.to_json)\n    end\n  end\n\n  describe '/message' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :id\n          optional :type1, default: 'default-type1'\n          optional :type2, default: 'default-type2'\n        end\n\n        get '/message' do\n          { id: params[:id], type1: params[:type1], type2: params[:type2] }\n        end\n      end\n    end\n\n    it 'set default values for optional params and allow to use required fields in the same time' do\n      get('/message?id=1')\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq({ id: '1', type1: 'default-type1', type2: 'default-type2' }.to_json)\n    end\n  end\n\n  describe '/numbers' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          optional :random, default: -> { Random.rand }\n          optional :not_random, default: Random.rand\n        end\n        get '/numbers' do\n          { random_number: params[:random], non_random_number: params[:non_random_number] }\n        end\n      end\n    end\n\n    it 'sets lambda based defaults at the time of call' do\n      get('/numbers')\n      expect(last_response.status).to eq(200)\n      before = JSON.parse(last_response.body)\n      get('/numbers')\n      expect(last_response.status).to eq(200)\n      after = JSON.parse(last_response.body)\n\n      expect(before['non_random_number']).to eq(after['non_random_number'])\n      expect(before['random_number']).not_to eq(after['random_number'])\n    end\n  end\n\n  describe '/array' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          optional :array, type: Array do\n            requires :name\n            optional :with_default, default: 'default'\n          end\n        end\n        get '/array' do\n          { array: params[:array] }\n        end\n      end\n    end\n\n    it 'sets default values for grouped arrays' do\n      get('/array?array[][name]=name&array[][name]=name2&array[][with_default]=bar2')\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq({ array: [{ name: 'name', with_default: 'default' }, { name: 'name2', with_default: 'bar2' }] }.to_json)\n    end\n  end\n\n  describe '/optional_array' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :thing1\n          optional :more_things, type: Array do\n            requires :nested_thing\n            requires :other_thing, default: 1\n          end\n        end\n        get '/optional_array' do\n          { thing1: params[:thing1] }\n        end\n      end\n    end\n\n    it 'lets you leave required values nested inside an optional blank' do\n      get '/optional_array', thing1: 'stuff'\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq({ thing1: 'stuff' }.to_json)\n    end\n  end\n\n  describe '/nested_optional_array' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :root, type: Hash do\n            optional :some_things, type: Array do\n              requires :foo\n              optional :options, type: Array do\n                requires :name, type: String\n                requires :value, type: String\n              end\n            end\n          end\n        end\n        get '/nested_optional_array' do\n          { root: params[:root] }\n        end\n      end\n    end\n\n    it 'allows optional arrays to be omitted' do\n      params = { some_things:\n                  [{ foo: 'one', options: [{ name: 'wat', value: 'nope' }] },\n                   { foo: 'two' },\n                   { foo: 'three', options: [{ name: 'wooop', value: 'yap' }] }] }\n      get '/nested_optional_array', root: params\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq({ root: params }.to_json)\n    end\n\n    it 'does not allows faulty optional arrays' do\n      params = { some_things:\n                   [\n                     { foo: 'one', options: [{ name: 'wat', value: 'nope' }] },\n                     { foo: 'two', options: [{ name: 'wat' }] },\n                     { foo: 'three' }\n                   ] }\n      error = { error: 'root[some_things][1][options][0][value] is missing' }\n      get '/nested_optional_array', root: params\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq(error.to_json)\n    end\n  end\n\n  describe '/another_nested_optional_array' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :root, type: Hash do\n            optional :some_things, type: Array do\n              requires :foo\n              optional :options, type: Array do\n                optional :name, type: String\n                optional :value, type: String\n              end\n            end\n          end\n        end\n        get '/another_nested_optional_array' do\n          { root: params[:root] }\n        end\n      end\n    end\n\n    it 'allows optional arrays with optional params' do\n      params = { some_things:\n                   [\n                     { foo: 'one', options: [{ value: 'nope' }] },\n                     { foo: 'two', options: [{ name: 'wat' }] },\n                     { foo: 'three' }\n                   ] }\n      get '/another_nested_optional_array', root: params\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq({ root: params }.to_json)\n    end\n  end\n\n  describe '/default_values_from_other_params' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :foo\n          optional :bar, default: ->(params) { params[:foo] }\n          optional :qux, default: ->(params) { params[:bar] }\n        end\n        get '/default_values_from_other_params' do\n          {\n            foo: params[:foo],\n            bar: params[:bar],\n            qux: params[:qux]\n          }\n        end\n      end\n    end\n\n    it 'sets default value for optional params using other params values' do\n      expected_foo_value = 'foo-value'\n\n      get(\"/default_values_from_other_params?foo=#{expected_foo_value}\")\n\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq({\n        foo: expected_foo_value,\n        bar: expected_foo_value,\n        qux: expected_foo_value\n      }.to_json)\n    end\n  end\n\n  context 'optional group with defaults' do\n    subject do\n      Class.new(Grape::API) do\n        default_format :json\n      end\n    end\n\n    def app\n      subject\n    end\n\n    context 'optional array without default value includes optional param with default value' do\n      before do\n        subject.params do\n          optional :optional_array, type: Array do\n            optional :foo_in_optional_array, default: 'bar'\n          end\n        end\n        subject.post '/optional_array' do\n          { optional_array: params[:optional_array] }\n        end\n      end\n\n      it 'returns nil for optional array if param is not provided' do\n        post '/optional_array'\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq({ optional_array: nil }.to_json)\n      end\n    end\n\n    context 'optional array with default value includes optional param with default value' do\n      before do\n        subject.params do\n          optional :optional_array_with_default, type: Array, default: [] do\n            optional :foo_in_optional_array, default: 'bar'\n          end\n        end\n        subject.post '/optional_array_with_default' do\n          { optional_array_with_default: params[:optional_array_with_default] }\n        end\n      end\n\n      it 'sets default value for optional array if param is not provided' do\n        post '/optional_array_with_default'\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq({ optional_array_with_default: [] }.to_json)\n      end\n    end\n\n    context 'optional hash without default value includes optional param with default value' do\n      before do\n        subject.params do\n          optional :optional_hash_without_default, type: Hash do\n            optional :foo_in_optional_hash, default: 'bar'\n          end\n        end\n        subject.post '/optional_hash_without_default' do\n          { optional_hash_without_default: params[:optional_hash_without_default] }\n        end\n      end\n\n      it 'returns nil for optional hash if param is not provided' do\n        post '/optional_hash_without_default'\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq({ optional_hash_without_default: nil }.to_json)\n      end\n\n      it 'does not fail even if invalid params is passed to default validator' do\n        expect { post '/optional_hash_without_default', optional_hash_without_default: '5678' }.not_to raise_error\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq({ error: 'optional_hash_without_default is invalid' }.to_json)\n      end\n    end\n\n    context 'optional hash with default value includes optional param with default value' do\n      before do\n        subject.params do\n          optional :optional_hash_with_default, type: Hash, default: {} do\n            optional :foo_in_optional_hash, default: 'bar'\n          end\n        end\n        subject.post '/optional_hash_with_default_empty_hash' do\n          { optional_hash_with_default: params[:optional_hash_with_default] }\n        end\n\n        subject.params do\n          optional :optional_hash_with_default, type: Hash, default: { foo_in_optional_hash: 'parent_default' } do\n            optional :some_param\n            optional :foo_in_optional_hash, default: 'own_default'\n          end\n        end\n        subject.post '/optional_hash_with_default_inner_params' do\n          { foo_in_optional_hash: params[:optional_hash_with_default][:foo_in_optional_hash] }\n        end\n      end\n\n      it 'sets default value for optional hash if param is not provided' do\n        post '/optional_hash_with_default_empty_hash'\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq({ optional_hash_with_default: {} }.to_json)\n      end\n\n      it 'sets default value from parent defaults for inner param if parent param is not provided' do\n        post '/optional_hash_with_default_inner_params'\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq({ foo_in_optional_hash: 'parent_default' }.to_json)\n      end\n\n      it 'sets own default value for inner param if parent param is provided' do\n        post '/optional_hash_with_default_inner_params', optional_hash_with_default: { some_param: 'param' }\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq({ foo_in_optional_hash: 'own_default' }.to_json)\n      end\n    end\n  end\n\n  context 'optional with nil as value' do\n    subject do\n      Class.new(Grape::API) do\n        default_format :json\n      end\n    end\n\n    def app\n      subject\n    end\n\n    context 'primitive types' do\n      [\n        [Integer, 0],\n        [Integer, 42],\n        [Float, 0.0],\n        [Float, 4.2],\n        [BigDecimal, 0.0],\n        [BigDecimal, 4.2],\n        [Numeric, 0],\n        [Numeric, 42],\n        [Date, Date.today],\n        [DateTime, DateTime.now],\n        [Time, Time.now],\n        [Time, Time.at(0)],\n        [Grape::API::Boolean, false],\n        [String, ''],\n        [String, 'non-empty-string'],\n        [Symbol, :symbol],\n        [TrueClass, true],\n        [FalseClass, false]\n      ].each do |type, default|\n        it 'respects the default value' do\n          subject.params do\n            optional :param, type: type, default: default\n          end\n          subject.get '/default_value' do\n            params[:param]\n          end\n\n          get '/default_value', param: nil\n          expect(last_response.status).to eq(200)\n          expect(last_response.body).to eq(default.to_json)\n        end\n      end\n    end\n\n    context 'structures types' do\n      [\n        [Hash, {}],\n        [Hash, { test: 'non-empty' }],\n        [Array, []],\n        [Array, ['non-empty']],\n        [Array[Integer], []],\n        [Set, []],\n        [Set, [1]]\n      ].each do |type, default|\n        it 'respects the default value' do\n          subject.params do\n            optional :param, type: type, default: default\n          end\n          subject.get '/default_value' do\n            params[:param]\n          end\n\n          get '/default_value', param: nil\n          expect(last_response.status).to eq(200)\n          expect(last_response.body).to eq(default.to_json)\n        end\n      end\n    end\n\n    context 'special types' do\n      [\n        [JSON, ''],\n        [JSON, { test: 'non-empty-string' }.to_json],\n        [Array[JSON], []],\n        [Array[JSON], [{ test: 'non-empty-string' }.to_json]],\n        [File, ''],\n        [File, { test: 'non-empty-string' }.to_json],\n        [Rack::Multipart::UploadedFile, ''],\n        [Rack::Multipart::UploadedFile, { test: 'non-empty-string' }.to_json]\n      ].each do |type, default|\n        it 'respects the default value' do\n          subject.params do\n            optional :param, type: type, default: default\n          end\n          subject.get '/default_value' do\n            params[:param]\n          end\n\n          get '/default_value', param: nil\n          expect(last_response.status).to eq(200)\n          expect(last_response.body).to eq(default.to_json)\n        end\n      end\n    end\n\n    context 'variant-member-type collections' do\n      [\n        [Array[Integer, String], [0, '']],\n        [Array[Integer, String], [42, 'non-empty-string']],\n        [[Integer, String, Array[Integer, String]], [0, '', [0, '']]],\n        [[Integer, String, Array[Integer, String]], [42, 'non-empty-string', [42, 'non-empty-string']]]\n      ].each do |type, default|\n        it 'respects the default value' do\n          subject.params do\n            optional :param, type: type, default: default\n          end\n          subject.get '/default_value' do\n            params[:param]\n          end\n\n          get '/default_value', param: nil\n          expect(last_response.status).to eq(200)\n          expect(last_response.body).to eq(default.to_json)\n        end\n      end\n    end\n  end\n\n  context 'array with default values and given conditions' do\n    subject do\n      Class.new(Grape::API) do\n        default_format :json\n      end\n    end\n\n    def app\n      subject\n    end\n\n    it 'applies the default values only if the conditions are met' do\n      subject.params do\n        requires :ary, type: Array do\n          requires :has_value, type: Grape::API::Boolean\n          given has_value: ->(has_value) { has_value } do\n            optional :type, type: String, values: %w[str int], default: 'str'\n            given type: ->(type) { type == 'str' } do\n              optional :str, type: String, default: 'a'\n            end\n            given type: ->(type) { type == 'int' } do\n              optional :int, type: Integer, default: 1\n            end\n          end\n        end\n      end\n      subject.post('/nested_given_and_default') { declared(self.params) }\n\n      params = {\n        ary: [\n          { has_value: false },\n          { has_value: true, type: 'int', int: 123 },\n          { has_value: true, type: 'str', str: 'b' }\n        ]\n      }\n      expected = {\n        'ary' => [\n          { 'has_value' => false, 'type' => nil,   'int' => nil, 'str' => nil },\n          { 'has_value' => true,  'type' => 'int', 'int' => 123, 'str' => nil },\n          { 'has_value' => true,  'type' => 'str', 'int' => nil, 'str' => 'b' }\n        ]\n      }\n\n      post '/nested_given_and_default', params\n      expect(last_response.status).to eq(201)\n      expect(JSON.parse(last_response.body)).to eq(expected)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/validators/exactly_one_of_validator_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Validators::ExactlyOneOfValidator do\n  describe '#validate!' do\n    subject(:validate) { post path, params }\n\n    describe '/' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            optional :beer\n            optional :wine\n            optional :grapefruit\n            exactly_one_of :beer, :wine, :grapefruit\n          end\n          post do\n          end\n        end\n      end\n\n      context 'when all params are present' do\n        let(:path) { '/' }\n        let(:params) { { beer: true, wine: true, grapefruit: true } }\n\n        it 'returns a validation error' do\n          validate\n          expect(last_response.status).to eq 400\n          expect(JSON.parse(last_response.body)).to eq(\n            'beer,wine,grapefruit' => ['are mutually exclusive']\n          )\n        end\n      end\n\n      context 'when a subset of params are present' do\n        let(:path) { '/' }\n        let(:params) { { beer: true, grapefruit: true } }\n\n        it 'returns a validation error' do\n          validate\n          expect(last_response.status).to eq 400\n          expect(JSON.parse(last_response.body)).to eq(\n            'beer,grapefruit' => ['are mutually exclusive']\n          )\n        end\n      end\n\n      context 'when exacly one param is present' do\n        let(:path) { '/' }\n        let(:params) { { beer: true, somethingelse: true } }\n\n        it 'does not return a validation error' do\n          validate\n          expect(last_response.status).to eq 201\n        end\n      end\n\n      context 'when none of the params are present' do\n        let(:path) { '/' }\n        let(:params) { { somethingelse: true } }\n\n        it 'returns a validation error' do\n          validate\n          expect(last_response.status).to eq 400\n          expect(JSON.parse(last_response.body)).to eq(\n            'beer,wine,grapefruit' => ['are missing, exactly one parameter must be provided']\n          )\n        end\n      end\n    end\n\n    describe '/mixed-params' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            optional :beer\n            optional :wine\n            optional :grapefruit\n            optional :other\n            exactly_one_of :beer, :wine, :grapefruit\n          end\n          post 'mixed-params' do\n          end\n        end\n      end\n\n      let(:path) { '/mixed-params' }\n      let(:params) { { beer: true, wine: true, grapefruit: true, other: true } }\n\n      it 'returns a validation error' do\n        validate\n        expect(last_response.status).to eq 400\n        expect(JSON.parse(last_response.body)).to eq(\n          'beer,wine,grapefruit' => ['are mutually exclusive']\n        )\n      end\n    end\n\n    describe '/custom-message' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            optional :beer\n            optional :wine\n            optional :grapefruit\n            exactly_one_of :beer, :wine, :grapefruit, message: 'you should choose one'\n          end\n          post '/custom-message' do\n          end\n        end\n      end\n\n      let(:path) { '/custom-message' }\n      let(:params) { { beer: true, wine: true } }\n\n      it 'returns a validation error' do\n        validate\n        expect(last_response.status).to eq 400\n        expect(JSON.parse(last_response.body)).to eq(\n          'beer,wine' => ['you should choose one']\n        )\n      end\n    end\n\n    describe '/nested-hash' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            requires :item, type: Hash do\n              optional :beer\n              optional :wine\n              optional :grapefruit\n              exactly_one_of :beer, :wine, :grapefruit\n            end\n          end\n          post '/nested-hash' do\n          end\n        end\n      end\n\n      let(:path) { '/nested-hash' }\n      let(:params) { { item: { beer: true, wine: true } } }\n\n      it 'returns a validation error with full names of the params' do\n        validate\n        expect(last_response.status).to eq 400\n        expect(JSON.parse(last_response.body)).to eq(\n          'item[beer],item[wine]' => ['are mutually exclusive']\n        )\n      end\n    end\n\n    describe '/nested-optional-hash' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            optional :item, type: Hash do\n              optional :beer\n              optional :wine\n              optional :grapefruit\n              exactly_one_of :beer, :wine, :grapefruit\n            end\n          end\n          post '/nested-optional-hash' do\n          end\n        end\n      end\n\n      let(:path) { '/nested-optional-hash' }\n\n      context 'when params are passed' do\n        let(:params) { { item: { beer: true, wine: true } } }\n\n        it 'returns a validation error with full names of the params' do\n          validate\n          expect(last_response.status).to eq 400\n          expect(JSON.parse(last_response.body)).to eq(\n            'item[beer],item[wine]' => ['are mutually exclusive']\n          )\n        end\n      end\n\n      context 'when params are empty' do\n        let(:params) { { other: true } }\n\n        it 'does not return a validation error' do\n          validate\n          expect(last_response.status).to eq 201\n        end\n      end\n    end\n\n    describe '/nested-array' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            requires :items, type: Array do\n              optional :beer\n              optional :wine\n              optional :grapefruit\n              exactly_one_of :beer, :wine, :grapefruit\n            end\n          end\n          post '/nested-array' do\n          end\n        end\n      end\n\n      let(:path) { '/nested-array' }\n      let(:params) { { items: [{ beer: true, wine: true }, { wine: true, grapefruit: true }] } }\n\n      it 'returns a validation error with full names of the params' do\n        validate\n        expect(last_response.status).to eq 400\n        expect(JSON.parse(last_response.body)).to eq(\n          'items[0][beer],items[0][wine]' => [\n            'are mutually exclusive'\n          ],\n          'items[1][wine],items[1][grapefruit]' => [\n            'are mutually exclusive'\n          ]\n        )\n      end\n    end\n\n    describe '/deeply-nested-array' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            requires :items, type: Array do\n              requires :nested_items, type: Array do\n                optional :beer, :wine, :grapefruit, type: Grape::API::Boolean\n                exactly_one_of :beer, :wine, :grapefruit\n              end\n            end\n          end\n          post '/deeply-nested-array' do\n          end\n        end\n      end\n\n      let(:path) { '/deeply-nested-array' }\n      let(:params) { { items: [{ nested_items: [{ beer: true, wine: true }] }] } }\n\n      it 'returns a validation error with full names of the params' do\n        validate\n        expect(last_response.status).to eq 400\n        expect(JSON.parse(last_response.body)).to eq(\n          'items[0][nested_items][0][beer],items[0][nested_items][0][wine]' => [\n            'are mutually exclusive'\n          ]\n        )\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/validators/except_values_validator_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Validators::ExceptValuesValidator do\n  describe 'IncompatibleOptionValues' do\n    subject { api }\n\n    context 'when a default value is set' do\n      let(:api) do\n        ev = except_values\n        dv = default_value\n        Class.new(Grape::API) do\n          params do\n            optional :type, except_values: ev, default: dv\n          end\n        end\n      end\n\n      context 'when default value is in exclude' do\n        let(:except_values) { 1..10 }\n        let(:default_value) { except_values.to_a.sample }\n\n        it 'raises IncompatibleOptionValues' do\n          expect { subject }.to raise_error Grape::Exceptions::IncompatibleOptionValues\n        end\n      end\n\n      context 'when default array has excluded values' do\n        let(:except_values) { 1..10 }\n        let(:default_value) { [8, 9, 10] }\n\n        it 'raises IncompatibleOptionValues' do\n          expect { subject }.to raise_error Grape::Exceptions::IncompatibleOptionValues\n        end\n      end\n    end\n\n    context 'when type is incompatible' do\n      let(:api) do\n        Class.new(Grape::API) do\n          params do\n            optional :type, except_values: 1..10, type: Symbol\n          end\n        end\n      end\n\n      it 'raises IncompatibleOptionValues' do\n        expect { subject }.to raise_error Grape::Exceptions::IncompatibleOptionValues\n      end\n    end\n  end\n\n  {\n    req_except: {\n      requires: { except_values: %w[invalid-type1 invalid-type2 invalid-type3] },\n      tests: [\n        { value: 'invalid-type1', rc: 400, body: { error: 'type has a value not allowed' }.to_json },\n        { value: 'invalid-type3', rc: 400, body: { error: 'type has a value not allowed' }.to_json },\n        { value: 'valid-type', rc: 200, body: { type: 'valid-type' }.to_json }\n      ]\n    },\n    req_except_hash: {\n      requires: { except_values: { value: %w[invalid-type1 invalid-type2 invalid-type3] } },\n      tests: [\n        { value: 'invalid-type1', rc: 400, body: { error: 'type has a value not allowed' }.to_json },\n        { value: 'invalid-type3', rc: 400, body: { error: 'type has a value not allowed' }.to_json },\n        { value: 'valid-type', rc: 200, body: { type: 'valid-type' }.to_json }\n      ]\n    },\n    req_except_custom_message: {\n      requires: { except_values: { value: %w[invalid-type1 invalid-type2 invalid-type3], message: 'is not allowed' } },\n      tests: [\n        { value: 'invalid-type1', rc: 400, body: { error: 'type is not allowed' }.to_json },\n        { value: 'invalid-type3', rc: 400, body: { error: 'type is not allowed' }.to_json },\n        { value: 'valid-type', rc: 200, body: { type: 'valid-type' }.to_json }\n      ]\n    },\n    req_except_no_value: {\n      requires: { except_values: { message: 'is not allowed' } },\n      tests: [\n        { value: 'invalid-type1', rc: 200, body: { type: 'invalid-type1' }.to_json }\n      ]\n    },\n    req_except_empty: {\n      requires: { except_values: [] },\n      tests: [\n        { value: 'invalid-type1', rc: 200, body: { type: 'invalid-type1' }.to_json }\n      ]\n    },\n    req_except_lambda: {\n      requires: { except_values: -> { %w[invalid-type1 invalid-type2 invalid-type3 invalid-type4] } },\n      tests: [\n        { value: 'invalid-type1', rc: 400, body: { error: 'type has a value not allowed' }.to_json },\n        { value: 'invalid-type4', rc: 400, body: { error: 'type has a value not allowed' }.to_json },\n        { value: 'valid-type', rc: 200, body: { type: 'valid-type' }.to_json }\n      ]\n    },\n    req_except_lambda_custom_message: {\n      requires: { except_values: { value: -> { %w[invalid-type1 invalid-type2 invalid-type3 invalid-type4] }, message: 'is not allowed' } },\n      tests: [\n        { value: 'invalid-type1', rc: 400, body: { error: 'type is not allowed' }.to_json },\n        { value: 'invalid-type4', rc: 400, body: { error: 'type is not allowed' }.to_json },\n        { value: 'valid-type', rc: 200, body: { type: 'valid-type' }.to_json }\n      ]\n    },\n    opt_except_default: {\n      optional: { except_values: %w[invalid-type1 invalid-type2 invalid-type3], default: 'valid-type2' },\n      tests: [\n        { value: 'invalid-type1', rc: 400, body: { error: 'type has a value not allowed' }.to_json },\n        { value: 'invalid-type3', rc: 400, body: { error: 'type has a value not allowed' }.to_json },\n        { value: 'valid-type', rc: 200, body: { type: 'valid-type' }.to_json },\n        { rc: 200, body: { type: 'valid-type2' }.to_json }\n      ]\n    },\n    opt_except_lambda_default: {\n      optional: { except_values: -> { %w[invalid-type1 invalid-type2 invalid-type3] }, default: 'valid-type2' },\n      tests: [\n        { value: 'invalid-type1', rc: 400, body: { error: 'type has a value not allowed' }.to_json },\n        { value: 'invalid-type3', rc: 400, body: { error: 'type has a value not allowed' }.to_json },\n        { value: 'valid-type', rc: 200, body: { type: 'valid-type' }.to_json },\n        { rc: 200, body: { type: 'valid-type2' }.to_json }\n      ]\n    },\n    req_except_type_coerce: {\n      requires: { type: Integer, except_values: [10, 11] },\n      tests: [\n        { value: 'invalid-type1', rc: 400, body: { error: 'type is invalid' }.to_json },\n        { value: 11, rc: 400, body: { error: 'type has a value not allowed' }.to_json },\n        { value: '11', rc: 400, body: { error: 'type has a value not allowed' }.to_json },\n        { value: '3', rc: 200, body: { type: 3 }.to_json },\n        { value: 3, rc: 200, body: { type: 3 }.to_json }\n      ]\n    },\n    opt_except_type_coerce_default: {\n      optional: { type: Integer, except_values: [10, 11], default: 12 },\n      tests: [\n        { value: 'invalid-type1', rc: 400, body: { error: 'type is invalid' }.to_json },\n        { value: 10, rc: 400, body: { error: 'type has a value not allowed' }.to_json },\n        { value: '3', rc: 200, body: { type: 3 }.to_json },\n        { value: 3, rc: 200, body: { type: 3 }.to_json },\n        { rc: 200, body: { type: 12 }.to_json }\n      ]\n    },\n    opt_except_array_type_coerce_default: {\n      optional: { type: Array[Integer], except_values: [10, 11], default: 12 },\n      tests: [\n        { value: 'invalid-type1', rc: 400, body: { error: 'type is invalid' }.to_json },\n        { value: 10, rc: 400, body: { error: 'type is invalid' }.to_json },\n        { value: [10], rc: 400, body: { error: 'type has a value not allowed' }.to_json },\n        { value: ['3'], rc: 200, body: { type: [3] }.to_json },\n        { value: [3], rc: 200, body: { type: [3] }.to_json },\n        { rc: 200, body: { type: 12 }.to_json }\n      ]\n    },\n    req_except_range: {\n      optional: { type: Integer, except_values: 10..12 },\n      tests: [\n        { value: 11, rc: 400, body: { error: 'type has a value not allowed' }.to_json },\n        { value: 13, rc: 200, body: { type: 13 }.to_json }\n      ]\n    }\n  }.each do |path, param_def|\n    param_def[:tests].each do |t|\n      describe \"when #{path}\" do\n        let(:app) do\n          Class.new(Grape::API) do\n            default_format :json\n            params do\n              requires :type, **param_def[:requires] if param_def.key? :requires\n              optional :type, **param_def[:optional] if param_def.key? :optional\n            end\n            get path do\n              { type: params[:type] }\n            end\n          end\n        end\n\n        let(:body) do\n          {}.tap do |body|\n            body[:type] = t[:value] if t.key? :value\n          end\n        end\n\n        before do\n          get path.to_s, **body\n        end\n\n        it \"returns body #{t[:body]} with status #{t[:rc]}\" do\n          expect(last_response.status).to eq t[:rc]\n          expect(last_response.body).to eq t[:body]\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/validators/length_validator_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Validators::LengthValidator do\n  describe '/with_min_max' do\n    let(:app) do\n      Class.new(Grape::API) do\n        params do\n          requires :list, length: { min: 2, max: 3 }\n        end\n        post 'with_min_max' do\n        end\n      end\n    end\n\n    context 'when length is within limits' do\n      it do\n        post '/with_min_max', list: [1, 2]\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq('')\n      end\n    end\n\n    context 'when length is exceeded' do\n      it do\n        post '/with_min_max', list: [1, 2, 3, 4, 5]\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('list is expected to have length within 2 and 3')\n      end\n    end\n\n    context 'when length is less than minimum' do\n      it do\n        post '/with_min_max', list: [1]\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('list is expected to have length within 2 and 3')\n      end\n    end\n  end\n\n  describe '/with_max_only' do\n    let(:app) do\n      Class.new(Grape::API) do\n        params do\n          requires :list, type: [Integer], length: { max: 3 }\n        end\n        post 'with_max_only' do\n        end\n      end\n    end\n\n    context 'when length is less than limits' do\n      it do\n        post '/with_max_only', list: [1, 2]\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq('')\n      end\n    end\n\n    context 'when length is exceeded' do\n      it do\n        post '/with_max_only', list: [1, 2, 3, 4, 5]\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('list is expected to have length less than or equal to 3')\n      end\n    end\n  end\n\n  describe '/with_min_only' do\n    let(:app) do\n      Class.new(Grape::API) do\n        params do\n          requires :list, type: [Integer], length: { min: 2 }\n        end\n        post 'with_min_only' do\n        end\n      end\n    end\n\n    context 'when length is greater than limit' do\n      it do\n        post '/with_min_only', list: [1, 2]\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq('')\n      end\n    end\n\n    context 'when length is less than limit' do\n      it do\n        post '/with_min_only', list: [1]\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('list is expected to have length greater than or equal to 2')\n      end\n    end\n  end\n\n  describe '/zero_min' do\n    let(:app) do\n      Class.new(Grape::API) do\n        params do\n          requires :list, type: [JSON], length: { min: 0 }\n        end\n        post 'zero_min' do\n        end\n      end\n    end\n\n    context 'when length is equal to the limit' do\n      it do\n        post '/zero_min', list: '[]'\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq('')\n      end\n    end\n\n    context 'when length is greater than limit' do\n      it do\n        post '/zero_min', list: [{ key: 'value' }]\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq('')\n      end\n    end\n  end\n\n  describe '/zero_max' do\n    let(:app) do\n      Class.new(Grape::API) do\n        params do\n          requires :list, type: [JSON], length: { max: 0 }\n        end\n        post 'zero_max' do\n        end\n      end\n    end\n\n    context 'when length is within the limit' do\n      it do\n        post '/zero_max', list: '[]'\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq('')\n      end\n    end\n\n    context 'when length is greater than limit' do\n      it do\n        post '/zero_max', list: [{ key: 'value' }]\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('list is expected to have length less than or equal to 0')\n      end\n    end\n  end\n\n  describe '/type_is_not_array' do\n    let(:app) do\n      Class.new(Grape::API) do\n        params do\n          requires :list, type: Integer, length: { max: 3 }\n        end\n        post 'type_is_not_array' do\n        end\n      end\n    end\n\n    context 'does not raise an error' do\n      it do\n        expect do\n          post 'type_is_not_array', list: 12\n        end.not_to raise_error\n      end\n    end\n  end\n\n  describe '/type_supports_length' do\n    let(:app) do\n      Class.new(Grape::API) do\n        params do\n          requires :list, type: Hash, length: { max: 3 }\n        end\n        post 'type_supports_length' do\n        end\n      end\n    end\n\n    context 'when length is within limits' do\n      it do\n        post 'type_supports_length', list: { key: 'value' }\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq('')\n      end\n    end\n\n    context 'when length exceeds the limit' do\n      it do\n        post 'type_supports_length', list: { key: 'value', key1: 'value', key3: 'value', key4: 'value' }\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('list is expected to have length less than or equal to 3')\n      end\n    end\n  end\n\n  describe '/negative_min' do\n    context 'when min is negative' do\n      let(:app) do\n        Class.new(Grape::API) do\n          params do\n            requires :list, type: [Integer], length: { min: -3 }\n          end\n          post 'negative_min' do\n          end\n        end\n      end\n\n      it 'raises an error' do\n        expect { post 'negative_min', list: [12] }.to raise_error(ArgumentError, 'min must be an integer greater than or equal to zero')\n      end\n    end\n  end\n\n  describe '/negative_max' do\n    context 'it raises an error' do\n      let(:app) do\n        Class.new(Grape::API) do\n          params do\n            requires :list, type: [Integer], length: { max: -3 }\n          end\n          post 'negative_max' do\n          end\n        end\n      end\n\n      it do\n        expect { post 'negative_max', list: [12] }.to raise_error(ArgumentError, 'max must be an integer greater than or equal to zero')\n      end\n    end\n  end\n\n  describe '/float_min' do\n    context 'when min is not an integer' do\n      let(:app) do\n        Class.new(Grape::API) do\n          params do\n            requires :list, type: [Integer], length: { min: 2.5 }\n          end\n          post 'float_min' do\n          end\n        end\n      end\n\n      it do\n        expect { post 'float_min', list: [12] }.to raise_error(ArgumentError, 'min must be an integer greater than or equal to zero')\n      end\n    end\n  end\n\n  describe '/float_max' do\n    context 'when max is not an integer' do\n      let(:app) do\n        Class.new(Grape::API) do\n          params do\n            requires :list, type: [Integer], length: { max: 2.5 }\n          end\n          post 'float_max' do\n          end\n        end\n      end\n\n      it do\n        expect { post 'float_max', list: [12] }.to raise_error(ArgumentError, 'max must be an integer greater than or equal to zero')\n      end\n    end\n  end\n\n  describe '/min_greater_than_max' do\n    context 'raises an error' do\n      let(:app) do\n        Class.new(Grape::API) do\n          params do\n            requires :list, type: [Integer], length: { min: 15, max: 3 }\n          end\n          post 'min_greater_than_max' do\n          end\n        end\n      end\n\n      it do\n        expect { post 'min_greater_than_max', list: [12] }.to raise_error(ArgumentError, 'min 15 cannot be greater than max 3')\n      end\n    end\n  end\n\n  describe '/min_equal_to_max' do\n    let(:app) do\n      Class.new(Grape::API) do\n        params do\n          requires :list, type: [Integer], length: { min: 3, max: 3 }\n        end\n        post 'min_equal_to_max' do\n        end\n      end\n    end\n\n    context 'when array meets expectations' do\n      it do\n        post 'min_equal_to_max', list: [1, 2, 3]\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq('')\n      end\n    end\n\n    context 'when array is less than min' do\n      it do\n        post 'min_equal_to_max', list: [1, 2]\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('list is expected to have length within 3 and 3')\n      end\n    end\n\n    context 'when array is greater than max' do\n      it do\n        post 'min_equal_to_max', list: [1, 2, 3, 4]\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('list is expected to have length within 3 and 3')\n      end\n    end\n  end\n\n  describe '/custom-message' do\n    let(:app) do\n      Class.new(Grape::API) do\n        params do\n          requires :list, type: [Integer], length: { min: 2, message: 'not match' }\n        end\n        post '/custom-message' do\n        end\n      end\n    end\n\n    context 'is within limits' do\n      it do\n        post '/custom-message', list: [1, 2, 3]\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq('')\n      end\n    end\n\n    context 'is outside limit' do\n      it do\n        post '/custom-message', list: [1]\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('list not match')\n      end\n    end\n  end\n\n  describe '/is' do\n    let(:app) do\n      Class.new(Grape::API) do\n        params do\n          requires :code, length: { is: 2 }\n        end\n        post 'is' do\n        end\n      end\n    end\n\n    context 'when length is exact' do\n      it do\n        post 'is', code: 'ZZ'\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq('')\n      end\n    end\n\n    context 'when length exceeds the limit' do\n      it do\n        post 'is', code: 'aze'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('code is expected to have length exactly equal to 2')\n      end\n    end\n\n    context 'when length is less than the limit' do\n      it do\n        post 'is', code: 'a'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('code is expected to have length exactly equal to 2')\n      end\n    end\n\n    context 'when length is zero' do\n      it do\n        post 'is', code: ''\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('code is expected to have length exactly equal to 2')\n      end\n    end\n  end\n\n  describe '/negative_is' do\n    let(:app) do\n      Class.new(Grape::API) do\n        params do\n          requires :code, length: { is: -2 }\n        end\n        post 'negative_is' do\n        end\n      end\n    end\n\n    context 'when `is` is negative' do\n      it do\n        expect { post 'negative_is', code: 'ZZ' }.to raise_error(ArgumentError, 'is must be an integer greater than zero')\n      end\n    end\n  end\n\n  describe '/is_with_max' do\n    context 'when `is` is combined with max' do\n      let(:app) do\n        Class.new(Grape::API) do\n          params do\n            requires :code, length: { is: 2, max: 10 }\n          end\n          post 'is_with_max' do\n          end\n        end\n      end\n\n      it do\n        expect { post 'is_with_max', code: 'ZZ' }.to raise_error(ArgumentError, 'is cannot be combined with min or max')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/validators/mutually_exclusive_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Validators::MutuallyExclusiveValidator do\n  describe '#validate!' do\n    subject(:validate) { post path, params }\n\n    describe '/' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            optional :beer\n            optional :wine\n            optional :grapefruit\n            mutually_exclusive :beer, :wine, :grapefruit\n          end\n          post do\n          end\n        end\n      end\n\n      context 'when all mutually exclusive params are present' do\n        let(:path) { '/' }\n        let(:params) { { beer: true, wine: true, grapefruit: true } }\n\n        it 'returns a validation error' do\n          validate\n          expect(last_response.status).to eq 400\n          expect(JSON.parse(last_response.body)).to eq(\n            'beer,wine,grapefruit' => ['are mutually exclusive']\n          )\n        end\n      end\n\n      context 'when a subset of mutually exclusive params are present' do\n        let(:path) { '/' }\n        let(:params) { { beer: true, grapefruit: true } }\n\n        it 'returns a validation error' do\n          validate\n          expect(last_response.status).to eq 400\n          expect(JSON.parse(last_response.body)).to eq(\n            'beer,grapefruit' => ['are mutually exclusive']\n          )\n        end\n      end\n\n      context 'when no mutually exclusive params are present' do\n        let(:path) { '/' }\n        let(:params) { { beer: true, somethingelse: true } }\n\n        it 'does not return a validation error' do\n          validate\n          expect(last_response.status).to eq 201\n        end\n      end\n    end\n\n    describe '/mixed-params' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            optional :beer\n            optional :wine\n            optional :grapefruit\n            optional :other\n            mutually_exclusive :beer, :wine, :grapefruit\n          end\n          post 'mixed-params' do\n          end\n        end\n      end\n\n      let(:path) { '/mixed-params' }\n      let(:params) { { beer: true, wine: true, grapefruit: true, other: true } }\n\n      it 'returns a validation error' do\n        validate\n        expect(last_response.status).to eq 400\n        expect(JSON.parse(last_response.body)).to eq(\n          'beer,wine,grapefruit' => ['are mutually exclusive']\n        )\n      end\n    end\n\n    describe '/custom-message' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            optional :beer\n            optional :wine\n            optional :grapefruit\n            mutually_exclusive :beer, :wine, :grapefruit, message: 'you should not mix beer and wine'\n          end\n          post '/custom-message' do\n          end\n        end\n      end\n\n      let(:path) { '/custom-message' }\n      let(:params) { { beer: true, wine: true } }\n\n      it 'returns a validation error' do\n        validate\n        expect(last_response.status).to eq 400\n        expect(JSON.parse(last_response.body)).to eq(\n          'beer,wine' => ['you should not mix beer and wine']\n        )\n      end\n    end\n\n    describe '/nested-hash' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            requires :item, type: Hash do\n              optional :beer\n              optional :wine\n              optional :grapefruit\n              mutually_exclusive :beer, :wine, :grapefruit\n            end\n          end\n          post '/nested-hash' do\n          end\n        end\n      end\n\n      let(:path) { '/nested-hash' }\n      let(:params) { { item: { beer: true, wine: true } } }\n\n      it 'returns a validation error with full names of the params' do\n        validate\n        expect(last_response.status).to eq 400\n        expect(JSON.parse(last_response.body)).to eq(\n          'item[beer],item[wine]' => ['are mutually exclusive']\n        )\n      end\n    end\n\n    describe '/nested-optional-hash' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            optional :item, type: Hash do\n              optional :beer\n              optional :wine\n              optional :grapefruit\n              mutually_exclusive :beer, :wine, :grapefruit\n            end\n          end\n          post '/nested-optional-hash' do\n          end\n        end\n      end\n\n      let(:path) { '/nested-optional-hash' }\n\n      context 'when params are passed' do\n        let(:params) { { item: { beer: true, wine: true } } }\n\n        it 'returns a validation error with full names of the params' do\n          validate\n          expect(last_response.status).to eq 400\n          expect(JSON.parse(last_response.body)).to eq(\n            'item[beer],item[wine]' => ['are mutually exclusive']\n          )\n        end\n      end\n\n      context 'when params are empty' do\n        let(:params) { {} }\n\n        it 'does not return a validation error' do\n          validate\n          expect(last_response.status).to eq 201\n        end\n      end\n    end\n\n    describe '/nested-array' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            requires :items, type: Array do\n              optional :beer\n              optional :wine\n              optional :grapefruit\n              mutually_exclusive :beer, :wine, :grapefruit\n            end\n          end\n          post '/nested-array' do\n          end\n        end\n      end\n\n      let(:path) { '/nested-array' }\n      let(:params) { { items: [{ beer: true, wine: true }, { wine: true, grapefruit: true }] } }\n\n      it 'returns a validation error with full names of the params' do\n        validate\n        expect(last_response.status).to eq 400\n        expect(JSON.parse(last_response.body)).to eq(\n          'items[0][beer],items[0][wine]' => ['are mutually exclusive'],\n          'items[1][wine],items[1][grapefruit]' => ['are mutually exclusive']\n        )\n      end\n    end\n\n    describe '/deeply-nested-array' do\n      let(:app) do\n        Class.new(Grape::API) do\n          rescue_from Grape::Exceptions::ValidationErrors do |e|\n            error!(e.errors.transform_keys! { |key| key.join(',') }, 400)\n          end\n\n          params do\n            requires :items, type: Array do\n              requires :nested_items, type: Array do\n                optional :beer, :wine, :grapefruit, type: Grape::API::Boolean\n                mutually_exclusive :beer, :wine, :grapefruit\n              end\n            end\n          end\n          post '/deeply-nested-array' do\n          end\n        end\n      end\n\n      let(:path) { '/deeply-nested-array' }\n      let(:params) { { items: [{ nested_items: [{ beer: true, wine: true }] }] } }\n\n      it 'returns a validation error with full names of the params' do\n        validate\n        expect(last_response.status).to eq 400\n        expect(JSON.parse(last_response.body)).to eq(\n          'items[0][nested_items][0][beer],items[0][nested_items][0][wine]' => ['are mutually exclusive']\n        )\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/validators/presence_validator_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Validators::PresenceValidator do\n  subject do\n    Class.new(Grape::API) do\n      format :json\n    end\n  end\n\n  def app\n    subject\n  end\n\n  context 'without validation' do\n    before do\n      subject.resource :bacons do\n        get do\n          'All the bacon'\n        end\n      end\n    end\n\n    it 'does not validate for any params' do\n      get '/bacons'\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('All the bacon'.to_json)\n    end\n  end\n\n  context 'with a custom validation message' do\n    before do\n      subject.resource :requires do\n        params do\n          requires :email, type: String, allow_blank: { value: false, message: 'has no value' }, regexp: { value: /^\\S+$/, message: 'format is invalid' }, message: 'is required'\n        end\n        get do\n          'Hello'\n        end\n      end\n    end\n\n    it 'requires when missing' do\n      get '/requires'\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('{\"error\":\"email is required, email has no value\"}')\n    end\n\n    it 'requires when empty' do\n      get '/requires', email: ''\n      expect(last_response.body).to eq('{\"error\":\"email has no value, email format is invalid\"}')\n    end\n\n    it 'valid when set' do\n      get '/requires', email: 'bob@example.com'\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('Hello'.to_json)\n    end\n  end\n\n  context 'with a required regexp parameter supplied in the POST body' do\n    before do\n      subject.format :json\n      subject.params do\n        requires :id, regexp: /^[0-9]+$/\n      end\n      subject.post do\n        { ret: params[:id] }\n      end\n    end\n\n    it 'validates id' do\n      post '/'\n      expect(last_response).to be_bad_request\n      expect(last_response.body).to eq('{\"error\":\"id is missing\"}')\n\n      post '/', { id: 'a56b' }.to_json, 'CONTENT_TYPE' => 'application/json'\n      expect(last_response.body).to eq('{\"error\":\"id is invalid\"}')\n      expect(last_response).to be_bad_request\n\n      post '/', { id: 56 }.to_json, 'CONTENT_TYPE' => 'application/json'\n      expect(last_response.body).to eq('{\"ret\":56}')\n      expect(last_response).to be_created\n    end\n  end\n\n  context 'with a required non-empty string' do\n    before do\n      subject.params do\n        requires :email, type: String, allow_blank: false, regexp: /^\\S+$/\n      end\n      subject.get do\n        'Hello'\n      end\n    end\n\n    it 'requires when missing' do\n      get '/'\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('{\"error\":\"email is missing, email is empty\"}')\n    end\n\n    it 'requires when empty' do\n      get '/', email: ''\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('{\"error\":\"email is empty, email is invalid\"}')\n    end\n\n    it 'valid when set' do\n      get '/', email: 'bob@example.com'\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('Hello'.to_json)\n    end\n  end\n\n  context 'with multiple parameters per requires' do\n    before do\n      subject.params do\n        requires :one, :two\n      end\n      subject.get '/single-requires' do\n        'Hello'\n      end\n\n      subject.params do\n        requires :one\n        requires :two\n      end\n      subject.get '/multiple-requires' do\n        'Hello'\n      end\n    end\n\n    it 'validates for all defined params' do\n      get '/single-requires'\n      expect(last_response.status).to eq(400)\n      single_requires_error = last_response.body\n\n      get '/multiple-requires'\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq(single_requires_error)\n    end\n  end\n\n  context 'with required parameters and no type' do\n    before do\n      subject.params do\n        requires :name, :company\n      end\n      subject.get do\n        'Hello'\n      end\n    end\n\n    it 'validates name, company' do\n      get '/'\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('{\"error\":\"name is missing, company is missing\"}')\n\n      get '/', name: 'Bob'\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('{\"error\":\"company is missing\"}')\n\n      get '/', name: 'Bob', company: 'TestCorp'\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('Hello'.to_json)\n    end\n  end\n\n  context 'with nested parameters' do\n    before do\n      subject.params do\n        requires :user, type: Hash do\n          requires :first_name\n          requires :last_name\n        end\n      end\n      subject.get '/nested' do\n        'Nested'\n      end\n    end\n\n    it 'validates nested parameters' do\n      get '/nested'\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('{\"error\":\"user is missing, user[first_name] is missing, user[last_name] is missing\"}')\n\n      get '/nested', user: { first_name: 'Billy' }\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('{\"error\":\"user[last_name] is missing\"}')\n\n      get '/nested', user: { first_name: 'Billy', last_name: 'Bob' }\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('Nested'.to_json)\n    end\n  end\n\n  context 'with triply nested required parameters' do\n    before do\n      subject.params do\n        requires :admin, type: Hash do\n          requires :admin_name\n          requires :super, type: Hash do\n            requires :user, type: Hash do\n              requires :first_name\n              requires :last_name\n            end\n          end\n        end\n      end\n      subject.get '/nested_triple' do\n        'Nested triple'\n      end\n    end\n\n    it 'validates triple nested parameters' do\n      get '/nested_triple'\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to include '{\"error\":\"admin is missing'\n\n      get '/nested_triple', user: { first_name: 'Billy' }\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to include '{\"error\":\"admin is missing'\n\n      get '/nested_triple', admin: { super: { first_name: 'Billy' } }\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('{\"error\":\"admin[admin_name] is missing, admin[super][user] is missing, admin[super][user][first_name] is missing, admin[super][user][last_name] is missing\"}')\n\n      get '/nested_triple', super: { user: { first_name: 'Billy', last_name: 'Bob' } }\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to include '{\"error\":\"admin is missing'\n\n      get '/nested_triple', admin: { super: { user: { first_name: 'Billy' } } }\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('{\"error\":\"admin[admin_name] is missing, admin[super][user][last_name] is missing\"}')\n\n      get '/nested_triple', admin: { admin_name: 'admin', super: { user: { first_name: 'Billy' } } }\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('{\"error\":\"admin[super][user][last_name] is missing\"}')\n\n      get '/nested_triple', admin: { admin_name: 'admin', super: { user: { first_name: 'Billy', last_name: 'Bob' } } }\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('Nested triple'.to_json)\n    end\n  end\n\n  context 'with reused parameter documentation once required and once optional' do\n    before do\n      docs = { name: { type: String, desc: 'some name' } }\n\n      subject.params do\n        requires :all, using: docs\n      end\n      subject.get '/required' do\n        'Hello required'\n      end\n\n      subject.params do\n        optional :all, using: docs\n      end\n      subject.get '/optional' do\n        'Hello optional'\n      end\n    end\n\n    it 'works with required' do\n      get '/required'\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('{\"error\":\"name is missing\"}')\n\n      get '/required', name: 'Bob'\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('Hello required'.to_json)\n    end\n\n    it 'works with optional' do\n      get '/optional'\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('Hello optional'.to_json)\n\n      get '/optional', name: 'Bob'\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('Hello optional'.to_json)\n    end\n  end\n\n  context 'with a custom type' do\n    it 'does not validate their type when it is missing' do\n      custom_type = Class.new do\n        def self.parse(value)\n          return if value.blank?\n\n          new\n        end\n      end\n\n      subject.params do\n        requires :custom, type: custom_type\n      end\n      subject.get '/custom' do\n        'custom'\n      end\n\n      get 'custom'\n\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('{\"error\":\"custom is missing\"}')\n\n      get 'custom', custom: 'filled'\n\n      expect(last_response.status).to eq(200)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/validators/regexp_validator_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Validators::RegexpValidator do\n  describe '#bad encoding' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :name, regexp: { value: /^[a-z]+$/ }\n        end\n        get '/bad_encoding'\n      end\n    end\n\n    context 'when value as bad encoding' do\n      it 'does not raise an error' do\n        expect { get '/bad_encoding', name: \"Hello \\x80\" }.not_to raise_error\n      end\n    end\n  end\n\n  describe '/' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :name, regexp: /^[a-z]+$/\n        end\n        get do\n        end\n      end\n    end\n\n    context 'invalid input' do\n      it 'refuses inapppopriate' do\n        get '/', name: 'invalid name'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('{\"error\":\"name is invalid\"}')\n      end\n\n      it 'refuses empty' do\n        get '/', name: ''\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('{\"error\":\"name is invalid\"}')\n      end\n    end\n\n    it 'accepts nil' do\n      get '/', name: nil\n      expect(last_response.status).to eq(200)\n    end\n\n    it 'accepts valid input' do\n      get '/', name: 'bob'\n      expect(last_response.status).to eq(200)\n    end\n  end\n\n  describe '/regexp_with_array' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :names, type: Array[String], regexp: /^[a-z]+$/\n        end\n        get 'regexp_with_array' do\n        end\n      end\n    end\n\n    it 'refuses inapppopriate items' do\n      get '/regexp_with_array', names: ['invalid name', 'abc']\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('{\"error\":\"names is invalid\"}')\n    end\n\n    it 'refuses empty items' do\n      get '/regexp_with_array', names: ['', 'abc']\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('{\"error\":\"names is invalid\"}')\n    end\n\n    it 'refuses nil items' do\n      get '/regexp_with_array', names: [nil, 'abc']\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('{\"error\":\"names is invalid\"}')\n    end\n\n    it 'accepts valid items' do\n      get '/regexp_with_array', names: ['bob']\n      expect(last_response.status).to eq(200)\n    end\n\n    it 'accepts nil instead of array' do\n      get '/regexp_with_array', names: nil\n      expect(last_response.status).to eq(200)\n    end\n  end\n\n  describe '/nested_regexp_with_array' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :people, type: Hash do\n            requires :names, type: Array[String], regexp: /^[a-z]+$/\n          end\n        end\n        get 'nested_regexp_with_array' do\n        end\n      end\n    end\n\n    it 'refuses inapppopriate' do\n      get '/nested_regexp_with_array', people: 'invalid name'\n      expect(last_response.status).to eq(400)\n      expect(last_response.body).to eq('{\"error\":\"people is invalid, people[names] is missing, people[names] is invalid\"}')\n    end\n  end\n\n  describe '/custom_message' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        resources :custom_message do\n          params do\n            requires :name, regexp: { value: /^[a-z]+$/, message: 'format is invalid' }\n          end\n          get do\n          end\n\n          params do\n            requires :names, type: { value: Array[String], message: 'can\\'t be nil' }, regexp: { value: /^[a-z]+$/, message: 'format is invalid' }\n          end\n          get 'regexp_with_array' do\n          end\n        end\n      end\n    end\n\n    context 'with invalid input' do\n      it 'refuses inapppopriate' do\n        get '/custom_message', name: 'invalid name'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('{\"error\":\"name format is invalid\"}')\n      end\n\n      it 'refuses empty' do\n        get '/custom_message', name: ''\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('{\"error\":\"name format is invalid\"}')\n      end\n    end\n\n    it 'accepts nil' do\n      get '/custom_message', name: nil\n      expect(last_response.status).to eq(200)\n    end\n\n    it 'accepts valid input' do\n      get '/custom_message', name: 'bob'\n      expect(last_response.status).to eq(200)\n    end\n\n    context 'regexp with array' do\n      it 'refuses inapppopriate items' do\n        get '/custom_message/regexp_with_array', names: ['invalid name', 'abc']\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('{\"error\":\"names format is invalid\"}')\n      end\n\n      it 'refuses empty items' do\n        get '/custom_message/regexp_with_array', names: ['', 'abc']\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('{\"error\":\"names format is invalid\"}')\n      end\n\n      it 'refuses nil items' do\n        get '/custom_message/regexp_with_array', names: [nil, 'abc']\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('{\"error\":\"names can\\'t be nil\"}')\n      end\n\n      it 'accepts valid items' do\n        get '/custom_message/regexp_with_array', names: ['bob']\n        expect(last_response.status).to eq(200)\n      end\n\n      it 'accepts nil instead of array' do\n        get '/custom_message/regexp_with_array', names: nil\n        expect(last_response.status).to eq(200)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/validators/same_as_validator_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Validators::SameAsValidator do\n  describe '/' do\n    let(:app) do\n      Class.new(Grape::API) do\n        params do\n          requires :password\n          requires :password_confirmation, same_as: :password\n        end\n        post do\n        end\n      end\n    end\n\n    context 'is the same' do\n      it do\n        post '/', password: '987654', password_confirmation: '987654'\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq('')\n      end\n    end\n\n    context 'is not the same' do\n      it do\n        post '/', password: '123456', password_confirmation: 'whatever'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('password_confirmation is not the same as password')\n      end\n    end\n  end\n\n  describe '/custom-message' do\n    let(:app) do\n      Class.new(Grape::API) do\n        params do\n          requires :password\n          requires :password_confirmation, same_as: { value: :password, message: 'not match' }\n        end\n        post '/custom-message' do\n        end\n      end\n    end\n\n    context 'is the same' do\n      it do\n        post '/custom-message', password: '987654', password_confirmation: '987654'\n        expect(last_response.status).to eq(201)\n        expect(last_response.body).to eq('')\n      end\n    end\n\n    context 'is not the same' do\n      it do\n        post '/custom-message', password: '123456', password_confirmation: 'whatever'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('password_confirmation not match')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations/validators/values_validator_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations::Validators::ValuesValidator do\n  let(:values_model) do\n    Class.new do\n      class << self\n        def values\n          @values ||= []\n          [default_values + @values].flatten.uniq\n        end\n\n        def add_value(value)\n          @values ||= []\n          @values << value\n        end\n\n        def excepts\n          @excepts ||= []\n          [default_excepts + @excepts].flatten.uniq\n        end\n\n        def add_except(except)\n          @excepts ||= []\n          @excepts << except\n        end\n\n        def include?(value)\n          values.include?(value)\n        end\n\n        def even?(value)\n          value.to_i.even?\n        end\n\n        private\n\n        def default_values\n          %w[valid-type1 valid-type2 valid-type3].freeze\n        end\n\n        def default_excepts\n          %w[invalid-type1 invalid-type2 invalid-type3].freeze\n        end\n      end\n    end\n  end\n\n  before do\n    stub_const('ValuesModel', values_model)\n  end\n\n  describe '#bad encoding' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :type, type: String, values: %w[a b]\n        end\n        get '/bad_encoding'\n      end\n    end\n\n    context 'when value as bad encoding' do\n      it 'does not raise an error' do\n        expect { get '/bad_encoding', type: \"Hello \\x80\" }.not_to raise_error\n      end\n    end\n  end\n\n  describe '/custom_message' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        resources :custom_message do\n          params do\n            requires :type, values: { value: ValuesModel.values, message: 'value does not include in values' }\n          end\n          get '/' do\n            { type: params[:type] }\n          end\n\n          params do\n            optional :type, values: { value: -> { ValuesModel.values }, message: 'value does not include in values' }, default: 'valid-type2'\n          end\n          get '/lambda' do\n            { type: params[:type] }\n          end\n        end\n      end\n    end\n\n    it 'allows a valid value for a parameter' do\n      get('/custom_message', type: 'valid-type1')\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ type: 'valid-type1' }.to_json)\n    end\n\n    it 'does not allow an invalid value for a parameter' do\n      get('/custom_message', type: 'invalid-type')\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq({ error: 'type value does not include in values' }.to_json)\n    end\n\n    it 'validates against values in a proc' do\n      ValuesModel.add_value('valid-type4')\n\n      get('/custom_message/lambda', type: 'valid-type4')\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ type: 'valid-type4' }.to_json)\n    end\n\n    it 'does not allow an invalid value for a parameter using lambda' do\n      get('/custom_message/lambda', type: 'invalid-type')\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq({ error: 'type value does not include in values' }.to_json)\n    end\n  end\n\n  describe '/' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :type, values: ValuesModel.values\n        end\n        get '/' do\n          { type: params[:type] }\n        end\n      end\n    end\n\n    it 'allows a valid value for a parameter' do\n      get('/', type: 'valid-type1')\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ type: 'valid-type1' }.to_json)\n    end\n\n    it 'does not allow an invalid value for a parameter' do\n      get('/', type: 'invalid-type')\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)\n    end\n\n    context 'nil value for a parameter' do\n      it 'does not allow for root params scope' do\n        get('/', type: nil)\n        expect(last_response.status).to eq 400\n        expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)\n      end\n    end\n\n    it 'does not validate updated values without proc' do\n      app # Instantiate with the existing values.\n      ValuesModel.add_value('valid-type4')\n      get('/', type: 'valid-type4')\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)\n    end\n  end\n\n  describe '/empty' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :type, values: []\n        end\n        get '/empty'\n      end\n    end\n\n    it 'rejects all values if values is an empty array' do\n      get('/empty', type: 'invalid-type')\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)\n    end\n  end\n\n  describe '/optional_with_required_values' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          optional :optional, type: Array do\n            requires :type, values: %w[a b]\n          end\n        end\n        get '/optional_with_required_values'\n      end\n    end\n\n    it 'allows nil value for a required param in child scope' do\n      get('/optional_with_required_values')\n      expect(last_response.status).to eq 200\n    end\n  end\n\n  describe '/optional_with_array_of_string_values' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          optional :optional, type: Array[String], values: %w[a b c]\n        end\n        put '/optional_with_array_of_string_values'\n      end\n    end\n\n    it 'accepts nil for an optional param with a list of values' do\n      put('/optional_with_array_of_string_values', optional: nil)\n      expect(last_response.status).to eq 200\n    end\n  end\n\n  describe '/default/valid' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          optional :type, values: ValuesModel.values, default: 'valid-type2'\n        end\n        get '/default/valid' do\n          { type: params[:type] }\n        end\n      end\n    end\n\n    it 'allows a valid default value' do\n      get('/default/valid')\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ type: 'valid-type2' }.to_json)\n    end\n  end\n\n  describe '/default/hash/valid' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          optional :type, values: { value: ValuesModel.values }, default: 'valid-type2'\n        end\n        get '/default/hash/valid' do\n          { type: params[:type] }\n        end\n      end\n    end\n\n    it 'allows a valid default value' do\n      get('/default/hash/valid')\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ type: 'valid-type2' }.to_json)\n    end\n  end\n\n  describe '/lambda' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          optional :type, values: -> { ValuesModel.values }, default: 'valid-type2'\n        end\n        get '/lambda' do\n          { type: params[:type] }\n        end\n      end\n    end\n\n    it 'allows a proc for values' do\n      get('/lambda', type: 'valid-type1')\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ type: 'valid-type1' }.to_json)\n    end\n\n    it 'validates against values in a proc' do\n      ValuesModel.add_value('valid-type4')\n\n      get('/lambda', type: 'valid-type4')\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ type: 'valid-type4' }.to_json)\n    end\n\n    it 'does not allow an invalid value for a parameter using lambda' do\n      get('/lambda', type: 'invalid-type')\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)\n    end\n\n    it 'evaluates the proc per-request, not at definition time (e.g. for DB-backed values)' do\n      app # instantiate at definition time, before the new value is added\n      ValuesModel.add_value('valid-type4')\n      get('/lambda', type: 'valid-type4')\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ type: 'valid-type4' }.to_json)\n    end\n  end\n\n  describe '/endless' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          optional :type, type: Integer, values: 1..\n        end\n        get '/endless' do\n          { type: params[:type] }\n        end\n      end\n    end\n\n    it 'validates against values in an endless range' do\n      get('/endless', type: 10)\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ type: 10 }.to_json)\n    end\n\n    it 'does not allow an invalid value for a parameter using an endless range' do\n      get('/endless', type: 0)\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)\n    end\n  end\n\n  describe '/lambda_val' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :type, values: ->(v) { ValuesModel.include? v }\n        end\n        get '/lambda_val' do\n          { type: params[:type] }\n        end\n      end\n    end\n\n    it 'allows value using lambda' do\n      get('/lambda_val', type: 'valid-type1')\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ type: 'valid-type1' }.to_json)\n    end\n\n    it 'does not allow invalid value using lambda' do\n      get('/lambda_val', type: 'invalid-type')\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)\n    end\n  end\n\n  describe '/lambda_int_val' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :number, type: Integer, values: ->(v) { v > 0 }\n        end\n        get '/lambda_int_val' do\n          { number: params[:number] }\n        end\n      end\n    end\n\n    it 'does not allow non-numeric string value for int value using lambda' do\n      get('/lambda_int_val', number: 'foo')\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq({ error: 'number is invalid, number does not have a valid value' }.to_json)\n    end\n\n    it 'does not allow nil for int value using lambda' do\n      get('/lambda_int_val', number: nil)\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq({ error: 'number does not have a valid value' }.to_json)\n    end\n\n    it 'allows numeric string for int value using lambda' do\n      get('/lambda_int_val', number: '3')\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ number: 3 }.to_json)\n    end\n  end\n\n  describe '/empty_lambda' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :type, values: -> { [] }\n        end\n        get '/empty_lambda'\n      end\n    end\n\n    it 'validates against an empty array in a proc' do\n      get('/empty_lambda', type: 'any')\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)\n    end\n  end\n\n  describe '/default_lambda' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          optional :type, values: ValuesModel.values, default: -> { ValuesModel.values.sample }\n        end\n        get '/default_lambda' do\n          { type: params[:type] }\n        end\n      end\n    end\n\n    it 'validates default value from proc' do\n      get('/default_lambda')\n      expect(last_response.status).to eq 200\n    end\n  end\n\n  describe '/default_and_values_lambda' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          optional :type, values: -> { ValuesModel.values }, default: -> { ValuesModel.values.sample }\n        end\n        get '/default_and_values_lambda' do\n          { type: params[:type] }\n        end\n      end\n    end\n\n    it 'validates default value from proc against values in a proc' do\n      get('/default_and_values_lambda')\n      expect(last_response.status).to eq 200\n    end\n  end\n\n  context 'IncompatibleOptionValues' do\n    it 'raises on an invalid default value from proc' do\n      subject = Class.new(Grape::API)\n      expect do\n        subject.params { optional :type, values: %w[valid-type1 valid-type2 valid-type3], default: \"#{ValuesModel.values.sample}_invalid\" }\n      end.to raise_error Grape::Exceptions::IncompatibleOptionValues\n    end\n\n    it 'raises on an invalid default value' do\n      subject = Class.new(Grape::API)\n      expect do\n        subject.params { optional :type, values: %w[valid-type1 valid-type2 valid-type3], default: 'invalid-type' }\n      end.to raise_error Grape::Exceptions::IncompatibleOptionValues\n    end\n\n    it 'raises when type is incompatible with values array' do\n      subject = Class.new(Grape::API)\n      expect do\n        subject.params { optional :type, values: %w[valid-type1 valid-type2 valid-type3], type: Symbol }\n      end.to raise_error Grape::Exceptions::IncompatibleOptionValues\n    end\n\n    it 'raises when values contains a value that is not a kind of the type' do\n      subject = Class.new(Grape::API)\n      expect do\n        subject.params { requires :type, values: [10.5, 11], type: Integer }\n      end.to raise_error Grape::Exceptions::IncompatibleOptionValues\n    end\n\n    it 'raises when except contains a value that is not a kind of the type' do\n      subject = Class.new(Grape::API)\n      expect do\n        subject.params { requires :type, except_values: [10.5, 11], type: Integer }\n      end.to raise_error Grape::Exceptions::IncompatibleOptionValues\n    end\n  end\n\n  describe '/values/optional_boolean' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          optional :type, type: Grape::API::Boolean, desc: 'A boolean', values: [true]\n        end\n        get '/values/optional_boolean' do\n          { type: params[:type] }\n        end\n      end\n    end\n\n    it 'allows a value from the list' do\n      get('/values/optional_boolean', type: true)\n\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ type: true }.to_json)\n    end\n\n    it 'rejects a value which is not in the list' do\n      get('/values/optional_boolean', type: false)\n\n      expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)\n    end\n  end\n\n  describe '/values/coercion' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :type, type: Integer, desc: 'An integer', values: [10, 11], default: 10\n        end\n        get '/values/coercion' do\n          { type: params[:type] }\n        end\n      end\n    end\n\n    it 'allows values to be a kind of the coerced type not just an instance of it' do\n      get('/values/coercion', type: 10)\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ type: 10 }.to_json)\n    end\n  end\n\n  describe '/values/array_coercion' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :type, type: Array[Integer], desc: 'An integer', values: [10, 11], default: 10\n        end\n        get '/values/array_coercion' do\n          { type: params[:type] }\n        end\n      end\n    end\n\n    it 'allows values to be a kind of the coerced type in an array' do\n      get('/values/array_coercion', type: [10])\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ type: [10] }.to_json)\n    end\n  end\n\n  describe '/allow_blank' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          optional :name, type: String, values: %w[a b], allow_blank: true\n        end\n        get '/allow_blank'\n      end\n    end\n\n    it 'allows a blank value when the allow_blank option is true' do\n      get 'allow_blank', name: nil\n      expect(last_response.status).to eq(200)\n\n      get 'allow_blank', name: ''\n      expect(last_response.status).to eq(200)\n    end\n  end\n\n  context 'with a lambda values' do\n    subject do\n      Class.new(Grape::API) do\n        params do\n          optional :type, type: String, values: -> { [SecureRandom.uuid] }, default: -> { SecureRandom.uuid }\n        end\n        get '/random_values'\n      end\n    end\n\n    def app\n      subject\n    end\n\n    before do\n      expect(SecureRandom).to receive(:uuid).and_return('foo').once\n    end\n\n    it 'only evaluates values dynamically with each request' do\n      get '/random_values', type: 'foo'\n      expect(last_response.status).to eq 200\n    end\n\n    it 'chooses default' do\n      get '/random_values'\n      expect(last_response.status).to eq 200\n    end\n  end\n\n  context 'with a range of values' do\n    subject(:app) do\n      Class.new(Grape::API) do\n        params do\n          optional :value, type: Float, values: 0.0..10.0\n        end\n        get '/value' do\n          { value: params[:value] }.to_json\n        end\n\n        params do\n          optional :values, type: Array[Float], values: 0.0..10.0\n        end\n        get '/values' do\n          { values: params[:values] }.to_json\n        end\n      end\n    end\n\n    it 'allows a single value inside of the range' do\n      get('/value', value: 5.2)\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ value: 5.2 }.to_json)\n    end\n\n    it 'allows an array of values inside of the range' do\n      get('/values', values: [8.6, 7.5, 3, 0.9])\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ values: [8.6, 7.5, 3.0, 0.9] }.to_json)\n    end\n\n    it 'rejects a single value outside the range' do\n      get('/value', value: 'a')\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq('value is invalid, value does not have a valid value')\n    end\n\n    it 'rejects an array of values if any of them are outside the range' do\n      get('/values', values: [8.6, 75, 3, 0.9])\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq('values does not have a valid value')\n    end\n  end\n\n  describe '/mixed/value/except' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :type, type: Integer, values: 1..5, except_values: [3]\n        end\n        get '/mixed/value/except' do\n          { type: params[:type] }\n        end\n      end\n    end\n\n    it 'allows value, but not in except' do\n      get '/mixed/value/except', type: 2\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ type: 2 }.to_json)\n    end\n\n    it 'rejects except' do\n      get '/mixed/value/except', type: 3\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq({ error: 'type has a value not allowed' }.to_json)\n    end\n\n    it 'rejects outside except and outside value' do\n      get '/mixed/value/except', type: 10\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)\n    end\n  end\n\n  describe '/proc' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :type, values: ->(v) { ValuesModel.include? v }\n        end\n        get '/proc' do\n          { type: params[:type] }\n        end\n      end\n    end\n\n    it 'accepts a single valid value' do\n      get '/proc', type: 'valid-type1'\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ type: 'valid-type1' }.to_json)\n    end\n\n    it 'accepts multiple valid values' do\n      get '/proc', type: %w[valid-type1 valid-type3]\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ type: %w[valid-type1 valid-type3] }.to_json)\n    end\n\n    it 'rejects a single invalid value' do\n      get '/proc', type: 'invalid-type1'\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)\n    end\n\n    it 'rejects an invalid value among valid ones' do\n      get '/proc', type: %w[valid-type1 invalid-type1 valid-type3]\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json)\n    end\n  end\n\n  describe '/proc/message' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :type, values: { value: ->(v) { ValuesModel.include? v }, message: 'failed check' }\n        end\n        get '/proc/message'\n      end\n    end\n\n    it 'uses supplied message' do\n      get '/proc/message', type: 'invalid-type1'\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq({ error: 'type failed check' }.to_json)\n    end\n  end\n\n  describe '/proc/custom_message' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :number, values: { value: ->(v) { ValuesModel.even? v }, message: 'must be even' }\n        end\n        get '/proc/custom_message' do\n          { message: 'success' }\n        end\n      end\n    end\n\n    it 'accepts a valid value' do\n      get '/proc/custom_message', number: 4\n      expect(last_response.status).to eq 200\n      expect(last_response.body).to eq({ message: 'success' }.to_json)\n    end\n\n    it 'rejects an invalid value' do\n      get '/proc/custom_message', number: 5\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq({ error: 'number must be even' }.to_json)\n    end\n  end\n\n  describe '/proc/arity2' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          requires :input_one, :input_two, values: { value: ->(v1, v2) { v1 + v2 > 10 } }\n        end\n        get '/proc/arity2'\n      end\n    end\n\n    it 'returns an error status code' do\n      get '/proc/arity2', input_one: 2, input_two: 3\n      expect(last_response.status).to eq 400\n    end\n  end\n\n  describe '/values_wrapped_by_with_block' do\n    let(:app) do\n      Class.new(Grape::API) do\n        default_format :json\n\n        params do\n          with(type: String) do\n            requires :type, values: ValuesModel.values\n          end\n        end\n        get 'values_wrapped_by_with_block'\n      end\n    end\n\n    it 'rejects an invalid value' do\n      get 'values_wrapped_by_with_block'\n\n      expect(last_response.status).to eq 400\n      expect(last_response.body).to eq({ error: 'type is missing, type does not have a valid value' }.to_json)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/grape/validations_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Validations do\n  subject { Class.new(Grape::API) }\n\n  let(:app) { subject }\n\n  describe 'params' do\n    context 'optional' do\n      before do\n        subject.params do\n          optional :a_number, regexp: /^[0-9]+$/\n          optional :attachment, type: File\n        end\n        subject.get '/optional' do\n          'optional works!'\n        end\n      end\n\n      it 'validates when params is present' do\n        get '/optional', a_number: 'string'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('a_number is invalid')\n\n        get '/optional', a_number: 45\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('optional works!')\n      end\n\n      it \"doesn't validate when param not present\" do\n        get '/optional', a_number: nil, attachment: nil\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('optional works!')\n      end\n\n      it 'adds to declared parameters' do\n        subject.params do\n          optional :some_param\n        end\n        subject.format :json\n        subject.get('/') do\n          declared(params)\n        end\n        env = Rack::MockRequest.env_for('/', method: Rack::GET)\n        response = Rack::MockResponse[*subject.call(env)]\n        expect(JSON.parse(response.body)).to eq('some_param' => nil)\n      end\n    end\n\n    context 'optional using Grape::Entity documentation' do\n      def define_optional_using\n        documentation = { field_a: { type: String }, field_b: { type: String } }\n        subject.params do\n          optional :all, using: documentation\n        end\n      end\n      before do\n        define_optional_using\n        subject.get '/optional' do\n          'optional with using works'\n        end\n      end\n\n      it 'adds entity documentation to declared params' do\n        define_optional_using\n        subject.format :json\n        subject.get('/') do\n          declared(params)\n        end\n        env = Rack::MockRequest.env_for('/', method: Rack::GET, params: { field_a: 'field_a', field_b: 'field_b' })\n        response = Rack::MockResponse[*subject.call(env)]\n        expect(JSON.parse(response.body)).to eq('field_a' => 'field_a', 'field_b' => 'field_b')\n      end\n\n      it 'works when field_a and field_b are not present' do\n        get '/optional'\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('optional with using works')\n      end\n\n      it 'works when field_a is present' do\n        get '/optional', field_a: 'woof'\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('optional with using works')\n      end\n\n      it 'works when field_b is present' do\n        get '/optional', field_b: 'woof'\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('optional with using works')\n      end\n    end\n\n    context 'required' do\n      before do\n        subject.params do\n          requires :key, type: String\n        end\n        subject.get('/required') { 'required works' }\n        subject.put('/required') { { key: params[:key] }.to_json }\n      end\n\n      it 'errors when param not present' do\n        get '/required'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('key is missing')\n      end\n\n      it \"doesn't throw a missing param when param is present\" do\n        get '/required', key: 'cool'\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('required works')\n      end\n\n      it 'adds to declared parameters' do\n        subject.params do\n          requires :some_param\n        end\n        subject.format :json\n        subject.get('/') do\n          declared(params)\n        end\n        env = Rack::MockRequest.env_for('/', method: Rack::GET, params: { some_param: 'some_param' })\n        response = Rack::MockResponse[*subject.call(env)]\n        expect(JSON.parse(response.body)).to eq('some_param' => 'some_param')\n      end\n\n      it 'works when required field is present but nil' do\n        put '/required', { key: nil }.to_json, 'CONTENT_TYPE' => 'application/json'\n        expect(last_response.status).to eq(200)\n        expect(JSON.parse(last_response.body)).to eq('key' => nil)\n      end\n    end\n\n    context 'requires with nested params' do\n      before do\n        subject.params do\n          requires :first_level, type: Hash do\n            optional :second_level, type: Array do\n              requires :value, type: Integer\n              optional :name, type: String\n              optional :third_level, type: Array do\n                requires :value, type: Integer\n                optional :name, type: String\n                optional :fourth_level, type: Array do\n                  requires :value, type: Integer\n                  optional :name, type: String\n                end\n              end\n            end\n          end\n        end\n        subject.put('/required') { 'required works' }\n      end\n\n      let(:request_params) do\n        {\n          first_level: {\n            second_level: [\n              { value: 1, name: 'Lisa' },\n              {\n                value: 2,\n                name: 'James',\n                third_level: [\n                  { value: 'three', name: 'Sophie' },\n                  {\n                    value: 4,\n                    name: 'Jenny',\n                    fourth_level: [\n                      { name: 'Samuel' }, { value: 6, name: 'Jane' }\n                    ]\n                  }\n                ]\n              }\n            ]\n          }\n        }\n      end\n\n      it 'validates correctly in deep nested params' do\n        put '/required', request_params.to_json, 'CONTENT_TYPE' => 'application/json'\n\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq(\n          'first_level[second_level][1][third_level][0][value] is invalid, ' \\\n          'first_level[second_level][1][third_level][1][fourth_level][0][value] is missing'\n        )\n      end\n    end\n\n    context 'requires :all using Grape::Entity documentation' do\n      def define_requires_all\n        documentation = {\n          required_field: { type: String, required: true, param_type: 'query' },\n          optional_field: { type: String },\n          optional_array_field: { type: Array[String], is_array: true }\n        }\n        subject.params do\n          requires :all, except: %i[optional_field optional_array_field], using: documentation\n        end\n      end\n      before do\n        define_requires_all\n        subject.get '/required' do\n          'required works'\n        end\n      end\n\n      it 'adds entity documentation to declared params' do\n        define_requires_all\n        subject.format :json\n        subject.get('/') do\n          declared(params)\n        end\n        env = Rack::MockRequest.env_for('/', method: Rack::GET, params: { required_field: 'required_field', optional_field: 'optional_field', optional_array_field: ['optional_array_field'] })\n        response = Rack::MockResponse[*subject.call(env)]\n        expect(JSON.parse(response.body)).to eq('required_field' => 'required_field', 'optional_field' => 'optional_field', 'optional_array_field' => ['optional_array_field'])\n      end\n\n      it 'errors when required_field is not present' do\n        get '/required'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('required_field is missing')\n      end\n\n      it 'works when required_field is present' do\n        get '/required', required_field: 'woof'\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('required works')\n      end\n    end\n\n    context 'requires :none using Grape::Entity documentation' do\n      def define_requires_none\n        documentation = {\n          required_field: { type: String, example: 'Foo' },\n          optional_field: { type: Integer, format: 'int64' }\n        }\n        subject.params do\n          requires :none, except: :required_field, using: documentation\n        end\n      end\n      before do\n        define_requires_none\n        subject.get '/required' do\n          'required works'\n        end\n      end\n\n      it 'adds entity documentation to declared params' do\n        define_requires_none\n        subject.format :json\n        subject.get('/') do\n          declared(params)\n        end\n        env = Rack::MockRequest.env_for('/', method: Rack::GET, params: { required_field: 'required_field', optional_field: 1 })\n        response = Rack::MockResponse[*subject.call(env)]\n        expect(JSON.parse(response.body)).to eq('required_field' => 'required_field', 'optional_field' => 1)\n      end\n\n      it 'errors when required_field is not present' do\n        get '/required'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('required_field is missing')\n      end\n\n      it 'works when required_field is present' do\n        get '/required', required_field: 'woof'\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('required works')\n      end\n    end\n\n    context 'requires :all or :none but except a non-existent field using Grape::Entity documentation' do\n      context 'requires :all' do\n        def define_requires_all\n          documentation = {\n            required_field: { type: String },\n            optional_field: { type: String }\n          }\n          subject.params do\n            requires :all, except: :non_existent_field, using: documentation\n          end\n        end\n\n        it 'adds only the entity documentation to declared params, nothing more' do\n          define_requires_all\n          subject.format :json\n          subject.get('/') do\n            declared(params)\n          end\n          env = Rack::MockRequest.env_for('/', method: Rack::GET, params: { required_field: 'required_field', optional_field: 'optional_field' })\n          response = Rack::MockResponse[*subject.call(env)]\n          expect(JSON.parse(response.body)).to eq('required_field' => 'required_field', 'optional_field' => 'optional_field')\n        end\n      end\n\n      context 'requires :none' do\n        def define_requires_none\n          documentation = {\n            required_field: { type: String },\n            optional_field: { type: String }\n          }\n          subject.params do\n            requires :none, except: :non_existent_field, using: documentation\n          end\n        end\n\n        it 'adds only the entity documentation to declared params, nothing more' do\n          expect { define_requires_none }.to raise_error(ArgumentError)\n        end\n      end\n    end\n\n    context 'required with an Array block' do\n      before do\n        subject.params do\n          requires :items, type: Array do\n            requires :key\n          end\n        end\n        subject.get('/required') { 'required works' }\n        subject.put('/required') { { items: params[:items] }.to_json }\n      end\n\n      it 'errors when param not present' do\n        get '/required'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('items is missing')\n      end\n\n      it 'errors when param is not an Array' do\n        get '/required', items: 'hello'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('items is invalid')\n\n        get '/required', items: { key: 'foo' }\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('items is invalid')\n      end\n\n      it \"doesn't throw a missing param when param is present\" do\n        get '/required', items: [{ key: 'hello' }, { key: 'world' }]\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('required works')\n      end\n\n      it \"doesn't throw a missing param when param is present but empty\" do\n        put '/required', { items: [] }.to_json, 'CONTENT_TYPE' => 'application/json'\n        expect(last_response.status).to eq(200)\n        expect(JSON.parse(last_response.body)).to eq('items' => [])\n      end\n\n      it 'adds to declared parameters' do\n        subject.format :json\n        subject.params do\n          requires :items, type: Array do\n            requires :key\n          end\n        end\n        subject.get('/') do\n          declared(params)\n        end\n        env = Rack::MockRequest.env_for('/', method: Rack::GET, params: { items: [key: 'my_key'] })\n        response = Rack::MockResponse[*subject.call(env)]\n        expect(JSON.parse(response.body)).to eq('items' => ['key' => 'my_key'])\n      end\n    end\n\n    # Ensure there is no leakage between declared Array types and\n    # subsequent Hash types\n    context 'required with an Array and a Hash block' do\n      before do\n        subject.params do\n          requires :cats, type: Array[String], default: []\n          requires :items, type: Hash do\n            requires :key\n          end\n        end\n        subject.get '/required' do\n          'required works'\n        end\n      end\n\n      it 'does not output index [0] for Hash types' do\n        get '/required', cats: ['Garfield'], items: { foo: 'bar' }\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('items[key] is missing')\n      end\n    end\n\n    context 'required with a Hash block' do\n      before do\n        subject.params do\n          requires :items, type: Hash do\n            requires :key\n          end\n        end\n        subject.get '/required' do\n          'required works'\n        end\n      end\n\n      it 'errors when param not present' do\n        get '/required'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('items is missing, items[key] is missing')\n      end\n\n      it 'errors when nested param not present' do\n        get '/required', items: { foo: 'bar' }\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('items[key] is missing')\n      end\n\n      it 'errors when param is not a Hash' do\n        get '/required', items: 'hello'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('items is invalid, items[key] is missing')\n\n        get '/required', items: [{ key: 'foo' }]\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('items is invalid')\n      end\n\n      it \"doesn't throw a missing param when param is present\" do\n        get '/required', items: { key: 'hello' }\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('required works')\n      end\n\n      it 'adds to declared parameters' do\n        subject.params do\n          requires :items, type: Array do\n            requires :key\n          end\n        end\n        subject.format :json\n        subject.get('/') do\n          declared(params)\n        end\n        env = Rack::MockRequest.env_for('/', method: Rack::GET, params: { items: [key: :my_key] })\n        response = Rack::MockResponse[*subject.call(env)]\n        expect(JSON.parse(response.body)).to eq('items' => ['key' => 'my_key'])\n      end\n    end\n\n    context 'hash with a required param with validation' do\n      before do\n        subject.params do\n          requires :items, type: Hash do\n            requires :key, type: String, values: %w[a b]\n          end\n        end\n        subject.get '/required' do\n          'required works'\n        end\n      end\n\n      it 'errors when param is not a Hash' do\n        get '/required', items: 'not a hash'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('items is invalid, items[key] is missing, items[key] is invalid')\n\n        get '/required', items: [{ key: 'hash in array' }]\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('items is invalid, items[key] does not have a valid value')\n      end\n\n      it 'works when all params match' do\n        get '/required', items: { key: 'a' }\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('required works')\n      end\n    end\n\n    context 'group' do\n      before do\n        subject.params do\n          group :items, type: Array do\n            requires :key\n          end\n        end\n        subject.get '/required' do\n          'required works'\n        end\n      end\n\n      it 'errors when param not present' do\n        get '/required'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('items is missing')\n      end\n\n      it \"doesn't throw a missing param when param is present\" do\n        get '/required', items: [key: 'hello']\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('required works')\n      end\n\n      it 'adds to declared parameters' do\n        subject.format :json\n        subject.params do\n          group :items, type: Array do\n            requires :key\n          end\n        end\n        subject.get('/') do\n          declared(params)\n        end\n        env = Rack::MockRequest.env_for('/', method: Rack::GET, params: { items: [key: :my_key] })\n        response = Rack::MockResponse[*subject.call(env)]\n        expect(JSON.parse(response.body)).to eq('items' => ['key' => 'my_key'])\n      end\n    end\n\n    context 'group params with nested params which has a type' do\n      let(:invalid_items) { { items: '' } }\n\n      before do\n        subject.params do\n          optional :items, type: Array do\n            optional :key1, type: String\n            optional :key2, type: String\n          end\n        end\n        subject.post '/group_with_nested' do\n          'group with nested works'\n        end\n      end\n\n      it 'errors when group param is invalid' do\n        post '/group_with_nested', items: invalid_items\n        expect(last_response.status).to eq(400)\n      end\n    end\n\n    context 'custom validator for a Hash' do\n      let(:date_range_validator) do\n        Class.new(Grape::Validations::Validators::Base) do\n          def validate_param!(attr_name, params)\n            return if params[attr_name][:from] <= params[attr_name][:to]\n\n            raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: \"'from' must be lower or equal to 'to'\")\n          end\n        end\n      end\n\n      before do\n        stub_const('DateRangeValidator', date_range_validator)\n        described_class.register(DateRangeValidator)\n        subject.params do\n          optional :date_range, date_range: true, type: Hash do\n            requires :from, type: Integer\n            requires :to, type: Integer\n          end\n        end\n        subject.get('/optional') do\n          'optional works'\n        end\n        subject.params do\n          requires :date_range, date_range: true, type: Hash do\n            requires :from, type: Integer\n            requires :to, type: Integer\n          end\n        end\n        subject.get('/required') do\n          'required works'\n        end\n      end\n\n      after do\n        described_class.deregister(:date_range)\n      end\n\n      context 'which is optional' do\n        it \"doesn't throw an error if the validation passes\" do\n          get '/optional', date_range: { from: 1, to: 2 }\n          expect(last_response.status).to eq(200)\n        end\n\n        it 'errors if the validation fails' do\n          get '/optional', date_range: { from: 2, to: 1 }\n          expect(last_response.status).to eq(400)\n        end\n      end\n\n      context 'which is required' do\n        it \"doesn't throw an error if the validation passes\" do\n          get '/required', date_range: { from: 1, to: 2 }\n          expect(last_response.status).to eq(200)\n        end\n\n        it 'errors if the validation fails' do\n          get '/required', date_range: { from: 2, to: 1 }\n          expect(last_response.status).to eq(400)\n        end\n      end\n    end\n\n    context 'validation within arrays' do\n      before do\n        subject.params do\n          group :children, type: Array do\n            requires :name\n            group :parents, type: Array do\n              requires :name, allow_blank: false\n            end\n          end\n        end\n        subject.get '/within_array' do\n          'within array works'\n        end\n      end\n\n      it 'can handle new scopes within child elements' do\n        get '/within_array', children: [\n          { name: 'John', parents: [{ name: 'Jane' }, { name: 'Bob' }] },\n          { name: 'Joe', parents: [{ name: 'Josie' }] }\n        ]\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('within array works')\n      end\n\n      it 'errors when a parameter is not present' do\n        get '/within_array', children: [\n          { name: 'Jim', parents: [{ name: 'Joy' }] },\n          { name: 'Job', parents: [{}] }\n        ]\n        # NOTE: with body parameters in json or XML or similar this\n        # should actually fail with: children[parents][name] is missing.\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('children[1][parents] is missing, children[0][parents][1][name] is missing, children[0][parents][1][name] is empty')\n      end\n\n      it 'errors when a parameter is not present in array within array' do\n        get '/within_array', children: [\n          { name: 'Jim', parents: [{ name: 'Joy' }] },\n          { name: 'Job', parents: [{ name: 'Bill' }, { name: '' }] }\n        ]\n\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('children[1][parents][1][name] is empty')\n      end\n\n      it 'handle errors for all array elements' do\n        get '/within_array', children: [\n          { name: 'Jim', parents: [] },\n          { name: 'Job', parents: [] }\n        ]\n\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq(\n          'children[0][parents][0][name] is missing, ' \\\n          'children[1][parents][0][name] is missing'\n        )\n      end\n\n      it 'safely handles empty arrays and blank parameters' do\n        # NOTE: with body parameters in json or XML or similar this\n        # should actually return 200, since an empty array is valid.\n        get '/within_array', children: []\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq(\n          'children[0][name] is missing, ' \\\n          'children[0][parents] is missing, ' \\\n          'children[0][parents] is invalid, ' \\\n          'children[0][parents][0][name] is missing, ' \\\n          'children[0][parents][0][name] is empty'\n        )\n\n        get '/within_array', children: [name: 'Jay']\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('children[0][parents] is missing, children[0][parents][0][name] is missing, children[0][parents][0][name] is empty')\n      end\n\n      it 'errors when param is not an Array' do\n        get '/within_array', children: 'hello'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('children is invalid')\n\n        get '/within_array', children: { name: 'foo' }\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('children is invalid')\n\n        get '/within_array', children: [name: 'Jay', parents: { name: 'Fred' }]\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('children[0][parents] is invalid')\n      end\n    end\n\n    context 'with block param' do\n      before do\n        subject.params do\n          requires :planets, type: Array do\n            requires :name\n          end\n        end\n        subject.get '/req' do\n          'within array works'\n        end\n        subject.put '/req' do\n          ''\n        end\n\n        subject.params do\n          group :stars, type: Array do\n            requires :name\n          end\n        end\n        subject.get '/grp' do\n          'within array works'\n        end\n        subject.put '/grp' do\n          ''\n        end\n\n        subject.params do\n          requires :name\n          optional :moons, type: Array do\n            requires :name\n          end\n        end\n        subject.get '/opt' do\n          'within array works'\n        end\n        subject.put '/opt' do\n          ''\n        end\n      end\n\n      it 'requires defaults to Array type' do\n        get '/req', planets: 'Jupiter, Saturn'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('planets is invalid')\n\n        get '/req', planets: { name: 'Jupiter' }\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('planets is invalid')\n\n        get '/req', planets: [{ name: 'Venus' }, { name: 'Mars' }]\n        expect(last_response.status).to eq(200)\n\n        put_with_json '/req', planets: []\n        expect(last_response.status).to eq(200)\n      end\n\n      it 'optional defaults to Array type' do\n        get '/opt', name: 'Jupiter', moons: 'Europa, Ganymede'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('moons is invalid')\n\n        get '/opt', name: 'Jupiter', moons: { name: 'Ganymede' }\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('moons is invalid')\n\n        get '/opt', name: 'Jupiter', moons: [{ name: 'Io' }, { name: 'Callisto' }]\n        expect(last_response.status).to eq(200)\n\n        put_with_json '/opt', name: 'Venus'\n        expect(last_response.status).to eq(200)\n\n        put_with_json '/opt', name: 'Mercury', moons: []\n        expect(last_response.status).to eq(200)\n      end\n\n      it 'group defaults to Array type' do\n        get '/grp', stars: 'Sun'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('stars is invalid')\n\n        get '/grp', stars: { name: 'Sun' }\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('stars is invalid')\n\n        get '/grp', stars: [{ name: 'Sun' }]\n        expect(last_response.status).to eq(200)\n\n        put_with_json '/grp', stars: []\n        expect(last_response.status).to eq(200)\n      end\n    end\n\n    context 'validation within arrays with JSON' do\n      before do\n        subject.params do\n          group :children, type: Array do\n            requires :name\n            group :parents, type: Array do\n              requires :name\n            end\n          end\n        end\n        subject.put '/within_array' do\n          'within array works'\n        end\n      end\n\n      it 'can handle new scopes within child elements' do\n        put_with_json '/within_array', children: [\n          { name: 'John', parents: [{ name: 'Jane' }, { name: 'Bob' }] },\n          { name: 'Joe', parents: [{ name: 'Josie' }] }\n        ]\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('within array works')\n      end\n\n      it 'errors when a parameter is not present' do\n        put_with_json '/within_array', children: [\n          { name: 'Jim', parents: [{}] },\n          { name: 'Job', parents: [{ name: 'Joy' }] }\n        ]\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('children[0][parents][0][name] is missing')\n      end\n\n      it 'safely handles empty arrays and blank parameters' do\n        put_with_json '/within_array', children: []\n        expect(last_response.status).to eq(200)\n        put_with_json '/within_array', children: [name: 'Jay']\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('children[0][parents] is missing, children[0][parents][0][name] is missing')\n      end\n    end\n\n    context 'optional with an Array block' do\n      before do\n        subject.params do\n          optional :items, type: Array do\n            requires :key\n          end\n        end\n        subject.get '/optional_group' do\n          'optional group works'\n        end\n      end\n\n      it \"doesn't throw a missing param when the group isn't present\" do\n        get '/optional_group'\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('optional group works')\n      end\n\n      it \"doesn't throw a missing param when both group and param are given\" do\n        get '/optional_group', items: [{ key: 'foo' }]\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('optional group works')\n      end\n\n      it 'errors when group is present, but required param is not' do\n        get '/optional_group', items: [{ not_key: 'foo' }]\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('items[0][key] is missing')\n      end\n\n      it \"errors when param is present but isn't an Array\" do\n        get '/optional_group', items: 'hello'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('items is invalid')\n\n        get '/optional_group', items: { key: 'foo' }\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('items is invalid')\n      end\n\n      it 'adds to declared parameters' do\n        subject.format :json\n        subject.params do\n          optional :items, type: Array do\n            requires :key\n          end\n        end\n        subject.get('/') do\n          declared(params)\n        end\n        env = Rack::MockRequest.env_for('/', method: Rack::GET, params: { items: [key: :my_key] })\n        response = Rack::MockResponse[*subject.call(env)]\n        expect(JSON.parse(response.body)).to eq('items' => ['key' => 'my_key'])\n      end\n    end\n\n    context 'nested optional Array blocks' do\n      before do\n        subject.params do\n          optional :items, type: Array do\n            requires :key\n            optional(:optional_subitems, type: Array) { requires :value }\n            requires(:required_subitems, type: Array) { requires :value }\n          end\n        end\n        subject.get('/nested_optional_group') { 'nested optional group works' }\n      end\n\n      it 'does no internal validations if the outer group is blank' do\n        get '/nested_optional_group'\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('nested optional group works')\n      end\n\n      it 'does internal validations if the outer group is present' do\n        get '/nested_optional_group', items: [{ key: 'foo' }]\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('items[0][required_subitems] is missing, items[0][required_subitems][0][value] is missing')\n\n        get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('nested optional group works')\n      end\n\n      it 'handles deep nesting' do\n        get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ not_value: 'baz' }] }]\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('items[0][optional_subitems][0][value] is missing')\n\n        get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ value: 'baz' }] }]\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('nested optional group works')\n      end\n\n      it 'handles validation within arrays' do\n        get '/nested_optional_group', items: [{ key: 'foo' }]\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('items[0][required_subitems] is missing, items[0][required_subitems][0][value] is missing')\n\n        get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('nested optional group works')\n\n        get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ not_value: 'baz' }] }]\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to eq('items[0][optional_subitems][0][value] is missing')\n      end\n\n      it 'adds to declared parameters' do\n        subject.params do\n          optional :items, type: Array do\n            requires :key\n            optional(:optional_subitems, type: Array) { requires :value }\n            requires(:required_subitems, type: Array) { requires :value }\n          end\n        end\n        subject.format :json\n        subject.get('/') do\n          declared(params)\n        end\n        env = Rack::MockRequest.env_for('/', method: Rack::GET, params: { items: [{ key: :my_key, required_subitems: [value: 'my_value'] }] })\n        response = Rack::MockResponse[*subject.call(env)]\n        expect(JSON.parse(response.body)).to eq('items' => [{ 'key' => 'my_key', 'optional_subitems' => [], 'required_subitems' => [{ 'value' => 'my_value' }] }])\n      end\n\n      context <<~DESC do\n        Issue occurs whenever:\n        * param structure with at least three levels\n        * 1st level item is a required Array that has >1 entry with an optional item present and >1 entry with an optional item missing#{'  '}\n        * 2nd level is an optional Array or Hash#{' '}\n        * 3rd level is a required item (can be any type)\n        * additional levels do not effect the issue from occuring\n      DESC\n\n        it 'example based off actual real world use case' do\n          subject.params do\n            requires :orders, type: Array do\n              requires :id, type: Integer\n              optional :drugs, type: Array do\n                requires :batches, type: Array do\n                  requires :batch_no, type: String\n                end\n              end\n            end\n          end\n\n          subject.get '/validate_required_arrays_under_optional_arrays' do\n            'validate_required_arrays_under_optional_arrays works!'\n          end\n\n          data = {\n            orders: [\n              { id: 77, drugs: [{ batches: [{ batch_no: 'A1234567' }] }] },\n              { id: 70 }\n            ]\n          }\n\n          get '/validate_required_arrays_under_optional_arrays', data\n          expect(last_response.body).to eq('validate_required_arrays_under_optional_arrays works!')\n          expect(last_response.status).to eq(200)\n        end\n\n        it 'simplest example using Array -> Array -> Hash -> String' do\n          subject.params do\n            requires :orders, type: Array do\n              requires :id, type: Integer\n              optional :drugs, type: Array do\n                requires :batch_no, type: String\n              end\n            end\n          end\n\n          subject.get '/validate_required_arrays_under_optional_arrays' do\n            'validate_required_arrays_under_optional_arrays works!'\n          end\n\n          data = {\n            orders: [\n              { id: 77, drugs: [{ batch_no: 'A1234567' }] },\n              { id: 70 }\n            ]\n          }\n\n          get '/validate_required_arrays_under_optional_arrays', data\n          expect(last_response.body).to eq('validate_required_arrays_under_optional_arrays works!')\n          expect(last_response.status).to eq(200)\n        end\n\n        it 'simplest example using Array -> Hash -> String' do\n          subject.params do\n            requires :orders, type: Array do\n              requires :id, type: Integer\n              optional :drugs, type: Hash do\n                requires :batch_no, type: String\n              end\n            end\n          end\n\n          subject.get '/validate_required_arrays_under_optional_arrays' do\n            'validate_required_arrays_under_optional_arrays works!'\n          end\n\n          data = {\n            orders: [\n              { id: 77, drugs: { batch_no: 'A1234567' } },\n              { id: 70 }\n            ]\n          }\n\n          get '/validate_required_arrays_under_optional_arrays', data\n          expect(last_response.body).to eq('validate_required_arrays_under_optional_arrays works!')\n          expect(last_response.status).to eq(200)\n        end\n\n        it 'correctly indexes invalida data' do\n          subject.params do\n            requires :orders, type: Array do\n              requires :id, type: Integer\n              optional :drugs, type: Array do\n                requires :batch_no, type: String\n                requires :quantity, type: Integer\n              end\n            end\n          end\n\n          subject.get '/correctly_indexes' do\n            'correctly_indexes works!'\n          end\n\n          data = {\n            orders: [\n              { id: 70 },\n              { id: 77, drugs: [{ batch_no: 'A1234567', quantity: 12 }, { batch_no: 'B222222' }] }\n            ]\n          }\n\n          get '/correctly_indexes', data\n          expect(last_response.body).to eq('orders[1][drugs][1][quantity] is missing')\n          expect(last_response.status).to eq(400)\n        end\n\n        context 'multiple levels of optional and requires settings' do\n          before do\n            subject.params do\n              requires :top, type: Array do\n                requires :top_id, type: Integer, allow_blank: false\n                optional :middle_1, type: Array do\n                  requires :middle_1_id, type: Integer, allow_blank: false\n                  optional :middle_2, type: Array do\n                    requires :middle_2_id, type: String, allow_blank: false\n                    optional :bottom, type: Array do\n                      requires :bottom_id, type: Integer, allow_blank: false\n                    end\n                  end\n                end\n              end\n            end\n\n            subject.get '/multi_level' do\n              'multi_level works!'\n            end\n          end\n\n          it 'with valid data' do\n            data = {\n              top: [\n                { top_id: 1, middle_1: [\n                  { middle_1_id: 11 }, { middle_1_id: 12, middle_2: [\n                    { middle_2_id: 121 }, { middle_2_id: 122, bottom: [{ bottom_id: 1221 }] }\n                  ] }\n                ] },\n                { top_id: 2, middle_1: [\n                  { middle_1_id: 21 }, { middle_1_id: 22, middle_2: [\n                    { middle_2_id: 221 }\n                  ] }\n                ] },\n                { top_id: 3, middle_1: [\n                  { middle_1_id: 31 }, { middle_1_id: 32 }\n                ] },\n                { top_id: 4 }\n              ]\n            }\n\n            get '/multi_level', data\n            expect(last_response.body).to eq('multi_level works!')\n            expect(last_response.status).to eq(200)\n          end\n\n          it 'with invalid data' do\n            data = {\n              top: [\n                { top_id: 1, middle_1: [\n                  { middle_1_id: 11 }, { middle_1_id: 12, middle_2: [\n                    { middle_2_id: 121 }, { middle_2_id: 122, bottom: [{ bottom_id: nil }] }\n                  ] }\n                ] },\n                { top_id: 2, middle_1: [\n                  { middle_1_id: 21 }, { middle_1_id: 22, middle_2: [{ middle_2_id: nil }] }\n                ] },\n                { top_id: 3, middle_1: [\n                  { middle_1_id: nil }, { middle_1_id: 32 }\n                ] },\n                { top_id: nil, missing_top_id: 4 }\n              ]\n            }\n            # debugger\n            get '/multi_level', data\n            expect(last_response.body.split(', ')).to contain_exactly(\n              'top[3][top_id] is empty',\n              'top[2][middle_1][0][middle_1_id] is empty',\n              'top[1][middle_1][1][middle_2][0][middle_2_id] is empty',\n              'top[0][middle_1][1][middle_2][1][bottom][0][bottom_id] is empty'\n            )\n            expect(last_response.status).to eq(400)\n          end\n        end\n      end\n\n      it 'exactly_one_of' do\n        subject.params do\n          requires :orders, type: Array do\n            requires :id, type: Integer\n            optional :drugs, type: Hash do\n              optional :batch_no, type: String\n              optional :batch_id, type: String\n              exactly_one_of :batch_no, :batch_id\n            end\n          end\n        end\n\n        subject.get '/exactly_one_of' do\n          'exactly_one_of works!'\n        end\n\n        data = {\n          orders: [\n            { id: 77, drugs: { batch_no: 'A1234567' } },\n            { id: 70 }\n          ]\n        }\n\n        get '/exactly_one_of', data\n        expect(last_response.body).to eq('exactly_one_of works!')\n        expect(last_response.status).to eq(200)\n      end\n\n      it 'at_least_one_of' do\n        subject.params do\n          requires :orders, type: Array do\n            requires :id, type: Integer\n            optional :drugs, type: Hash do\n              optional :batch_no, type: String\n              optional :batch_id, type: String\n              at_least_one_of :batch_no, :batch_id\n            end\n          end\n        end\n\n        subject.get '/at_least_one_of' do\n          'at_least_one_of works!'\n        end\n\n        data = {\n          orders: [\n            { id: 77, drugs: { batch_no: 'A1234567' } },\n            { id: 70 }\n          ]\n        }\n\n        get '/at_least_one_of', data\n        expect(last_response.body).to eq('at_least_one_of works!')\n        expect(last_response.status).to eq(200)\n      end\n\n      it 'all_or_none_of' do\n        subject.params do\n          requires :orders, type: Array do\n            requires :id, type: Integer\n            optional :drugs, type: Hash do\n              optional :batch_no, type: String\n              optional :batch_id, type: String\n              all_or_none_of :batch_no, :batch_id\n            end\n          end\n        end\n\n        subject.get '/all_or_none_of' do\n          'all_or_none_of works!'\n        end\n\n        data = {\n          orders: [\n            { id: 77, drugs: { batch_no: 'A1234567', batch_id: '12' } },\n            { id: 70 }\n          ]\n        }\n\n        get '/all_or_none_of', data\n        expect(last_response.body).to eq('all_or_none_of works!')\n        expect(last_response.status).to eq(200)\n      end\n    end\n\n    context 'multiple validation errors' do\n      before do\n        subject.params do\n          requires :yolo\n          requires :swag\n        end\n        subject.get '/two_required' do\n          'two required works'\n        end\n      end\n\n      it 'throws the validation errors' do\n        get '/two_required'\n        expect(last_response.status).to eq(400)\n        expect(last_response.body).to match(/yolo is missing/)\n        expect(last_response.body).to match(/swag is missing/)\n      end\n    end\n\n    context 'custom validation' do\n      let(:custom_validator) do\n        Class.new(Grape::Validations::Validators::Base) do\n          def validate_param!(attr_name, params)\n            return if params[attr_name] == 'im custom'\n\n            raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: 'is not custom!')\n          end\n        end\n      end\n\n      before do\n        stub_const('CustomvalidatorValidator', custom_validator)\n        described_class.register(CustomvalidatorValidator)\n      end\n\n      after do\n        described_class.deregister(:customvalidator)\n      end\n\n      context 'when using optional with a custom validator' do\n        before do\n          subject.params do\n            optional :custom, customvalidator: true\n          end\n          subject.get '/optional_custom' do\n            'optional with custom works!'\n          end\n        end\n\n        it 'validates when param is present' do\n          get '/optional_custom', custom: 'im custom'\n          expect(last_response.status).to eq(200)\n          expect(last_response.body).to eq('optional with custom works!')\n\n          get '/optional_custom', custom: 'im wrong'\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq('custom is not custom!')\n        end\n\n        it \"skips validation when parameter isn't present\" do\n          get '/optional_custom'\n          expect(last_response.status).to eq(200)\n          expect(last_response.body).to eq('optional with custom works!')\n        end\n\n        it 'validates with custom validator when param present and incorrect type' do\n          subject.params do\n            optional :custom, type: String, customvalidator: true\n          end\n\n          get '/optional_custom', custom: 123\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq('custom is not custom!')\n        end\n      end\n\n      context 'when using requires with a custom validator' do\n        before do\n          subject.params do\n            requires :custom, customvalidator: true\n          end\n          subject.get '/required_custom' do\n            'required with custom works!'\n          end\n        end\n\n        it 'validates when param is present' do\n          get '/required_custom', custom: 'im wrong, validate me'\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq('custom is not custom!')\n\n          get '/required_custom', custom: 'im custom'\n          expect(last_response.status).to eq(200)\n          expect(last_response.body).to eq('required with custom works!')\n        end\n\n        it 'validates when param is not present' do\n          get '/required_custom'\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq('custom is missing, custom is not custom!')\n        end\n\n        context 'nested namespaces' do\n          before do\n            subject.params do\n              requires :custom, customvalidator: true\n            end\n            subject.namespace 'nested' do\n              get 'one' do\n                'validation failed'\n              end\n              namespace 'nested' do\n                get 'two' do\n                  'validation failed'\n                end\n              end\n            end\n            subject.namespace 'peer' do\n              get 'one' do\n                'no validation required'\n              end\n              namespace 'nested' do\n                get 'two' do\n                  'no validation required'\n                end\n              end\n            end\n\n            subject.namespace 'unrelated' do\n              params do\n                requires :name\n              end\n              get 'one' do\n                'validation required'\n              end\n\n              namespace 'double' do\n                get 'two' do\n                  'no validation required'\n                end\n              end\n            end\n          end\n\n          specify 'the parent namespace uses the validator' do\n            get '/nested/one', custom: 'im wrong, validate me'\n            expect(last_response.status).to eq(400)\n            expect(last_response.body).to eq('custom is not custom!')\n          end\n\n          specify 'the nested namespace inherits the custom validator' do\n            get '/nested/nested/two', custom: 'im wrong, validate me'\n            expect(last_response.status).to eq(400)\n            expect(last_response.body).to eq('custom is not custom!')\n          end\n\n          specify 'peer namespaces does not have the validator' do\n            get '/peer/one', custom: 'im not validated'\n            expect(last_response.status).to eq(200)\n            expect(last_response.body).to eq('no validation required')\n          end\n\n          specify 'namespaces nested in peers should also not have the validator' do\n            get '/peer/nested/two', custom: 'im not validated'\n            expect(last_response.status).to eq(200)\n            expect(last_response.body).to eq('no validation required')\n          end\n\n          specify 'when nested, specifying a route should clear out the validations for deeper nested params' do\n            get '/unrelated/one'\n            expect(last_response.status).to eq(400)\n            get '/unrelated/double/two'\n            expect(last_response.status).to eq(200)\n          end\n        end\n      end\n\n      context 'when using options on param' do\n        let(:custom_validator_with_options) do\n          Class.new(Grape::Validations::Validators::Base) do\n            def validate_param!(attr_name, params)\n              return if params[attr_name] == @option[:text]\n\n              raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message)\n            end\n          end\n        end\n\n        before do\n          stub_const('CustomvalidatorWithOptionsValidator', custom_validator_with_options)\n          described_class.register(CustomvalidatorWithOptionsValidator)\n          subject.params do\n            optional :custom, customvalidator_with_options: { text: 'im custom with options', message: 'is not custom with options!' }\n          end\n          subject.get '/optional_custom' do\n            'optional with custom works!'\n          end\n        end\n\n        after do\n          described_class.deregister(:customvalidator_with_options)\n        end\n\n        it 'validates param with custom validator with options' do\n          get '/optional_custom', custom: 'im custom with options'\n          expect(last_response.status).to eq(200)\n          expect(last_response.body).to eq('optional with custom works!')\n\n          get '/optional_custom', custom: 'im wrong'\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq('custom is not custom with options!')\n        end\n      end\n    end\n\n    context 'named' do\n      context 'can be included in usual params' do\n        before do\n          shared_params = Module.new do\n            extend Grape::DSL::Helpers::BaseHelper\n\n            params :period do\n              optional :start_date\n              optional :end_date\n            end\n          end\n\n          subject.helpers shared_params\n\n          subject.helpers do\n            params :pagination do\n              optional :page, type: Integer\n              optional :per_page, type: Integer\n            end\n          end\n        end\n\n        it 'by #use' do\n          subject.format :json\n          subject.params do\n            use :pagination\n          end\n          subject.get('/') do\n            declared(params)\n          end\n          env = Rack::MockRequest.env_for('/', method: Rack::GET, params: { page: 1, per_page: 10 })\n          response = Rack::MockResponse[*subject.call(env)]\n          expect(JSON.parse(response.body)).to eq('page' => 1, 'per_page' => 10)\n        end\n\n        it 'by #use with multiple params' do\n          subject.format :json\n          subject.params do\n            use :pagination, :period\n          end\n          subject.get('/') do\n            declared(params)\n          end\n          env = Rack::MockRequest.env_for('/', method: Rack::GET, params: { page: 1, per_page: 10, start_date: '2025-01-01', end_date: '2026-01-01' })\n          response = Rack::MockResponse[*subject.call(env)]\n          expect(JSON.parse(response.body)).to eq('page' => 1, 'per_page' => 10, 'start_date' => '2025-01-01', 'end_date' => '2026-01-01')\n        end\n      end\n\n      context 'with block' do\n        before do\n          subject.helpers do\n            params :order do |options|\n              optional :order, type: Symbol, values: %i[asc desc], default: options[:default_order]\n              optional :order_by, type: Symbol, values: options[:order_by], default: options[:default_order_by]\n            end\n          end\n          subject.format :json\n          subject.params do\n            use :order, default_order: :asc, order_by: %i[name created_at], default_order_by: :created_at\n          end\n          subject.get '/order' do\n            {\n              order: params[:order],\n              order_by: params[:order_by]\n            }\n          end\n        end\n\n        it 'returns defaults' do\n          get '/order'\n          expect(last_response.status).to eq(200)\n          expect(last_response.body).to eq({ order: :asc, order_by: :created_at }.to_json)\n        end\n\n        it 'overrides default value for order' do\n          get '/order?order=desc'\n          expect(last_response.status).to eq(200)\n          expect(last_response.body).to eq({ order: :desc, order_by: :created_at }.to_json)\n        end\n\n        it 'overrides default value for order_by' do\n          get '/order?order_by=name'\n          expect(last_response.status).to eq(200)\n          expect(last_response.body).to eq({ order: :asc, order_by: :name }.to_json)\n        end\n\n        it 'fails with invalid value' do\n          get '/order?order=invalid'\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq('{\"error\":\"order does not have a valid value\"}')\n        end\n      end\n    end\n\n    context 'with block and keyword argument' do\n      before do\n        subject.helpers do\n          params :shared_params do |type:|\n            optional :param, default: type\n          end\n        end\n        subject.format :json\n        subject.params do\n          use :shared_params, type: 'value'\n        end\n        subject.get '/shared_params' do\n          {\n            param: params[:param]\n          }\n        end\n      end\n\n      it 'works' do\n        get '/shared_params'\n\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq({ param: 'value' }.to_json)\n      end\n    end\n\n    context 'with block and empty args' do\n      before do\n        subject.helpers do\n          params :shared_params do |empty_args|\n            optional :param, default: empty_args[:some]\n          end\n        end\n        subject.format :json\n        subject.params do\n          use :shared_params\n        end\n        subject.get '/shared_params' do\n          :ok\n        end\n      end\n\n      it 'works' do\n        get '/shared_params'\n\n        expect(last_response.status).to eq(200)\n      end\n    end\n\n    context 'all or none' do\n      context 'optional params' do\n        before do\n          subject.resource :custom_message do\n            params do\n              optional :beer\n              optional :wine\n              optional :juice\n              all_or_none_of :beer, :wine, :juice, message: 'all params are required or none is required'\n            end\n            get '/all_or_none' do\n              'all_or_none works!'\n            end\n          end\n        end\n\n        context 'with a custom validation message' do\n          it 'errors when any one is present' do\n            get '/custom_message/all_or_none', beer: 'string'\n            expect(last_response.status).to eq(400)\n            expect(last_response.body).to eq 'beer, wine, juice all params are required or none is required'\n          end\n\n          it 'works when all params are present' do\n            get '/custom_message/all_or_none', beer: 'string', wine: 'anotherstring', juice: 'anotheranotherstring'\n            expect(last_response.status).to eq(200)\n            expect(last_response.body).to eq 'all_or_none works!'\n          end\n\n          it 'works when none are present' do\n            get '/custom_message/all_or_none'\n            expect(last_response.status).to eq(200)\n            expect(last_response.body).to eq 'all_or_none works!'\n          end\n        end\n      end\n    end\n\n    context 'mutually exclusive' do\n      context 'optional params' do\n        context 'with custom validation message' do\n          it 'errors when two or more are present' do\n            subject.resources :custom_message do\n              params do\n                optional :beer\n                optional :wine\n                optional :juice\n                mutually_exclusive :beer, :wine, :juice, message: 'are mutually exclusive cannot pass both params'\n              end\n              get '/mutually_exclusive' do\n                'mutually_exclusive works!'\n              end\n            end\n            get '/custom_message/mutually_exclusive', beer: 'string', wine: 'anotherstring'\n            expect(last_response.status).to eq(400)\n            expect(last_response.body).to eq 'beer, wine are mutually exclusive cannot pass both params'\n          end\n        end\n\n        it 'errors when two or more are present' do\n          subject.params do\n            optional :beer\n            optional :wine\n            optional :juice\n            mutually_exclusive :beer, :wine, :juice\n          end\n          subject.get '/mutually_exclusive' do\n            'mutually_exclusive works!'\n          end\n\n          get '/mutually_exclusive', beer: 'string', wine: 'anotherstring'\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq 'beer, wine are mutually exclusive'\n        end\n      end\n\n      context 'more than one set of mutually exclusive params' do\n        context 'with a custom validation message' do\n          it 'errors for all sets' do\n            subject.resources :custom_message do\n              params do\n                optional :beer\n                optional :wine\n                mutually_exclusive :beer, :wine, message: 'are mutually exclusive pass only one'\n                optional :nested, type: Hash do\n                  optional :scotch\n                  optional :aquavit\n                  mutually_exclusive :scotch, :aquavit, message: 'are mutually exclusive pass only one'\n                end\n                optional :nested2, type: Array do\n                  optional :scotch2\n                  optional :aquavit2\n                  mutually_exclusive :scotch2, :aquavit2, message: 'are mutually exclusive pass only one'\n                end\n              end\n              get '/mutually_exclusive' do\n                'mutually_exclusive works!'\n              end\n            end\n            get '/custom_message/mutually_exclusive', beer: 'true', wine: 'true', nested: { scotch: 'true', aquavit: 'true' }, nested2: [{ scotch2: 'true' }, { scotch2: 'true', aquavit2: 'true' }]\n            expect(last_response.status).to eq(400)\n            expect(last_response.body).to eq(\n              'beer, wine are mutually exclusive pass only one, nested[scotch], nested[aquavit] are mutually exclusive pass only one, nested2[1][scotch2], nested2[1][aquavit2] are mutually exclusive pass only one'\n            )\n          end\n        end\n\n        it 'errors for all sets' do\n          subject.params do\n            optional :beer\n            optional :wine\n            mutually_exclusive :beer, :wine\n            optional :nested, type: Hash do\n              optional :scotch\n              optional :aquavit\n              mutually_exclusive :scotch, :aquavit\n            end\n            optional :nested2, type: Array do\n              optional :scotch2\n              optional :aquavit2\n              mutually_exclusive :scotch2, :aquavit2\n            end\n          end\n          subject.get '/mutually_exclusive' do\n            'mutually_exclusive works!'\n          end\n\n          get '/mutually_exclusive', beer: 'true', wine: 'true', nested: { scotch: 'true', aquavit: 'true' }, nested2: [{ scotch2: 'true' }, { scotch2: 'true', aquavit2: 'true' }]\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq 'beer, wine are mutually exclusive, nested[scotch], nested[aquavit] are mutually exclusive, nested2[1][scotch2], nested2[1][aquavit2] are mutually exclusive'\n        end\n      end\n\n      context 'in a group' do\n        it 'works when only one from the set is present' do\n          subject.params do\n            group :drink, type: Hash do\n              optional :wine\n              optional :beer\n              optional :juice\n              mutually_exclusive :beer, :wine, :juice\n            end\n          end\n          subject.get '/mutually_exclusive_group' do\n            'mutually_exclusive_group works!'\n          end\n\n          get '/mutually_exclusive_group', drink: { beer: 'true' }\n          expect(last_response.status).to eq(200)\n        end\n\n        it 'errors when more than one from the set is present' do\n          subject.params do\n            group :drink, type: Hash do\n              optional :wine\n              optional :beer\n              optional :juice\n\n              mutually_exclusive :beer, :wine, :juice\n            end\n          end\n          subject.get '/mutually_exclusive_group' do\n            'mutually_exclusive_group works!'\n          end\n\n          get '/mutually_exclusive_group', drink: { beer: 'true', juice: 'true', wine: 'true' }\n          expect(last_response.status).to eq(400)\n        end\n      end\n\n      context 'mutually exclusive params inside Hash group' do\n        it 'invalidates if request param is invalid type' do\n          subject.params do\n            optional :wine, type: Hash do\n              optional :grape\n              optional :country\n              mutually_exclusive :grape, :country\n            end\n          end\n          subject.post '/mutually_exclusive' do\n            'mutually_exclusive works!'\n          end\n\n          post '/mutually_exclusive', wine: '2015 sauvignon'\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq 'wine is invalid'\n        end\n      end\n    end\n\n    context 'exactly one of' do\n      context 'params' do\n        before do\n          subject.resources :custom_message do\n            params do\n              optional :beer\n              optional :wine\n              optional :juice\n              exactly_one_of :beer, :wine, :juice, message: 'are missing, exactly one parameter is required'\n            end\n            get '/exactly_one_of' do\n              'exactly_one_of works!'\n            end\n          end\n\n          subject.params do\n            optional :beer\n            optional :wine\n            optional :juice\n            exactly_one_of :beer, :wine, :juice\n          end\n          subject.get '/exactly_one_of' do\n            'exactly_one_of works!'\n          end\n        end\n\n        context 'with a custom validation message' do\n          it 'errors when none are present' do\n            get '/custom_message/exactly_one_of'\n            expect(last_response.status).to eq(400)\n            expect(last_response.body).to eq 'beer, wine, juice are missing, exactly one parameter is required'\n          end\n\n          it 'succeeds when one is present' do\n            get '/custom_message/exactly_one_of', beer: 'string'\n            expect(last_response.status).to eq(200)\n            expect(last_response.body).to eq 'exactly_one_of works!'\n          end\n\n          it 'errors when two or more are present' do\n            get '/custom_message/exactly_one_of', beer: 'string', wine: 'anotherstring'\n            expect(last_response.status).to eq(400)\n            expect(last_response.body).to eq 'beer, wine are missing, exactly one parameter is required'\n          end\n        end\n\n        it 'errors when none are present' do\n          get '/exactly_one_of'\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq 'beer, wine, juice are missing, exactly one parameter must be provided'\n        end\n\n        it 'succeeds when one is present' do\n          get '/exactly_one_of', beer: 'string'\n          expect(last_response.status).to eq(200)\n          expect(last_response.body).to eq 'exactly_one_of works!'\n        end\n\n        it 'errors when two or more are present' do\n          get '/exactly_one_of', beer: 'string', wine: 'anotherstring'\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq 'beer, wine are mutually exclusive'\n        end\n      end\n\n      context 'nested params' do\n        before do\n          subject.params do\n            requires :nested, type: Hash do\n              optional :beer_nested\n              optional :wine_nested\n              optional :juice_nested\n              exactly_one_of :beer_nested, :wine_nested, :juice_nested\n            end\n            optional :nested2, type: Array do\n              optional :beer_nested2\n              optional :wine_nested2\n              optional :juice_nested2\n              exactly_one_of :beer_nested2, :wine_nested2, :juice_nested2\n            end\n          end\n          subject.get '/exactly_one_of_nested' do\n            'exactly_one_of works!'\n          end\n        end\n\n        it 'errors when none are present' do\n          get '/exactly_one_of_nested'\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq 'nested is missing, nested[beer_nested], nested[wine_nested], nested[juice_nested] are missing, exactly one parameter must be provided'\n        end\n\n        it 'succeeds when one is present' do\n          get '/exactly_one_of_nested', nested: { beer_nested: 'string' }\n          expect(last_response.status).to eq(200)\n          expect(last_response.body).to eq 'exactly_one_of works!'\n        end\n\n        it 'errors when two or more are present' do\n          get '/exactly_one_of_nested', nested: { beer_nested: 'string' }, nested2: [{ beer_nested2: 'string', wine_nested2: 'anotherstring' }]\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq 'nested2[0][beer_nested2], nested2[0][wine_nested2] are mutually exclusive'\n        end\n      end\n    end\n\n    context 'at least one of' do\n      context 'params' do\n        before do\n          subject.resources :custom_message do\n            params do\n              optional :beer\n              optional :wine\n              optional :juice\n              at_least_one_of :beer, :wine, :juice, message: 'are missing, please specify at least one param'\n            end\n            get '/at_least_one_of' do\n              'at_least_one_of works!'\n            end\n          end\n\n          subject.params do\n            optional :beer\n            optional :wine\n            optional :juice\n            at_least_one_of :beer, :wine, :juice\n          end\n          subject.get '/at_least_one_of' do\n            'at_least_one_of works!'\n          end\n        end\n\n        context 'with a custom validation message' do\n          it 'errors when none are present' do\n            get '/custom_message/at_least_one_of'\n            expect(last_response.status).to eq(400)\n            expect(last_response.body).to eq 'beer, wine, juice are missing, please specify at least one param'\n          end\n\n          it 'does not error when one is present' do\n            get '/custom_message/at_least_one_of', beer: 'string'\n            expect(last_response.status).to eq(200)\n            expect(last_response.body).to eq 'at_least_one_of works!'\n          end\n\n          it 'does not error when two are present' do\n            get '/custom_message/at_least_one_of', beer: 'string', wine: 'string'\n            expect(last_response.status).to eq(200)\n            expect(last_response.body).to eq 'at_least_one_of works!'\n          end\n        end\n\n        it 'errors when none are present' do\n          get '/at_least_one_of'\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq 'beer, wine, juice are missing, at least one parameter must be provided'\n        end\n\n        it 'does not error when one is present' do\n          get '/at_least_one_of', beer: 'string'\n          expect(last_response.status).to eq(200)\n          expect(last_response.body).to eq 'at_least_one_of works!'\n        end\n\n        it 'does not error when two are present' do\n          get '/at_least_one_of', beer: 'string', wine: 'string'\n          expect(last_response.status).to eq(200)\n          expect(last_response.body).to eq 'at_least_one_of works!'\n        end\n      end\n\n      context 'nested params' do\n        before do\n          subject.params do\n            requires :nested, type: Hash do\n              optional :beer\n              optional :wine\n              optional :juice\n              at_least_one_of :beer, :wine, :juice\n            end\n            optional :nested2, type: Array do\n              optional :beer\n              optional :wine\n              optional :juice\n              at_least_one_of :beer, :wine, :juice\n            end\n          end\n          subject.get '/at_least_one_of_nested' do\n            'at_least_one_of works!'\n          end\n        end\n\n        it 'errors when none are present' do\n          get '/at_least_one_of_nested'\n          expect(last_response.status).to eq(400)\n          expect(last_response.body).to eq 'nested is missing, nested[beer], nested[wine], nested[juice] are missing, at least one parameter must be provided'\n        end\n\n        it 'does not error when one is present' do\n          get '/at_least_one_of_nested', nested: { beer: 'string' }, nested2: [{ beer: 'string' }]\n          expect(last_response.status).to eq(200)\n          expect(last_response.body).to eq 'at_least_one_of works!'\n        end\n\n        it 'does not error when two are present' do\n          get '/at_least_one_of_nested', nested: { beer: 'string', wine: 'string' }, nested2: [{ beer: 'string', wine: 'string' }]\n          expect(last_response.status).to eq(200)\n          expect(last_response.body).to eq 'at_least_one_of works!'\n        end\n      end\n    end\n\n    context 'in a group' do\n      it 'works when only one from the set is present' do\n        subject.params do\n          group :drink, type: Hash do\n            optional :wine\n            optional :beer\n            optional :juice\n\n            exactly_one_of :beer, :wine, :juice\n          end\n        end\n        subject.get '/exactly_one_of_group' do\n          'exactly_one_of_group works!'\n        end\n\n        get '/exactly_one_of_group', drink: { beer: 'true' }\n        expect(last_response.status).to eq(200)\n      end\n\n      it 'errors when no parameter from the set is present' do\n        subject.params do\n          group :drink, type: Hash do\n            optional :wine\n            optional :beer\n            optional :juice\n\n            exactly_one_of :beer, :wine, :juice\n          end\n        end\n        subject.get '/exactly_one_of_group' do\n          'exactly_one_of_group works!'\n        end\n\n        get '/exactly_one_of_group', drink: {}\n        expect(last_response.status).to eq(400)\n      end\n\n      it 'errors when more than one from the set is present' do\n        subject.params do\n          group :drink, type: Hash do\n            optional :wine\n            optional :beer\n            optional :juice\n\n            exactly_one_of :beer, :wine, :juice\n          end\n        end\n        subject.get '/exactly_one_of_group' do\n          'exactly_one_of_group works!'\n        end\n\n        get '/exactly_one_of_group', drink: { beer: 'true', juice: 'true', wine: 'true' }\n        expect(last_response.status).to eq(400)\n      end\n\n      it 'does not falsely think the param is there if it is provided outside the block' do\n        subject.params do\n          group :drink, type: Hash do\n            optional :wine\n            optional :beer\n            optional :juice\n\n            exactly_one_of :beer, :wine, :juice\n          end\n        end\n        subject.get '/exactly_one_of_group' do\n          'exactly_one_of_group works!'\n        end\n\n        get '/exactly_one_of_group', drink: { foo: 'bar' }, beer: 'true'\n        expect(last_response.status).to eq(400)\n      end\n    end\n\n    # Ensure there is no leakage of indices between requests\n    context 'required with a hash inside an array' do\n      before do\n        subject.params do\n          requires :items, type: Array do\n            requires :item, type: Hash do\n              requires :name, type: String\n            end\n          end\n        end\n        subject.post '/required' do\n          'required works'\n        end\n      end\n\n      let(:valid_item) { { item: { name: 'foo' } } }\n\n      let(:params) do\n        {\n          items: [\n            valid_item,\n            valid_item,\n            {}\n          ]\n        }\n      end\n\n      it 'makes sure the error message is independent of the previous request' do\n        post_with_json '/required', {}\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('items is missing, items[item][name] is missing')\n\n        post_with_json '/required', params\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('items[2][item] is missing, items[2][item][name] is missing')\n\n        post_with_json '/required', {}\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('items is missing, items[item][name] is missing')\n      end\n    end\n  end\n\n  describe 'require_validator' do\n    subject { described_class.require_validator(short_name) }\n\n    context 'when found' do\n      let(:short_name) { :presence }\n\n      it { is_expected.to be(Grape::Validations::Validators::PresenceValidator) }\n    end\n\n    context 'when not found' do\n      let(:short_name) { :test }\n\n      it 'raises an error' do\n        expect { subject }.to raise_error(Grape::Exceptions::UnknownValidator)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/integration/dry_validation/dry_validation_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe 'Dry::Schema', if: defined?(Dry::Schema) do\n  describe 'Grape::DSL::Validations' do\n    subject { app }\n\n    let(:app) do\n      Class.new do\n        extend Grape::DSL::Validations\n        extend Grape::DSL::Settings\n      end\n    end\n\n    describe '.contract' do\n      it 'saves the schema instance' do\n        expect(subject.contract(Dry::Schema.Params)).to be_a Grape::Validations::ContractScope\n      end\n\n      it 'errors without params or block' do\n        expect { subject.contract }.to raise_error(ArgumentError)\n      end\n    end\n  end\n\n  describe 'Grape::Validations::ContractScope' do\n    let(:validated_params) { {} }\n    let(:app) do\n      vp = validated_params\n\n      Class.new(Grape::API) do\n        after_validation do\n          vp.replace(params)\n        end\n      end\n    end\n\n    context 'with simple schema, pre-defined' do\n      let(:contract) do\n        Dry::Schema.Params do\n          required(:number).filled(:integer)\n        end\n      end\n\n      before do\n        app.contract(contract)\n        app.post('/required')\n      end\n\n      it 'coerces the parameter value one level deep' do\n        post '/required', number: '1'\n        expect(last_response).to be_created\n        expect(validated_params).to eq('number' => 1)\n      end\n\n      it 'shows expected validation error' do\n        post '/required'\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('number is missing')\n      end\n    end\n\n    context 'with contract class' do\n      let(:contract) do\n        Class.new(Dry::Validation::Contract) do\n          params do\n            required(:number).filled(:integer)\n            required(:name).filled(:string)\n          end\n\n          rule(:number) do\n            key.failure('is too high') if value > 5\n          end\n        end\n      end\n\n      before do\n        app.contract(contract)\n        app.post('/required')\n      end\n\n      it 'coerces the parameter' do\n        post '/required', number: '1', name: '2'\n        expect(last_response).to be_created\n        expect(validated_params).to eq('number' => 1, 'name' => '2')\n      end\n\n      it 'shows expected validation error' do\n        post '/required', number: '6'\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('name is missing, number is too high')\n      end\n    end\n\n    context 'with nested schema' do\n      before do\n        app.contract do\n          required(:home).hash do\n            required(:address).hash do\n              required(:number).filled(:integer)\n            end\n          end\n          required(:turns).array(:integer)\n        end\n\n        app.post('/required')\n      end\n\n      it 'keeps unknown parameters' do\n        post '/required', home: { address: { number: '1', street: 'Baker' } }, turns: %w[2 3]\n        expect(last_response).to be_created\n        expected = { 'home' => { 'address' => { 'number' => 1, 'street' => 'Baker' } }, 'turns' => [2, 3] }\n        expect(validated_params).to eq(expected)\n      end\n\n      it 'shows expected validation error' do\n        post '/required', home: { address: { something: 'else' } }\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('home[address][number] is missing, turns is missing')\n      end\n    end\n\n    context 'with mixed validation sources' do\n      before do\n        app.resource :foos do\n          route_param :foo_id, type: Integer do\n            contract do\n              required(:number).filled(:integer)\n            end\n            post('/required')\n          end\n        end\n      end\n\n      it 'combines the coercions' do\n        post '/foos/123/required', number: '1'\n        expect(last_response).to be_created\n        expected = { 'foo_id' => 123, 'number' => 1 }\n        expect(validated_params).to eq(expected)\n      end\n\n      it 'shows validation error for missing' do\n        post '/foos/123/required'\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('number is missing')\n      end\n\n      it 'includes keys from all sources into declared' do\n        declared_params = nil\n\n        app.after_validation do\n          declared_params = declared(params)\n        end\n\n        post '/foos/123/required', number: '1', string: '2'\n        expect(last_response).to be_created\n        expected = { 'foo_id' => 123, 'number' => 1 }\n        expect(validated_params).to eq(expected.merge('string' => '2'))\n        expect(declared_params).to eq(expected)\n      end\n    end\n\n    context 'with schema config validate_keys=true' do\n      it 'validates the whole params hash' do\n        app.resource :foos do\n          route_param :foo_id do\n            contract do\n              config.validate_keys = true\n\n              required(:number).filled(:integer)\n              required(:foo_id).filled(:integer)\n            end\n            post('/required')\n          end\n        end\n\n        post '/foos/123/required', number: '1'\n        expect(last_response).to be_created\n        expected = { 'foo_id' => 123, 'number' => 1 }\n        expect(validated_params).to eq(expected)\n      end\n\n      it 'fails validation for any parameters not in schema' do\n        app.resource :foos do\n          route_param :foo_id, type: Integer do\n            contract do\n              config.validate_keys = true\n\n              required(:number).filled(:integer)\n            end\n            post('/required')\n          end\n        end\n\n        post '/foos/123/required', number: '1'\n        expect(last_response).to be_bad_request\n        expect(last_response.body).to eq('foo_id is not allowed')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/integration/grape_entity/entity_spec.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'rack/contrib/jsonp'\n\ndescribe 'Grape::Entity', if: defined?(Grape::Entity) do\n  describe '#present' do\n    subject { Class.new(Grape::API) }\n\n    let(:app) { subject }\n\n    before do\n      stub_const('TestObject', Class.new)\n      stub_const('FakeCollection', Class.new do\n        def first\n          TestObject.new\n        end\n      end)\n    end\n\n    it 'sets the object as the body if no options are provided' do\n      inner_body = nil\n      subject.get '/example' do\n        present({ abc: 'def' })\n        inner_body = body\n      end\n      get '/example'\n      expect(inner_body).to eql(abc: 'def')\n    end\n\n    it 'pulls a representation from the class options if it exists' do\n      entity = Class.new(Grape::Entity)\n      allow(entity).to receive(:represent).and_return('Hiya')\n\n      subject.represent Object, with: entity\n      subject.get '/example' do\n        present Object.new\n      end\n      get '/example'\n      expect(last_response.body).to eq('Hiya')\n    end\n\n    it 'pulls a representation from the class options if the presented object is a collection of objects' do\n      entity = Class.new(Grape::Entity)\n      allow(entity).to receive(:represent).and_return('Hiya')\n\n      subject.represent TestObject, with: entity\n      subject.get '/example' do\n        present [TestObject.new]\n      end\n\n      subject.get '/example2' do\n        present FakeCollection.new\n      end\n\n      get '/example'\n      expect(last_response.body).to eq('Hiya')\n\n      get '/example2'\n      expect(last_response.body).to eq('Hiya')\n    end\n\n    it 'pulls a representation from the class ancestor if it exists' do\n      entity = Class.new(Grape::Entity)\n      allow(entity).to receive(:represent).and_return('Hiya')\n\n      subclass = Class.new(Object)\n\n      subject.represent Object, with: entity\n      subject.get '/example' do\n        present subclass.new\n      end\n      get '/example'\n      expect(last_response.body).to eq('Hiya')\n    end\n\n    it 'automatically uses Klass::Entity if that exists' do\n      some_model = Class.new\n      entity = Class.new(Grape::Entity)\n      allow(entity).to receive(:represent).and_return('Auto-detect!')\n\n      some_model.const_set :Entity, entity\n\n      subject.get '/example' do\n        present some_model.new\n      end\n      get '/example'\n      expect(last_response.body).to eq('Auto-detect!')\n    end\n\n    it 'automatically uses Klass::Entity based on the first object in the collection being presented' do\n      some_model = Class.new\n      entity = Class.new(Grape::Entity)\n      allow(entity).to receive(:represent).and_return('Auto-detect!')\n\n      some_model.const_set :Entity, entity\n\n      subject.get '/example' do\n        present [some_model.new]\n      end\n      get '/example'\n      expect(last_response.body).to eq('Auto-detect!')\n    end\n\n    it 'does not run autodetection for Entity when explicitly provided' do\n      entity = Class.new(Grape::Entity)\n      some_array = []\n\n      subject.get '/example' do\n        present some_array, with: entity\n      end\n\n      expect(some_array).not_to receive(:first)\n      get '/example'\n    end\n\n    it 'does not use #first method on ActiveRecord::Relation to prevent needless sql query' do\n      entity = Class.new(Grape::Entity)\n      some_relation = Class.new\n      some_model = Class.new\n\n      allow(entity).to receive(:represent).and_return('Auto-detect!')\n      allow(some_relation).to receive(:first)\n      allow(some_relation).to receive(:klass).and_return(some_model)\n\n      some_model.const_set :Entity, entity\n\n      subject.get '/example' do\n        present some_relation\n      end\n\n      expect(some_relation).not_to receive(:first)\n      get '/example'\n      expect(last_response.body).to eq('Auto-detect!')\n    end\n\n    it 'autodetection does not use Entity if it is not a presenter' do\n      some_model = Class.new\n      entity = Class.new\n\n      some_model.class.const_set :Entity, entity\n\n      subject.get '/example' do\n        present some_model\n      end\n      get '/example'\n      expect(entity).not_to receive(:represent)\n    end\n\n    it 'adds a root key to the output if one is given' do\n      inner_body = nil\n      subject.get '/example' do\n        present({ abc: 'def' }, root: :root)\n        inner_body = body\n      end\n      get '/example'\n      expect(inner_body).to eql(root: { abc: 'def' })\n    end\n\n    %i[json serializable_hash].each do |format|\n      it \"presents with #{format}\" do\n        entity = Class.new(Grape::Entity)\n        entity.root 'examples', 'example'\n        entity.expose :id\n\n        subject.format format\n        subject.get '/example' do\n          c = Class.new do\n            attr_reader :id\n\n            def initialize(id)\n              @id = id\n            end\n          end\n          present c.new(1), with: entity\n        end\n\n        get '/example'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('{\"example\":{\"id\":1}}')\n      end\n\n      it \"presents with #{format} collection\" do\n        entity = Class.new(Grape::Entity)\n        entity.root 'examples', 'example'\n        entity.expose :id\n\n        subject.format format\n        subject.get '/examples' do\n          c = Class.new do\n            attr_reader :id\n\n            def initialize(id)\n              @id = id\n            end\n          end\n          examples = [c.new(1), c.new(2)]\n          present examples, with: entity\n        end\n\n        get '/examples'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('{\"examples\":[{\"id\":1},{\"id\":2}]}')\n      end\n    end\n\n    it 'presents with xml' do\n      entity = Class.new(Grape::Entity)\n      entity.root 'examples', 'example'\n      entity.expose :name\n\n      subject.format :xml\n\n      subject.get '/example' do\n        c = Class.new do\n          attr_reader :name\n\n          def initialize(args)\n            @name = args[:name] || 'no name set'\n          end\n        end\n        present c.new(name: 'johnnyiller'), with: entity\n      end\n      get '/example'\n      expect(last_response).to be_successful\n      expect(last_response.content_type).to eq('application/xml')\n      expect(last_response.body).to eq <<~XML\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <hash>\n          <example>\n            <name>johnnyiller</name>\n          </example>\n        </hash>\n      XML\n    end\n\n    it 'presents with json' do\n      entity = Class.new(Grape::Entity)\n      entity.root 'examples', 'example'\n      entity.expose :name\n\n      subject.format :json\n\n      subject.get '/example' do\n        c = Class.new do\n          attr_reader :name\n\n          def initialize(args)\n            @name = args[:name] || 'no name set'\n          end\n        end\n        present c.new(name: 'johnnyiller'), with: entity\n      end\n      get '/example'\n      expect(last_response).to be_successful\n      expect(last_response.content_type).to eq('application/json')\n      expect(last_response.body).to eq('{\"example\":{\"name\":\"johnnyiller\"}}')\n    end\n\n    it 'presents with jsonp utilising Rack::JSONP' do\n      subject.use Rack::JSONP\n\n      entity = Class.new(Grape::Entity)\n      entity.root 'examples', 'example'\n      entity.expose :name\n\n      # Rack::JSONP expects a standard JSON response in UTF-8 format\n      subject.format :json\n      subject.formatter :json, lambda { |object, _|\n        object.to_json.encode('utf-8')\n      }\n\n      subject.get '/example' do\n        c = Class.new do\n          attr_reader :name\n\n          def initialize(args)\n            @name = args[:name] || 'no name set'\n          end\n        end\n\n        present c.new(name: 'johnnyiller'), with: entity\n      end\n\n      get '/example?callback=abcDef'\n      expect(last_response).to be_successful\n      expect(last_response.content_type).to eq('application/javascript')\n      expect(last_response.body).to include 'abcDef({\"example\":{\"name\":\"johnnyiller\"}})'\n    end\n\n    context 'present with multiple entities' do\n      it 'present with multiple entities using optional symbol' do\n        user = Class.new do\n          attr_reader :name\n\n          def initialize(args)\n            @name = args[:name] || 'no name set'\n          end\n        end\n        user1 = user.new(name: 'user1')\n        user2 = user.new(name: 'user2')\n\n        entity = Class.new(Grape::Entity)\n        entity.expose :name\n\n        subject.format :json\n        subject.get '/example' do\n          present :page, 1\n          present :user1, user1, with: entity\n          present :user2, user2, with: entity\n        end\n        get '/example'\n        expect_response_json = {\n          'page' => 1,\n          'user1' => { 'name' => 'user1' },\n          'user2' => { 'name' => 'user2' }\n        }\n        expect(JSON(last_response.body)).to eq(expect_response_json)\n      end\n    end\n  end\n\n  describe 'Grape::Middleware::Error' do\n    let(:error_entity) do\n      Class.new(Grape::Entity) do\n        expose :code\n        expose :static\n\n        def static\n          'static text'\n        end\n      end\n    end\n    let(:options) { { default_message: 'Aww, hamburgers.' } }\n\n    let(:error_app) do\n      Class.new do\n        class << self\n          attr_accessor :error, :format\n\n          def call(_env)\n            throw :error, error\n          end\n        end\n      end\n    end\n\n    let(:app) do\n      opts = options\n      Rack::Builder.app do\n        use Spec::Support::EndpointFaker\n        use Grape::Middleware::Error, **opts\n        run ErrApp\n      end\n    end\n\n    before do\n      stub_const('ErrApp', error_app)\n      stub_const('ErrorEntity', error_entity)\n    end\n\n    context 'with http code' do\n      it 'presents an error message' do\n        ErrApp.error = { message: { code: 200, with: ErrorEntity } }\n        get '/'\n\n        expect(last_response.body).to eq({ code: 200, static: 'static text' }.to_json)\n      end\n    end\n  end\n\n  describe 'error_presenter' do\n    subject { last_response }\n\n    let(:error_presenter) do\n      Class.new(Grape::Entity) do\n        expose :code\n        expose :static\n\n        def static\n          'some static text'\n        end\n      end\n    end\n\n    before do\n      stub_const('ErrorPresenter', error_presenter)\n      get '/exception'\n    end\n\n    context 'when using http_codes' do\n      let(:app) do\n        Class.new(Grape::API) do\n          desc 'some desc', http_codes: [[408, 'Unauthorized', ErrorPresenter]]\n          get '/exception' do\n            error!({ code: 408 }, 408)\n          end\n        end\n      end\n\n      it 'is used as presenter' do\n        expect(subject).to be_request_timeout\n        expect(subject.body).to eql({ code: 408, static: 'some static text' }.to_json)\n      end\n    end\n\n    context 'when using with' do\n      let(:app) do\n        Class.new(Grape::API) do\n          get '/exception' do\n            error!({ code: 408, with: ErrorPresenter }, 408)\n          end\n        end\n      end\n\n      it 'presented with' do\n        expect(subject).to be_request_timeout\n        expect(subject.body).to eql({ code: 408, static: 'some static text' }.to_json)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/integration/hashie/hashie_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe 'Hashie', if: defined?(Hashie) do\n  subject { app }\n\n  let(:app) { Class.new(Grape::API) }\n\n  describe 'Grape::ParamsBuilder::HashieMash' do\n    describe 'in an endpoint' do\n      describe '#params' do\n        before do\n          subject.params do\n            build_with :hashie_mash\n          end\n\n          subject.get do\n            params.class\n          end\n        end\n\n        it 'is of type Hashie::Mash' do\n          get '/'\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('Hashie::Mash')\n        end\n      end\n    end\n\n    describe 'in an api' do\n      before do\n        subject.build_with :hashie_mash\n      end\n\n      describe '#params' do\n        before do\n          subject.get do\n            params.class\n          end\n        end\n\n        it 'is Hashie::Mash' do\n          get '/'\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('Hashie::Mash')\n        end\n      end\n\n      context 'in a nested namespace api' do\n        before do\n          subject.namespace :foo do\n            get do\n              params.class\n            end\n          end\n        end\n\n        it 'is Hashie::Mash' do\n          get '/foo'\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('Hashie::Mash')\n        end\n      end\n\n      it 'is indifferent to key or symbol access' do\n        subject.params do\n          build_with :hashie_mash\n          requires :a, type: String\n        end\n        subject.get '/foo' do\n          [params[:a], params['a']]\n        end\n\n        get '/foo', a: 'bar'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('[\"bar\", \"bar\"]')\n      end\n\n      it 'does not overwrite route_param with a regular param if they have same name' do\n        subject.namespace :route_param do\n          route_param :foo do\n            get { params.to_json }\n          end\n        end\n\n        get '/route_param/bar', foo: 'baz'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('{\"foo\":\"bar\"}')\n      end\n\n      it 'does not overwrite route_param with a defined regular param if they have same name' do\n        subject.namespace :route_param do\n          params do\n            build_with :hashie_mash\n            requires :foo, type: String\n          end\n          route_param :foo do\n            get do\n              [params[:foo], params['foo']]\n            end\n          end\n        end\n\n        get '/route_param/bar', foo: 'baz'\n        expect(last_response).to be_successful\n        expect(last_response.body).to eq('[\"bar\", \"bar\"]')\n      end\n    end\n  end\n\n  describe 'Grape::Request' do\n    let(:default_method) { Rack::GET }\n    let(:default_params) { {} }\n    let(:default_options) do\n      {\n        method: method,\n        params: params\n      }\n    end\n    let(:default_env) do\n      Rack::MockRequest.env_for('/', options)\n    end\n    let(:method) { default_method }\n    let(:params) { default_params }\n    let(:options) { default_options }\n    let(:env) { default_env }\n    let(:request) { Grape::Request.new(env) }\n\n    describe '#params' do\n      let(:params) do\n        {\n          a: '123',\n          b: 'xyz'\n        }\n      end\n\n      it 'by default returns stringified parameter keys' do\n        expect(request.params).to eq(ActiveSupport::HashWithIndifferentAccess.new('a' => '123', 'b' => 'xyz'))\n      end\n\n      context 'when build_params_with: Grape::Extensions::Hash::ParamBuilder is specified' do\n        let(:request) { Grape::Request.new(env, build_params_with: :hash) }\n\n        it 'returns symbolized params' do\n          expect(request.params).to eq(a: '123', b: 'xyz')\n        end\n      end\n\n      describe 'with grape.routing_args' do\n        let(:options) do\n          default_options.merge('grape.routing_args' => routing_args)\n        end\n        let(:routing_args) do\n          {\n            version: '123',\n            route_info: '456',\n            c: 'ccc'\n          }\n        end\n\n        it 'cuts version and route_info' do\n          expect(request.params).to eq(ActiveSupport::HashWithIndifferentAccess.new(a: '123', b: 'xyz', c: 'ccc'))\n        end\n      end\n    end\n\n    describe 'when the build_params_with is set to Hashie' do\n      subject(:request_params) { Grape::Request.new(env, build_params_with: :hashie_mash).params }\n\n      context 'when the API includes a specific param builder' do\n        it { is_expected.to be_a(Hashie::Mash) }\n      end\n    end\n  end\n\n  describe 'Grape::Validations::Validators::CoerceValidator' do\n    context 'when params is Hashie::Mash' do\n      context 'for primitive collections' do\n        before do\n          subject.params do\n            build_with :hashie_mash\n            optional :a, types: [String, Array[String]]\n            optional :b, types: [Array[Integer], Array[String]]\n            optional :c, type: Array[Integer, String]\n            optional :d, types: [Integer, String, Set[Integer, String]]\n          end\n          subject.get '/' do\n            (\n              params.a ||\n                params.b ||\n                params.c ||\n                params.d\n            ).inspect\n          end\n        end\n\n        it 'allows singular form declaration' do\n          get '/', a: 'one way'\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('\"one way\"')\n\n          get '/', a: %w[the other]\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('#<Hashie::Array [\"the\", \"other\"]>')\n\n          get '/', a: { a: 1, b: 2 }\n          expect(last_response).to be_bad_request\n          expect(last_response.body).to eq('a is invalid')\n\n          get '/', a: [1, 2, 3]\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('#<Hashie::Array [\"1\", \"2\", \"3\"]>')\n        end\n\n        it 'allows multiple collection types' do\n          get '/', b: [1, 2, 3]\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('#<Hashie::Array [1, 2, 3]>')\n\n          get '/', b: %w[1 2 3]\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('#<Hashie::Array [1, 2, 3]>')\n\n          get '/', b: [1, true, 'three']\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('#<Hashie::Array [\"1\", \"true\", \"three\"]>')\n        end\n\n        it 'allows collections with multiple types' do\n          get '/', c: [1, '2', true, 'three']\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('#<Hashie::Array [1, 2, \"true\", \"three\"]>')\n\n          get '/', d: '1'\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('1')\n\n          get '/', d: 'one'\n          expect(last_response).to be_successful\n          expect(last_response.body).to eq('\"one\"')\n\n          get '/', d: %w[1 two]\n          expect(last_response).to be_successful\n          json_set = JSON.parse([1, 'two'].to_set.to_json)\n          expect(last_response.body).to eq(json_set)\n        end\n      end\n    end\n  end\n\n  describe 'Grape::Endpoint' do\n    before do\n      subject.format :json\n      subject.params do\n        requires :first\n        optional :second\n        optional :third, default: 'third-default'\n        optional :multiple_types, types: [Integer, String]\n        optional :nested, type: Hash do\n          optional :fourth\n          optional :fifth\n          optional :nested_two, type: Hash do\n            optional :sixth\n            optional :nested_three, type: Hash do\n              optional :seventh\n            end\n          end\n          optional :nested_arr, type: Array do\n            optional :eighth\n          end\n          optional :empty_arr, type: Array\n          optional :empty_typed_arr, type: Array[String]\n          optional :empty_hash, type: Hash\n          optional :empty_set, type: Set\n          optional :empty_typed_set, type: Set[String]\n        end\n        optional :arr, type: Array do\n          optional :nineth\n        end\n        optional :empty_arr, type: Array\n        optional :empty_typed_arr, type: Array[String]\n        optional :empty_hash, type: Hash\n        optional :empty_hash_two, type: Hash\n        optional :empty_set, type: Set\n        optional :empty_typed_set, type: Set[String]\n      end\n    end\n\n    context 'when params are not built with default class' do\n      it 'returns an object that corresponds with the params class - hashie mash' do\n        subject.params do\n          build_with :hashie_mash\n        end\n        subject.get '/declared' do\n          d = declared(params, include_missing: true)\n          { declared_class: d.class.to_s }\n        end\n\n        get '/declared?first=present'\n        expect(JSON.parse(last_response.body)['declared_class']).to eq('Hashie::Mash')\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/integration/multi_json/json_spec.rb",
    "content": "# frozen_string_literal: true\n\n# grape_entity depends on multi-json and it breaks the test.\ndescribe Grape::Json, if: defined?(MultiJson) && !defined?(Grape::Entity) do\n  subject { described_class }\n\n  it { is_expected.to eq(MultiJson) }\nend\n"
  },
  {
    "path": "spec/integration/multi_xml/xml_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe Grape::Xml, if: defined?(MultiXml) do\n  subject { described_class }\n\n  it { is_expected.to eq(MultiXml) }\nend\n"
  },
  {
    "path": "spec/integration/rails/mounting_spec.rb",
    "content": "# frozen_string_literal: true\n\ndescribe 'Rails', if: defined?(Rails) do\n  context 'rails mounted' do\n    let(:api) do\n      Class.new(Grape::API) do\n        lint!\n        get('/test_grape') { 'rails mounted' }\n      end\n    end\n\n    let(:app) do\n      require 'rails'\n      require 'action_controller/railtie'\n\n      # https://github.com/rails/rails/issues/51784\n      # same error as described if not redefining the following\n      ActiveSupport::Dependencies.autoload_paths = []\n      ActiveSupport::Dependencies.autoload_once_paths = []\n\n      Class.new(Rails::Application) do\n        config.eager_load = false\n        config.load_defaults \"#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}\"\n        config.api_only = true\n        config.consider_all_requests_local = true\n        config.hosts << 'example.org'\n\n        routes.append do\n          mount GrapeApi => '/'\n\n          get 'up', to: lambda { |_env|\n            [200, {}, ['hello world']]\n          }\n        end\n      end\n    end\n\n    before do\n      stub_const('GrapeApi', api)\n      app.initialize!\n    end\n\n    it 'cascades' do\n      get '/test_grape'\n      expect(last_response).to be_successful\n      expect(last_response.body).to eq('rails mounted')\n      get '/up'\n      expect(last_response).to be_successful\n      expect(last_response.body).to eq('hello world')\n    end\n  end\nend\n"
  },
  {
    "path": "spec/integration/rails/railtie_spec.rb",
    "content": "# frozen_string_literal: true\n\nif defined?(Rails)\n  describe Grape::Railtie do\n    describe '.railtie' do\n      subject { test_app.deprecators[:grape] }\n\n      let(:test_app) do\n        # https://github.com/rails/rails/issues/51784\n        # same error as described if not redefining the following\n        ActiveSupport::Dependencies.autoload_paths = []\n        ActiveSupport::Dependencies.autoload_once_paths = []\n\n        Class.new(Rails::Application) do\n          config.eager_load = false\n          config.load_defaults \"#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}\"\n        end\n      end\n\n      before { test_app.initialize! }\n\n      it { is_expected.to be(Grape.deprecator) }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/shared/versioning_examples.rb",
    "content": "# frozen_string_literal: true\n\nshared_examples_for 'versioning' do\n  it 'sets the API version' do\n    subject.format :txt\n    subject.version 'v1', **macro_options\n    subject.get :hello do\n      \"Version: #{request.env[Grape::Env::API_VERSION]}\"\n    end\n    versioned_get '/hello', 'v1', macro_options\n    expect(last_response.body).to eql 'Version: v1'\n  end\n\n  it 'adds the prefix before the API version' do\n    subject.format :txt\n    subject.prefix 'api'\n    subject.version 'v1', **macro_options\n    subject.get :hello do\n      \"Version: #{request.env[Grape::Env::API_VERSION]}\"\n    end\n    versioned_get '/hello', 'v1', macro_options.merge(prefix: 'api')\n    expect(last_response.body).to eql 'Version: v1'\n  end\n\n  it 'is able to specify version as a nesting' do\n    subject.version 'v2', **macro_options\n    subject.get '/awesome' do\n      'Radical'\n    end\n\n    subject.version 'v1', **macro_options do\n      get '/legacy' do\n        'Totally'\n      end\n    end\n\n    versioned_get '/awesome', 'v1', macro_options\n    expect(last_response.status).to be 404\n\n    versioned_get '/awesome', 'v2', macro_options\n    expect(last_response.status).to be 200\n    versioned_get '/legacy', 'v1', macro_options\n    expect(last_response.status).to be 200\n    versioned_get '/legacy', 'v2', macro_options\n    expect(last_response.status).to be 404\n  end\n\n  it 'is able to specify multiple versions' do\n    subject.version 'v1', 'v2', **macro_options\n    subject.get 'awesome' do\n      'I exist'\n    end\n\n    versioned_get '/awesome', 'v1', macro_options\n    expect(last_response.status).to be 200\n    versioned_get '/awesome', 'v2', macro_options\n    expect(last_response.status).to be 200\n    versioned_get '/awesome', 'v3', macro_options\n    expect(last_response.status).to be 404\n  end\n\n  context 'with different versions for the same endpoint' do\n    context 'without a prefix' do\n      it 'allows the same endpoint to be implemented' do\n        subject.format :txt\n        subject.version 'v2', **macro_options\n        subject.get 'version' do\n          request.env[Grape::Env::API_VERSION]\n        end\n\n        subject.version 'v1', **macro_options do\n          get 'version' do\n            \"version #{request.env[Grape::Env::API_VERSION]}\"\n          end\n        end\n\n        versioned_get '/version', 'v2', macro_options\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('v2')\n        versioned_get '/version', 'v1', macro_options\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('version v1')\n      end\n    end\n\n    context 'with a prefix' do\n      it 'allows the same endpoint to be implemented' do\n        subject.format :txt\n        subject.prefix 'api'\n        subject.version 'v2', **macro_options\n        subject.get 'version' do\n          request.env[Grape::Env::API_VERSION]\n        end\n\n        subject.version 'v1', **macro_options do\n          get 'version' do\n            \"version #{request.env[Grape::Env::API_VERSION]}\"\n          end\n        end\n\n        versioned_get '/version', 'v1', macro_options.merge(prefix: subject.prefix)\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('version v1')\n\n        versioned_get '/version', 'v2', macro_options.merge(prefix: subject.prefix)\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('v2')\n      end\n    end\n  end\n\n  context 'with before block defined within a version block' do\n    it 'calls before block that is defined within the version block' do\n      subject.format :txt\n      subject.prefix 'api'\n      subject.version 'v2', **macro_options do\n        before do\n          @output ||= 'v2-'\n        end\n\n        get 'version' do\n          @output += 'version'\n        end\n      end\n\n      subject.version 'v1', **macro_options do\n        before do\n          @output ||= 'v1-'\n        end\n\n        get 'version' do\n          @output += 'version'\n        end\n      end\n\n      versioned_get '/version', 'v1', macro_options.merge(prefix: subject.prefix)\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('v1-version')\n\n      versioned_get '/version', 'v2', macro_options.merge(prefix: subject.prefix)\n      expect(last_response.status).to eq(200)\n      expect(last_response.body).to eq('v2-version')\n    end\n  end\n\n  it 'does not overwrite version parameter with API version' do\n    subject.format :txt\n    subject.version 'v1', **macro_options\n    subject.params { requires :version }\n    subject.get :api_version_with_version_param do\n      params[:version]\n    end\n    versioned_get '/api_version_with_version_param?version=1', 'v1', macro_options\n    expect(last_response.body).to eql '1'\n  end\n\n  context 'with catch-all' do\n    let(:options) { macro_options }\n    let(:v1) do\n      klass = Class.new(Grape::API)\n      klass.version 'v1', **options\n      klass.get 'version' do\n        'v1'\n      end\n      klass\n    end\n    let(:v2) do\n      klass = Class.new(Grape::API)\n      klass.version 'v2', **options\n      klass.get 'version' do\n        'v2'\n      end\n      klass\n    end\n\n    before do\n      subject.format :txt\n\n      subject.mount v1\n      subject.mount v2\n\n      subject.route :any, '*path' do\n        params[:path]\n      end\n    end\n\n    context 'v1' do\n      it 'finds endpoint' do\n        versioned_get '/version', 'v1', macro_options\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('v1')\n      end\n\n      it 'finds catch all' do\n        versioned_get '/whatever', 'v1', macro_options\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to end_with 'whatever'\n      end\n    end\n\n    context 'v2' do\n      it 'finds endpoint' do\n        versioned_get '/version', 'v2', macro_options\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to eq('v2')\n      end\n\n      it 'finds catch all' do\n        versioned_get '/whatever', 'v2', macro_options\n        expect(last_response.status).to eq(200)\n        expect(last_response.body).to end_with 'whatever'\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/spec_helper.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'simplecov'\nrequire 'rubygems'\nrequire 'bundler'\nBundler.require :default, :test\n\nGrape.deprecator.behavior = :raise\n\n%w[support].each do |dir|\n  Dir[\"#{File.dirname(__FILE__)}/#{dir}/**/*.rb\"].each do |file|\n    require file\n  end\nend\n\nGrape.config.lint = true # lint all apis by default\nGrape::Util::Registry.include(Deregister)\n\n# The default value for this setting is true in a standard Rails app,\n# so it should be set to true here as well to reflect that.\nI18n.enforce_available_locales = true\n\nRSpec.configure do |config|\n  config.include Rack::Test::Methods\n  config.include Spec::Support::Helpers\n  config.raise_errors_for_deprecations!\n  config.filter_run_when_matching :focus\n\n  config.before(:all) { Grape::Util::InheritableSetting.reset_global! }\n  config.before { Grape::Util::InheritableSetting.reset_global! }\n\n  # Enable flags like --only-failures and --next-failure\n  config.example_status_persistence_file_path = '.rspec_status'\nend\n"
  },
  {
    "path": "spec/support/basic_auth_encode_helpers.rb",
    "content": "# frozen_string_literal: true\n\nmodule Spec\n  module Support\n    module Helpers\n      def encode_basic_auth(username, password)\n        \"Basic #{Base64.encode64(\"#{username}:#{password}\")}\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/chunked_response.rb",
    "content": "# frozen_string_literal: true\n\n# this is a copy of Rack::Chunked which has been removed in rack > 3.0\n\nclass ChunkedResponse\n  class Body\n    TERM = \"\\r\\n\"\n    TAIL = \"0#{TERM}\".freeze\n\n    # Store the response body to be chunked.\n    def initialize(body)\n      @body = body\n    end\n\n    # For each element yielded by the response body, yield\n    # the element in chunked encoding.\n    def each(&)\n      term = TERM\n      @body.each do |chunk|\n        size = chunk.bytesize\n        next if size == 0\n\n        yield [size.to_s(16), term, chunk.b, term].join\n      end\n      yield TAIL\n      yield_trailers(&)\n      yield term\n    end\n\n    # Close the response body if the response body supports it.\n    def close\n      @body.close if @body.respond_to?(:close)\n    end\n\n    private\n\n    # Do nothing as this class does not support trailer headers.\n    def yield_trailers; end\n  end\n\n  class TrailerBody < Body\n    private\n\n    # Yield strings for each trailer header.\n    def yield_trailers\n      @body.trailers.each_pair do |k, v|\n        yield \"#{k}: #{v}\\r\\n\"\n      end\n    end\n  end\n\n  def initialize(app)\n    @app = app\n  end\n\n  def call(env)\n    status, headers, body = response = @app.call(env)\n\n    if !Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.key?(status.to_i) &&\n       !headers[Rack::CONTENT_LENGTH] &&\n       !headers['Transfer-Encoding']\n\n      headers['Transfer-Encoding'] = 'chunked'\n      response[2] = if headers['trailer']\n                      TrailerBody.new(body)\n                    else\n                      Body.new(body)\n                    end\n    end\n\n    response\n  end\nend\n"
  },
  {
    "path": "spec/support/content_type_helpers.rb",
    "content": "# frozen_string_literal: true\n\nmodule Spec\n  module Support\n    module Helpers\n      %w[put patch post delete].each do |method|\n        define_method :\"#{method}_with_json\" do |uri, params = {}, env = {}, &block|\n          params = params.to_json\n          env['CONTENT_TYPE'] ||= 'application/json'\n          __send__(method, uri, params, env, &block)\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/cookie_jar.rb",
    "content": "# frozen_string_literal: true\n\nrequire 'uri'\nmodule Spec\n  module Support\n    # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie\n    class CookieJar\n      attr_reader :attributes\n\n      def initialize(raw)\n        @attributes = raw.split(/;\\s*/).flat_map.with_index do |attribute, i|\n          attribute, value = attribute.split('=', 2)\n          if i.zero?\n            [['name', attribute], ['value', unescape(value)]]\n          else\n            [[attribute.downcase, parse_value(attribute, value)]]\n          end\n        end.to_h.freeze\n      end\n\n      def to_h\n        @attributes.dup\n      end\n\n      def to_s\n        @attributes.to_s\n      end\n\n      private\n\n      def unescape(value)\n        URI.decode_www_form_component(value, Encoding::UTF_8)\n      end\n\n      def parse_value(attribute, value)\n        case attribute\n        when 'expires'\n          Time.parse(value)\n        when 'max-age'\n          value.to_i\n        when 'secure', 'httponly', 'partitioned'\n          true\n        else\n          unescape(value)\n        end\n      end\n    end\n  end\nend\n\nmodule Rack\n  class MockResponse\n    def cookie_jar\n      @cookie_jar ||= Array(headers[Rack::SET_COOKIE]).flat_map { |h| h.split(\"\\n\") }.map { |c| Spec::Support::CookieJar.new(c).to_h }\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/deprecated_warning_handlers.rb",
    "content": "# frozen_string_literal: true\n\nWarning[:deprecated] = true\n\nmodule DeprecatedWarningHandler\n  class DeprecationWarning < StandardError; end\n\n  DEPRECATION_REGEX = /is deprecated/\n\n  def warn(message)\n    return super unless message.match?(DEPRECATION_REGEX)\n\n    exception = DeprecationWarning.new(message)\n    exception.set_backtrace(caller)\n    raise exception\n  end\nend\n\nWarning.singleton_class.prepend(DeprecatedWarningHandler)\n"
  },
  {
    "path": "spec/support/deregister.rb",
    "content": "# frozen_string_literal: true\n\nmodule Deregister\n  def deregister(key)\n    registry.delete(key)\n  end\nend\n"
  },
  {
    "path": "spec/support/endpoint_faker.rb",
    "content": "# frozen_string_literal: true\n\nmodule Spec\n  module Support\n    class EndpointFaker\n      class FakerAPI < Grape::API\n        get('/')\n      end\n\n      def initialize(app, endpoint = FakerAPI.endpoints.first)\n        @app = app\n        @endpoint = endpoint\n      end\n\n      def call(env)\n        @endpoint.instance_exec do\n          @request = Grape::Request.new(env.dup)\n        end\n\n        @app.call(env.merge(Grape::Env::API_ENDPOINT => @endpoint))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/file_streamer.rb",
    "content": "# frozen_string_literal: true\n\nclass FileStreamer\n  def initialize(file_path)\n    @file_path = file_path\n  end\n\n  def each(&blk)\n    File.open(@file_path, 'rb') do |file|\n      file.each(10, &blk)\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/integer_helpers.rb",
    "content": "# frozen_string_literal: true\n\nmodule Spec\n  module Support\n    module Helpers\n      INTEGER_CLASS_NAME = 0.class.to_s.freeze\n\n      def integer_class_name\n        INTEGER_CLASS_NAME\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "spec/support/versioned_helpers.rb",
    "content": "# frozen_string_literal: true\n\n# Versioning\nmodule Spec\n  module Support\n    module Helpers\n      # Returns the path with options[:version] prefixed if options[:using] is :path.\n      # Returns normal path otherwise.\n      def versioned_path(options)\n        case options[:using]\n        when :path\n          File.join('/', options[:prefix] || '', options[:version], options[:path])\n        when :param, :header, :accept_version_header\n          File.join('/', options[:prefix] || '', options[:path])\n        else\n          raise ArgumentError.new(\"unknown versioning strategy: #{options[:using]}\")\n        end\n      end\n\n      def versioned_headers(options)\n        case options[:using]\n        when :path, :param\n          {}\n        when :header\n          {\n            'HTTP_ACCEPT' => [\n              \"application/vnd.#{options[:vendor]}-#{options[:version]}\",\n              options[:format]\n            ].compact.join('+')\n          }\n        when :accept_version_header\n          {\n            'HTTP_ACCEPT_VERSION' => options[:version].to_s\n          }\n        else\n          raise ArgumentError.new(\"unknown versioning strategy: #{options[:using]}\")\n        end\n      end\n\n      def versioned_get(path, version_name, version_options)\n        path = versioned_path(version_options.merge(version: version_name, path: path))\n        headers = versioned_headers(version_options.merge(version: version_name))\n        params = {}\n        params = { version_options[:parameter] => version_name } if version_options[:using] == :param\n        get path, params, headers\n      end\n    end\n  end\nend\n"
  }
]