From 37741252fabe7feeb1ab3251c79899fe07a11971 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 30 Jun 2023 15:21:45 -0400 Subject: [PATCH] improvement: migrate to tesla instead of finch --- .check.exs | 21 ++ .credo.exs | 184 +++++++++++++++++ .formatter.exs | 2 - .github/CODE_OF_CONDUCT.md | 76 +++++++ .github/CONTRIBUTING.md | 10 + .github/ISSUE_TEMPLATE/bug_report.md | 27 +++ .github/ISSUE_TEMPLATE/proposal.md | 36 ++++ .github/PULL_REQUEST_TEMPLATE.md | 4 + .github/workflows/elixir.yml | 14 ++ FUNDING.yml | 1 + LICENSE | 21 ++ config/config.exs | 16 ++ documentation/.git_keep | 0 lib/data_layer/data_layer.ex | 272 ++++++++----------------- lib/data_layer/info.ex | 17 +- lib/default_tesla.ex | 5 + lib/finch/plug.ex | 15 -- lib/finch/plug/function.ex | 13 -- lib/paginator/continuation_property.ex | 4 +- lib/paginator/paginator.ex | 1 - logos/.gitkeep | 1 + logos/cropped-for-header.png | Bin 0 -> 14460 bytes logos/logo-black-text.png | Bin 0 -> 18327 bytes logos/logo-only.png | Bin 0 -> 24521 bytes logos/logo-white-text.png | Bin 0 -> 18337 bytes logos/small-logo.png | Bin 0 -> 3088 bytes mix.exs | 130 +++++++++++- mix.lock | 18 +- test/hackernews_test.exs | 8 - 29 files changed, 648 insertions(+), 248 deletions(-) create mode 100644 .check.exs create mode 100644 .credo.exs create mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/proposal.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/elixir.yml create mode 100644 FUNDING.yml create mode 100644 LICENSE create mode 100644 documentation/.git_keep create mode 100644 lib/default_tesla.ex delete mode 100644 lib/finch/plug.ex delete mode 100644 lib/finch/plug/function.ex create mode 100644 logos/.gitkeep create mode 100644 logos/cropped-for-header.png create mode 100644 logos/logo-black-text.png create mode 100644 logos/logo-only.png create mode 100644 logos/logo-white-text.png create mode 100644 logos/small-logo.png diff --git a/.check.exs b/.check.exs new file mode 100644 index 0000000..865ff7e --- /dev/null +++ b/.check.exs @@ -0,0 +1,21 @@ +[ + ## all available options with default values (see `mix check` docs for description) + # parallel: true, + # skipped: true, + + ## list of tools (see `mix check` docs for defaults) + tools: [ + ## curated tools may be disabled (e.g. the check for compilation warnings) + # {:compiler, false}, + + ## ...or adjusted (e.g. use one-line formatter for more compact credo output) + # {:credo, "mix credo --format oneline"}, + + {:check_formatter, command: "mix spark.formatter --check"} + + ## custom new tools may be added (mix tasks or arbitrary commands) + # {:my_mix_task, command: "mix release", env: %{"MIX_ENV" => "prod"}}, + # {:my_arbitrary_tool, command: "npm test", cd: "assets"}, + # {:my_arbitrary_script, command: ["my_script", "argument with spaces"], cd: "scripts"} + ] +] diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..a69af8f --- /dev/null +++ b/.credo.exs @@ -0,0 +1,184 @@ +# This file contains the configuration for Credo and you are probably reading +# this after creating it with `mix credo.gen.config`. +# +# If you find anything wrong or unclear in this file, please report an +# issue on GitHub: https://github.com/rrrene/credo/issues +# +%{ + # + # You can have as many configs as you like in the `configs:` field. + configs: [ + %{ + # + # Run any config using `mix credo -C `. If no config name is given + # "default" is used. + # + name: "default", + # + # These are the files included in the analysis: + files: %{ + # + # You can give explicit globs or simply directories. + # In the latter case `**/*.{ex,exs}` will be used. + # + included: [ + "lib/", + "src/", + "test/", + "web/", + "apps/*/lib/", + "apps/*/src/", + "apps/*/test/", + "apps/*/web/" + ], + excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] + }, + # + # Load and configure plugins here: + # + plugins: [], + # + # If you create your own checks, you must specify the source files for + # them here, so they can be loaded by Credo before running the analysis. + # + requires: [], + # + # If you want to enforce a style guide and need a more traditional linting + # experience, you can change `strict` to `true` below: + # + strict: false, + # + # To modify the timeout for parsing files, change this value: + # + parse_timeout: 5000, + # + # If you want to use uncolored output by default, you can change `color` + # to `false` below: + # + color: true, + # + # You can customize the parameters of any check by adding a second element + # to the tuple. + # + # To disable a check put `false` as second element: + # + # {Credo.Check.Design.DuplicatedCode, false} + # + checks: [ + # + ## Consistency Checks + # + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, []}, + + # + ## Design Checks + # + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, false}, + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, [exit_status: 2]}, + {Credo.Check.Design.TagFIXME, []}, + + # + ## Readability Checks + # + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.LargeNumbers, []}, + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, []}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PredicateFunctionNames, []}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, + {Credo.Check.Readability.VariableNames, []}, + + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, false}, + {Credo.Check.Refactor.FunctionArity, [max_arity: 12]}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MapInto, false}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, [max_nesting: 6]}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, + + # + ## Warnings + # + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.LazyLogging, false}, + {Credo.Check.Warning.MixEnv, false}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []}, + {Credo.Check.Warning.UnsafeExec, []}, + + # + # Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`) + + # + # Controversial and experimental checks (opt-in, just replace `false` with `[]`) + # + {Credo.Check.Readability.StrictModuleLayout, false}, + {Credo.Check.Consistency.MultiAliasImportRequireUse, false}, + {Credo.Check.Consistency.UnusedVariableNames, false}, + {Credo.Check.Design.DuplicatedCode, false}, + {Credo.Check.Readability.AliasAs, false}, + {Credo.Check.Readability.MultiAlias, false}, + {Credo.Check.Readability.Specs, false}, + {Credo.Check.Readability.SinglePipe, false}, + {Credo.Check.Readability.WithCustomTaggedTuple, false}, + {Credo.Check.Refactor.ABCSize, false}, + {Credo.Check.Refactor.AppendSingleItem, false}, + {Credo.Check.Refactor.DoubleBooleanNegation, false}, + {Credo.Check.Refactor.ModuleDependencies, false}, + {Credo.Check.Refactor.NegatedIsNil, false}, + {Credo.Check.Refactor.PipeChainStart, false}, + {Credo.Check.Refactor.VariableRebinding, false}, + {Credo.Check.Warning.LeakyEnvironment, false}, + {Credo.Check.Warning.MapGetUnsafePass, false}, + {Credo.Check.Warning.UnsafeToAtom, false} + + # + # Custom checks can be created using `mix credo.gen.check`. + # + ] + } + ] +} diff --git a/.formatter.exs b/.formatter.exs index 01893f4..8305ac6 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -2,7 +2,6 @@ spark_locals_without_parens = [ base: 1, base_entity_path: 1, base_paginator: 1, - before_request: 1, endpoint: 1, endpoint: 2, entity_path: 1, @@ -10,7 +9,6 @@ spark_locals_without_parens = [ field: 2, fields_in: 1, filter_handler: 1, - finch: 1, get_endpoint: 2, get_endpoint: 3, limit_with: 1, diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..7aa6f74 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at zach@zachdaniel.dev. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..1253191 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# Contributing to Ash + +* We have a zero tolerance policy for failure to abide by our code of conduct. It is very standard, but please make sure + you have read it. +* Issues may be opened to propose new ideas, to ask questions, or to file bugs. +* Before working on a feature, please talk to the core team/the rest of the community via a proposal. We are + building something that needs to be cohesive and well thought out across all use cases. Our top priority is + supporting real life use cases like yours, but we have to make sure that we do that in a sustainable way. The + best compromise there is to make sure that discussions are centered around the *use case* for a feature, rather + than the propsed feature itself. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..a1558f3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug, needs review +assignees: '' + +-https://hexdocs.pm/ash_json_api_wrapper-- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +A minimal set of resource definitions and calls that can reproduce the bug. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +** Runtime + - Elixir version + - Erlang version + - OS + - Ash version + - any related extension versions + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/proposal.md b/.github/ISSUE_TEMPLATE/proposal.md new file mode 100644 index 0000000..f347dcb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/proposal.md @@ -0,0 +1,36 @@ +--- +name: Proposal +about: Suggest an idea for this project +title: '' +labels: enhancement, needs review +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Express the feature either with a change to resource syntax, or with a change to the resource interface** + +For example + +```elixir + attributes do + attribute :foo, :integer, bar: 10 # <- Adding `bar` here would cause + end +``` + +Or + +```elixir + Api.read(:resource, bar: 10) # <- Adding `bar` here would cause +``` + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..8c13744 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,4 @@ +### Contributor checklist + +- [ ] Bug fixes include regression tests +- [ ] Features include unit/acceptance tests diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml new file mode 100644 index 0000000..0f6cfe0 --- /dev/null +++ b/.github/workflows/elixir.yml @@ -0,0 +1,14 @@ +name: CI +on: + push: + tags: + - "v*" + branches: [main] + pull_request: + branches: [main] + workflow_call: +jobs: + ash-ci: + uses: ash-project/ash/.github/workflows/ash-ci.yml@main + secrets: + HEX_API_KEY: ${{ secrets.HEX_API_KEY }} diff --git a/FUNDING.yml b/FUNDING.yml new file mode 100644 index 0000000..b98cb0d --- /dev/null +++ b/FUNDING.yml @@ -0,0 +1 @@ +github: zachdaniel \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4eb51a5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Zachary Scott Daniel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/config/config.exs b/config/config.exs index 13568a8..e6cbe63 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,3 +1,19 @@ import Config config :ash, :use_all_identities_in_manage_relationship?, false + +if Mix.env() == :dev do + config :git_ops, + mix_project: AshJsonApiWrapper.MixProject, + changelog_file: "CHANGELOG.md", + repository_url: "https://github.com/ash-project/ash_json_api_wrapper", + # Instructs the tool to manage your mix version in your `mix.exs` file + # See below for more information + manage_mix_version?: true, + # Instructs the tool to manage the version in your README.md + # Pass in `true` to use `"README.md"` or a string to customize + manage_readme_version: [ + "README.md" + ], + version_tag_prefix: "v" +end diff --git a/documentation/.git_keep b/documentation/.git_keep new file mode 100644 index 0000000..e69de29 diff --git a/lib/data_layer/data_layer.ex b/lib/data_layer/data_layer.ex index 47b7fdb..974edcb 100644 --- a/lib/data_layer/data_layer.ex +++ b/lib/data_layer/data_layer.ex @@ -96,15 +96,6 @@ defmodule AshJsonApiWrapper.DataLayer do AshJsonApiWrapper.Paginator.Builtins ], schema: [ - before_request: [ - type: - {:spark_function_behaviour, AshJsonApiWrapper.Finch.Plug, - {AshJsonApiWrapper.Finch.Plug.Function, 2}}, - doc: """ - A function that takes the finch request and returns the finch request. - Will be called just before the request is made for all requests, but before JSON encoding the body and query encoding the query parameters. - """ - ], base_entity_path: [ type: :string, doc: """ @@ -118,17 +109,11 @@ defmodule AshJsonApiWrapper.DataLayer do A module implementing the `AshJSonApiWrapper.Paginator` behaviour, to allow scanning pages when reading. """ ], - finch: [ + tesla: [ type: :atom, - required: true, + default: AshJsonApiWrapper.DefaultTesla, doc: """ - The name used when setting up your finch supervisor in your Application. - - e.g in this example from finch's readme: - - ```elixir - {Finch, name: MyConfiguredFinch <- this value} - ``` + The Tesla module to use. """ ] ] @@ -143,7 +128,6 @@ defmodule AshJsonApiWrapper.DataLayer do defmodule Query do defstruct [ :api, - :request, :context, :headers, :action, @@ -151,6 +135,9 @@ defmodule AshJsonApiWrapper.DataLayer do :offset, :filter, :runtime_filter, + :path, + :query_params, + :body, :sort, :endpoint, :templates, @@ -211,8 +198,8 @@ defmodule AshJsonApiWrapper.DataLayer do def can?(_, _), do: false @impl true - def resource_to_query(resource) do - %Query{request: Finch.build(:get, AshJsonApiWrapper.DataLayer.Info.endpoint_base(resource))} + def resource_to_query(resource, api \\ nil) do + %Query{path: AshJsonApiWrapper.DataLayer.Info.endpoint_base(resource), api: api} end @impl true @@ -226,7 +213,7 @@ defmodule AshJsonApiWrapper.DataLayer do if query.action do case validate_filter(filter, resource, query.action) do {:ok, {endpoint, templates, instructions}, remaining_filter} -> - {instructions, templates} = + {templates, instructions} = if templates && !Enum.empty?(templates) do {templates, instructions} else @@ -280,11 +267,11 @@ defmodule AshJsonApiWrapper.DataLayer do Enum.map(values, &{:set, field, &1}) end - {instructions, templates} + {templates, instructions} end new_query_params = - Enum.reduce(instructions || [], query.request.query || %{}, fn + Enum.reduce(instructions || [], query.query_params || %{}, fn {:set, field, value}, query -> field = field @@ -303,7 +290,7 @@ defmodule AshJsonApiWrapper.DataLayer do | endpoint: endpoint, templates: templates, runtime_filter: remaining_filter, - request: %{query.request | query: new_query_params} + query_params: new_query_params }} {:error, error} -> @@ -341,10 +328,10 @@ defmodule AshJsonApiWrapper.DataLayer do {:ok, %{ query - | request: %{query.request | query: params, headers: headers}, + | query_params: params, + headers: headers, api: query.api, action: action, - headers: headers, context: context }} end @@ -508,18 +495,15 @@ defmodule AshJsonApiWrapper.DataLayer do {with_attrs, changeset.context[:data_layer][:query_params] || %{}} end - request = - :post - |> Finch.build( - endpoint.path || AshJsonApiWrapper.DataLayer.Info.endpoint_base(resource), - [{"Content-Type", "application/json"}, {"Accept", "application/json"}], - body - ) - |> Map.put(:query, params) + path = endpoint.path || AshJsonApiWrapper.DataLayer.Info.endpoint_base(resource) + headers = [{"Content-Type", "application/json"}, {"Accept", "application/json"}] - with request <- request(request, changeset, resource, endpoint.path), - {:ok, %{status: status} = response} when status >= 200 and status < 300 <- - do_request(request, resource), + with {:ok, %{status: status} = response} when status >= 200 and status < 300 <- + AshJsonApiWrapper.DataLayer.Info.tesla(resource).get(path, + body: body, + query: params, + headers: headers + ), {:ok, body} <- Jason.decode(response.body), {:ok, entities} <- get_entities(body, endpoint, resource), {:ok, processed} <- @@ -527,8 +511,9 @@ defmodule AshJsonApiWrapper.DataLayer do {:ok, Enum.at(processed, 0)} else {:ok, %{status: status} = response} -> + # TODO: add method/query params {:error, - "Received status code #{status} in request #{inspect(request)}. Response: #{inspect(response)}"} + "Received status code #{status} from GET #{path}. Response: #{inspect(response)}"} other -> other @@ -567,18 +552,25 @@ defmodule AshJsonApiWrapper.DataLayer do end def run_query(query, resource, overridden?) do + endpoint = + query.endpoint || AshJsonApiWrapper.DataLayer.Info.endpoint(resource, query.action.name) + + query = + if overridden? do + query + else + %{query | path: endpoint.path} + end + if query.templates do query.templates |> Enum.uniq() |> Task.async_stream( fn template -> - query = %{ - query - | request: fill_template(query.request, template), - templates: nil - } - - run_query(query, resource, true) + query + |> fill_template(template) + |> Map.put(:templates, nil) + |> run_query(resource, true) end, timeout: :infinity ) @@ -596,20 +588,10 @@ defmodule AshJsonApiWrapper.DataLayer do end ) else - endpoint = - query.endpoint || AshJsonApiWrapper.DataLayer.Info.endpoint(resource, query.action.name) - - path = - if overridden? do - query.request.path - else - endpoint.path - end - query = if query.limit do if query.offset && query.offset != 0 do - Logger.warn( + Logger.warning( "ash_json_api_wrapper does not support limits with offsets yet, and so they will both be applied after." ) @@ -619,10 +601,7 @@ defmodule AshJsonApiWrapper.DataLayer do {:param, param} -> %{ query - | request: %{ - query.request - | query: Map.put(query.request.query || %{}, param, query.limit) - } + | query_params: Map.put(query.query_params || %{}, param, query.limit) } _ -> @@ -633,18 +612,18 @@ defmodule AshJsonApiWrapper.DataLayer do query end - with request <- request(query.request, query.context, resource, path), - {:ok, %{status: status} = response} when status >= 200 and status < 300 <- - do_request(request, resource), + with {:ok, %{status: status} = response} when status >= 200 and status < 300 <- + make_request(resource, query), {:ok, body} <- Jason.decode(response.body), - {:ok, entities} <- get_entities(body, endpoint, resource, paginate_with: request) do + {:ok, entities} <- get_entities(body, endpoint, resource, paginate_with: query) do entities |> limit_offset(query) |> process_entities(resource, endpoint) else {:ok, %{status: status} = response} -> + # TODO: more info here {:error, - "Received status code #{status} in request #{inspect(query.request)}. Response: #{inspect(response)}"} + "Received status code #{status} from #{query.path}. Response: #{inspect(response)}"} other -> other @@ -672,23 +651,26 @@ defmodule AshJsonApiWrapper.DataLayer do defp do_sort(other, _), do: other - defp fill_template(request, template) do + defp fill_template(query, template) do template |> List.wrap() - |> Enum.reduce(request, fn - {key, replacement}, request -> + |> Enum.reduce(query, fn + {key, replacement}, query -> %{ - request - | path: String.replace(request.path, ":#{key}", to_string(replacement)) + query + | path: String.replace(query.path, ":#{key}", to_string(replacement)) } - {:set, key, value}, request -> + {:set, key, value}, query -> key = key |> List.wrap() |> Enum.map(&to_string/1) - %{request | query: AshJsonApiWrapper.Helpers.put_at_path(request.query, key, value)} + %{ + query + | query_params: AshJsonApiWrapper.Helpers.put_at_path(query.query_params, key, value) + } end) end @@ -707,108 +689,25 @@ defmodule AshJsonApiWrapper.DataLayer do end end - defp request(request, query_or_changeset, resource, path) do - case AshJsonApiWrapper.DataLayer.Info.before_request(resource) do - {module, opts} -> - module.call(Map.put(request, :path, path), query_or_changeset, opts) + defp make_request(resource, query) do + # log_send(path, query) + AshJsonApiWrapper.DataLayer.Info.tesla(resource).get(query.path, + body: query.body, + query: query.query_params + ) - nil -> - request - |> Map.put(:path, path) - end + # |> log_resp(path, query) end - defp do_request(request, resource) do - request - |> encode_query() - |> encode_body() - |> log_send() - |> make_request(AshJsonApiWrapper.DataLayer.Info.finch(resource)) - |> log_resp() - end + # defp log_send(request) do + # Logger.debug("Sending request: #{inspect(request)}") + # request + # end - defp make_request(request, finch) do - case Finch.request(request, finch) do - {:ok, %{status: code, headers: headers} = response} when code >= 300 and code < 400 -> - headers - # some function to pluck headers - |> get_header("location") - |> case do - nil -> - {:ok, response} - - location -> - raise "Following 300+ status code redirects not yet supported, was redirected to #{location}" - end - - other -> - other - end - end - - defp get_header(headers, name) do - Enum.find_value(headers, fn {key, value} -> - if key == name do - value - end - end) - end - - defp log_send(request) do - Logger.debug("Sending request: #{inspect(request)}") - request - end - - defp log_resp(response) do - Logger.debug("Received response: #{inspect(response)}") - response - end - - defp encode_query(%{query: query} = request) when is_map(query) do - %{request | query: do_encode_query(query)} - end - - defp encode_query(request), do: request - - defp do_encode_query(query) do - query - |> sanitize_for_encoding() - |> URI.encode_query() - end - - defp sanitize_for_encoding(value, acc \\ %{}, prefix \\ nil) - - defp sanitize_for_encoding(value, acc, prefix) when is_map(value) do - value - |> Enum.reduce(acc, fn {key, value}, acc -> - new_prefix = - if prefix do - prefix <> "[#{key}]" - else - to_string(key) - end - - sanitize_for_encoding(value, acc, new_prefix) - end) - end - - defp sanitize_for_encoding(value, acc, prefix) when is_list(value) do - value - |> Enum.with_index() - |> Map.new(fn {value, index} -> - {to_string(index), sanitize_for_encoding(value)} - end) - |> sanitize_for_encoding(acc, prefix) - end - - defp sanitize_for_encoding(value, _acc, nil), do: value - defp sanitize_for_encoding(value, acc, prefix), do: Map.put(acc, prefix, value) - - defp encode_body(%{body: body} = request) when is_map(body) do - %{request | body: Jason.encode!(body)} - end - - defp encode_body(request), do: request + # defp log_resp(response) do + # Logger.debug("Received response: #{inspect(response)}") + # response + # end defp process_entities(entities, resource, endpoint) do Enum.reduce_while(entities, {:ok, []}, fn entity, {:ok, entities} -> @@ -916,29 +815,30 @@ defmodule AshJsonApiWrapper.DataLayer do body, %{paginator: {module, opts}} = endpoint, resource, - request, + paginate_with, entity_callback, bodies ) do - case module.continue(body, Enum.at(bodies, 0), request, opts) do + case module.continue(body, Enum.at(bodies, 0), opts) do :halt -> {:ok, bodies} {:ok, instructions} -> - request = apply_instructions(request, instructions) + query = apply_instructions(paginate_with, instructions) - case do_request(request, resource) do + case make_request(resource, query) do {:ok, %{status: status} = response} when status >= 200 and status < 300 -> with {:ok, new_body} <- Jason.decode(response.body), {:ok, entities} <- entity_callback.(new_body) do - get_all_bodies(new_body, endpoint, resource, request, entity_callback, [ + get_all_bodies(new_body, endpoint, resource, paginate_with, entity_callback, [ entities | bodies ]) end {:ok, %{status: status} = response} -> + # TODO: more info {:error, - "Received status code #{status} in request #{inspect(request)}. Response: #{inspect(response)}"} + "Received status code #{status} in #{query.path}. Response: #{inspect(response)}"} {:error, error} -> {:error, error} @@ -946,23 +846,23 @@ defmodule AshJsonApiWrapper.DataLayer do end end - defp apply_instructions(request, instructions) do - request + defp apply_instructions(query, instructions) do + query |> apply_params(instructions) |> apply_headers(instructions) end - defp apply_params(request, %{params: params}) when is_map(params) do - %{request | query: Ash.Helpers.deep_merge_maps(request.query || %{}, params)} + defp apply_params(query, %{params: params}) when is_map(params) do + %{query | query_params: Ash.Helpers.deep_merge_maps(query.query_params || %{}, params)} end - defp apply_params(request, _), do: request + defp apply_params(query, _), do: query - defp apply_headers(request, %{headers: headers}) when is_map(headers) do + defp apply_headers(query, %{headers: headers}) when is_map(headers) do %{ - request + query | headers: - request.headers + query.headers |> Kernel.||(%{}) |> Map.new() |> Map.merge(headers) @@ -970,7 +870,7 @@ defmodule AshJsonApiWrapper.DataLayer do } end - defp apply_headers(request, _), do: request + defp apply_headers(query, _), do: query defp get_field(resource, endpoint, field) do Enum.find(endpoint.fields || [], &(&1.name == field)) || diff --git a/lib/data_layer/info.ex b/lib/data_layer/info.ex index dbf7ca8..4d65293 100644 --- a/lib/data_layer/info.ex +++ b/lib/data_layer/info.ex @@ -8,9 +8,15 @@ defmodule AshJsonApiWrapper.DataLayer.Info do Extension.get_opt(resource, [:json_api_wrapper, :endpoints], :base, nil, false) end - @spec finch(map | Ash.Resource.t()) :: module | nil - def finch(resource) do - Extension.get_opt(resource, [:json_api_wrapper], :finch, nil, false) + @spec tesla(map | Ash.Resource.t()) :: module | nil + def tesla(resource) do + Extension.get_opt( + resource, + [:json_api_wrapper], + :tesla, + AshJsonApiWrapper.DefaultTesla, + false + ) end @spec base_entity_path(map | Ash.Resource.t()) :: String.t() | nil @@ -23,11 +29,6 @@ defmodule AshJsonApiWrapper.DataLayer.Info do Extension.get_opt(resource, [:json_api_wrapper], :base_paginator, nil, false) end - @spec before_request(map | Ash.Resource.t()) :: AshJsonApiWrapper.Finch.Plug.ref() | nil - def before_request(resource) do - Extension.get_opt(resource, [:json_api_wrapper], :before_request, nil) - end - @spec field(map | Ash.Resource.t(), atom) :: AshJsonApiWrapper.Field.t() | nil def field(resource, name) do resource diff --git a/lib/default_tesla.ex b/lib/default_tesla.ex new file mode 100644 index 0000000..d3bd821 --- /dev/null +++ b/lib/default_tesla.ex @@ -0,0 +1,5 @@ +defmodule AshJsonApiWrapper.DefaultTesla do + use Tesla + + plug(Tesla.Middleware.FollowRedirects) +end diff --git a/lib/finch/plug.ex b/lib/finch/plug.ex deleted file mode 100644 index 363a875..0000000 --- a/lib/finch/plug.ex +++ /dev/null @@ -1,15 +0,0 @@ -defmodule AshJsonApiWrapper.Finch.Plug do - defmacro __using__(_) do - quote do - @behaviour AshJsonApiWrapper.Finch.Plug - end - end - - @type ref :: {module, Keyword.t()} - - @callback call( - request :: Finch.Request.t(), - query_or_changeset :: Ash.Changeset.t() | Ash.Query.t(), - opts :: Keyword.t() - ) :: Finch.Request.t() -end diff --git a/lib/finch/plug/function.ex b/lib/finch/plug/function.ex deleted file mode 100644 index 5bbe251..0000000 --- a/lib/finch/plug/function.ex +++ /dev/null @@ -1,13 +0,0 @@ -defmodule AshJsonApiWrapper.Finch.Plug.Function do - @moduledoc "Function implementation handler for `AshJsonApiWrapper.Finch`" - use AshJsonApiWrapper.Finch.Plug - - @impl AshJsonApiWrapper.Finch.Plug - def call(request, query_or_changeset, fun: {m, f, a}) do - apply(m, f, [request, query_or_changeset | a]) - end - - def call(request, query_or_changeset, fun: fun) do - fun.(request, query_or_changeset) - end -end diff --git a/lib/paginator/continuation_property.ex b/lib/paginator/continuation_property.ex index c33ee8e..c5197d0 100644 --- a/lib/paginator/continuation_property.ex +++ b/lib/paginator/continuation_property.ex @@ -1,9 +1,9 @@ defmodule AshJsonApiWrapper.Paginator.ContinuationProperty do use AshJsonApiWrapper.Paginator - def continue(_response, [], _, _), do: :halt + def continue(_response, [], _), do: :halt - def continue(response, _entities, _request, opts) do + def continue(response, _entities, opts) do case ExJSONPath.eval(response, opts[:get]) do {:ok, [value | _]} when not is_nil(value) -> if opts[:header] do diff --git a/lib/paginator/paginator.ex b/lib/paginator/paginator.ex index 18d8d45..74bce6a 100644 --- a/lib/paginator/paginator.ex +++ b/lib/paginator/paginator.ex @@ -14,7 +14,6 @@ defmodule AshJsonApiWrapper.Paginator do @callback continue( response :: term, entities :: [Ash.Resource.record()], - request :: Finch.Request.t(), opts :: Keyword.t() ) :: {:ok, %{optional(:params) => map, optional(:headers) => map}} | :halt end diff --git a/logos/.gitkeep b/logos/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/logos/.gitkeep @@ -0,0 +1 @@ + diff --git a/logos/cropped-for-header.png b/logos/cropped-for-header.png new file mode 100644 index 0000000000000000000000000000000000000000..118c31883b3e56466d0fb4f307a0945d91a8a38b GIT binary patch literal 14460 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 LYBH5?%-;WBnZ4QK literal 0 HcmV?d00001 diff --git a/logos/logo-black-text.png b/logos/logo-black-text.png new file mode 100644 index 0000000000000000000000000000000000000000..536e3d4bbac7b156457fe929acaf2c67b895b0c0 GIT binary patch literal 18327 zcmeIa`9DdCV=aeIiGNJHVm*;v zIa-uxwkYV|M%W*!aH|4-d7?<}`&0jC;>RtW54&MNdG`LP3UUg$I$bi8pu8$Hms3+$_vVi-*FJv9`{yhNSNy?GtE(4;;}M!MyBWpG8w&ry!9Zd3 z<8KKXZbSnsp>BskSIe_U{=0JQ;FZHC(XQFhafBFAcDC85?UB68#s7QCku(`y)Ax^T z-HMo~3Rf+Y*RSRNb1u6xGHzirX=>E-IG(PQ_Zi_w{#(L*kj!k0Nr*=_GndG`df4z0 zL%y8rzc1AlMmug_sx<|^6!oY>aD(&zmUr9q{YHlT`?>QtX}#I&4Wa)@eubMhL|;6_ zAm+4?uiU2@3NXtgzVZBbF}uce3H>|BwZP)Bo;tLqzNgj$G6! zMb{_1zqZBu&ft&kZ9rc~anQ-~P|Onotx$E}A1jqB$hiF;n#rBn^jdclyH<00?XOK@ z29>@Na8Tn)WE5U~VnrmRA+oJf=ijGxHga7Jq26g)VI#-i3Tsz{|_TqQI%4Y8hZQ~)TAk>7)J3!v(zpAdY?B}8R2)o)=2tYzqzyd8H(tG zPn)E{Z-F}*cm&gB988w}Q%PI&t7iJHME6|&8=dvcrlljOSmF3(kW*#U++?!eU%%uB z$xwO%`S(MHC+HY>g@0pJnImHT`V&l35I^~l>Yvred_0?=)1C!gtY@++AZfk-i>UwCf{kCcZbmgdcU9)pEgabpTG_Qu3l;L(s7v)QTiu)7}s0gD=oo^A8G=FAl`mPWkK24C1z7b`rNL(fUV;TyQt)< zqk&u^cG0G;opG0+AE}g+sVAHzWM0vUh=-ZZBxl_|6eP$9Sztmge3-fW%^V2UBbX<_ z2`{(4^BpEV3mrJ4&iR9T@%z^p;TFc#mf&Y#@XVlc&q12pL>_jG;)GZeehk1IX8-4i zQ7&HEC4+k}MPrmW$C8b|7#R391tawqzE~tlE}^z?<()Pu) zoANv>Ic;ZYIt8|1bUVwzjBe?i&R?n`^9NC!Q2y=8uP%;`@=LC|wPwNG2%{eDrkq?X zPRVmmpPI^ZB$gUk7WIxM!}gECc9FQ40-93j1q}NP978(q4fVXRhx{`61W@d0K(U`< zO0ogPrs=}L@DO71HGJ}?9I*5)M0Uj>btMG6ZV0H4N|x{e82R&4d~9l$X5B8!#lxVqBi1pHh-0ZwWp%=NEvmV%Z| z9wwdL^uxT)8^#?WZH7CaInKGje?^CNPn;x?H`}2;YHB+)C6U#agHwAc6P82@Ub*xz zQmA{p$3*yHaam_vlKS4;c_X7e0xSt_+lM^2@STgLA$4o@arB28(F!-za6#|r?9EG4 z&4%}Xxe=u!o5tI&UNc#W?2ByiGjV>uG4(`%4(wt5iau{SK{SD155M{q`tU6WdU3HL zq(rYiF%9NO1See4!9d-MTBK^hFRv|3oIQXcvLl<;^sf!KCWXwLo+36z7w>*twTL^f z%Gr9StRGjd3vs=Q4l1?v`Lk(TTR+k}zrLSct?P+SG5{z2tUBfs$-fjSJ~AGYTjefQp8ViMg#0jYVM?*HwlN@X>gwxVoR6R{H7hw8bpEHi+>Ps|R2Uyth=p2ZL43#F;G!QIRt(jEMh+ce{& zQ#8NEqE~lR+}EN;9ppzu*}T^yxo(<%Cy}4Ttmn?oCJ)VV!D}7RxG~}kc60al0#(s1 zY?AW{e8$hHuziNB@#qOjzB->YEPYJE6skP^{oy~RuCLrLSspnf0(ifzoEtyv5A=%b};*4XXqy&p%CBD%O?bDi41q)U&FdVs}?swJ!Z9*MIhR@(v3V9 zQ&qu3%7+3e*5&j&+%F)>rAwfJ?ab`*&D!Oyo7( zC2)4l5fXo3<(V^_+?->+5Dm2vP_k5U)fMbk0a`G~H_}gs`uy6$0|%pe#(Qo)dKuHm z0k)LEeMqY(VqSKHs@=2G&>MhAR^KZP@D>TZ#EJAzm6V@f13)H4T5O3hCHg8gNPIq@ zY#J)tMDg)yfNCTHJL^S2lxhQyg@MB5BV#kKcKO@~5QbR<{cABIK@d*l1)-B4iHn@V zwNi5aP->@XDut@_ys>4MW%d}&PrtV582aGjOsa}$Wd-wWYk($5x_>MUMtdIPRv^$brhgGqHm)1h#R)0H3Aef{xrOa=K4vL9hv#I8 zgk`HQbr_nlRNa#fc~*sX&#%_+2qmfKQGS|n;%L38#}8-M13;5<^2kya_ViWk7VZgu zkP+Awx@Jv29FJYn2hzYP$+t?I_^`(f+AVwj_ZODosM6AA-||G$d25S2jB0zd^NEu( z{*7K?MHZmZ+^g7W$4_dZgH))p_x#*F3V8Y7do-`^Ipg8+f$+0EVu3H0^|NlOOvIur z{_Z__fTvAiIwHq zxrUF3@DMX#9V;Z?JkQf3I{nYf3!Z^w$Thtw0-La^s`D9|ar@z^ah?{2;E}@FK^<(V z;#7fFOYyaqx^=a!q4^&tNYW}_zD^gA+uQFFJduf}u2ZsBxJ+A9%&4dZ4?L)3%6aN` zv&zmi8VaZ8zo~7N1vLBqWWt|~=X?apYkK~tz>oV~L>}=?YDp>90-&EcGEl)=Sv-XpkdSYak0R{SV59#Y@Sie z^B|COU9x3IP@Q_YTasRv*e0SUyG7jH#XD)*Eh&7COTXid?1;2Sl3tHkDRO_?kl3bz z`v8BE`i#Xz4S~(F(J#iFRw^Wi=7cT0@gK0OO>+k2PqN%Tz)Ze!w9 zrlbtA{^;SbU|!TG%_I{I4_U8pm^(hrcY-}-&gx=EC8@&&qrTucS|r?w(4^6+c6in+Cj&3 zkdeq2>5(gj;|=^^hnBS+a8GuYxO?M|;?1LCq~e(|Kdfe8 z(ggFwCe?mDA=4{*&Mj%H5FOmm;q#lYh_3PSbg16)G6GL(;vas!#ojvTK2WNhS!-QH z_WY+7Z+D;4x!j_hnKgO%iy>!#|3+sq0qgS*Hp-uNHskqtGcn5Yy&(l@^PQC2SYU}P zW#_tmoRR%7gI!h{sd)|l@_i3NA4{|S>fNwY`DOuK^RQQ|wApEiU6JB{+xSzF&lMK1 zF(Nkn3Se=cv!ftj#U5|S8TX-gyypsl4NwRs@Xk6KD-*i;11ACgme@Fmb?qseygHp9Q+0|5GC$TGv=7@V+!DW!Xu`vz*7e8ahjsH)vn|r9ZeirP zKgpqa>1LIY`Nsa~jLSRKq4nF3lT%i2JHfHtZj~$9t|K+KY+WhQ*UFGj;jAb+G&rB+BJ6A^6_qPCxkL@Zq~gV=4Mc z)xJox+V$irPv8FiFYvKn?fAMSmgFYk2pHkA(~#xgxh1oO^q#5?^@l6)`ioVuw#gX@BkHD zu-A-%OU2Ka4@HEw+aZz_MlL53OA->B@WYms9e!OV1q;Vq%^koupYHx%c11)zk<9;w z_LU^~&RK4oeab?ULIpn-ioMgsS(vuG#(xe*BDJH)X+JD*x6Y5ytsWb5Z;;WT>+qw+ z&ATf*N z;B1I~GL70qom~A!U)cfSxXd=sTOLmARh(ZxJK)jH}jfv8EVvvaLh(C zweBJnD0Y{Jr|9P*U%k0n1nuv+VHkoThy`YO;UH}EiHZH0mon{4L#Pq0u0S;$Yuz!b znBJJ@aL&1?B`*gCkp;8*t+nSuc`QxF$6)Px3w0I%=i;oHv5BpOgm3YrJM}Igzd~G4qWm99!C491d zANZyEu!M!8XUDm)e?HXY^#bJ{oj&!vPupN{Ay7)kvcf;?7x8EBylpk^;Zkus8uMiG z?k1lpzjE zrsf(MP@98HpLo0o*`x+=XwQ4CP?q|~--Uu*E9D2$8)#F(DpuThPdg53@~w*eE@{eh9POOS^g!S$T(`GiQ+&{ureBeeTZYKh8zZ8uMDL z5ab0yg;h1~Q8~bDcw!krmt4!XF7$}`66t)M%n7^taYN>7X5md73|X>l?3_!}y9pB?{BVn`+Gr5BsN&h}UJT@2~3+-Y2C`U%qI5oP$WZ^-*;vZ;o&P!kp)+?zYLfmx3EueW1Fda zHRn^Dc5Q}{yEc1KrAiGNgX>!(SfW_qn3xJz+-I@`Da!c4{(@R^1AR+hkd<%Byy#ia z{cp)ID^@&(SZ58?q$&wihx7lIGuVv=L%cL~4ku(H*XK~`>X6~~X6tLDgT#u~wG$_A=QJ!h@$8_M{4K*n zd3dh2(3=ty!l_GL9*=}52W|uCa>)NGw(=NsIN!b!1Z}CS2s=;}k9EnafsUzj{BjjL zz&0Kt-32RY;Y2gc3n^1~YvDHY=p&?Sv3i{2!j&U9a=YfJ?pQ(o3(J{7KLNhC;plO; z(D<*AQvQl#OGdT2SJoFLr=PhXDDI*zAdP$}`Z zdrfeBHnmPdGY$;8KlaQd=yAIa+mi{VHSBgk$~AL${1rwvPw%93%5j&?!lxaJMOt3C zpgd}R@&`&UNnnDLuSr%BC8VckncQB#$yu%LdcXRI@3?zms+;^?SyqDch}FxV-*pDg z9~pTX>pN4sa^}&3^$ew&x)4yhS{_$Q>QuRr+FS=Acd_6tO@H03d`%yWlhT!fQ8{>x-RB$A>s#pRLFK)5YF@6xJL zvt`Uur|6%*TF9rsPIeGI%_BN~xR&$SAf9dpLgi9%@?=MB;DO>@%?x9Y&4w2)4pKS; z<+%2g0}jh&p|Rv-80#PJNeroJOGCE%2q{?VLh}>T=l?bgT7vt(bT!W!Uuyy7_ne6A z(uy-6%nC{#)X0&8L5xH4`G+w~{xy3q+I+?^H6mK%s(vyve7Y$Q%bHZXN2$LdNLOq7 zOhH^&hR5GI|KWZUQ8(_rfPwYixLB#=7m|9-^_KnwUqhx;Ou_zd4qQH>Kut;f8K*KH zGzTMoKYn%G#A^6ob9wFPXB}Ga>XK?18rh#Gp^5yEzAu?J{Q$`1Z<-VCP{KTpDB?1A zYL9nUUHv-Ql#+5lXXTn5t=vM=ioiS+;h;00#{gKm9g}zsP)hV#v&Dq)L-J@|f~u9D`z~qn+A}R=;oP2D?)7v; zIT&A-L?Bh)S#=IA>b0LSpXyN`x6p0M6K!1p+0%mR8S8Jj5MrxmJfCSp?wqc?%st&W zzc+ELp5D_n48P+oyvfM z^Y^I+IT$4$dv~6Im;&n$O3HVLy^dP@FB8wO6N;UzCcoONR*`8R?9*>7KQN-TyG8V` z)hYJQLXGFr+EWmQW^CmG?$aYi#f}0oKyPHW@Qum-l?Cvd(0s99lM5Il-t(R-a$-bCtHYGGP`- zY`wCWZl|DCI0;9eemuM(lGaN9KJ&g!0!gHVp>k(+LdYyeW71Tl5nrp?jv_Ac91^GV zdFDJFy=aCRm`0rR4)oIisM3lb=D6^TL#z^sG|H@yDAX_4ta2F9Qs>!oYJPmVaB2-G z9Q6&Wwl&m!cE@E8G9I}Ptn_3X@HcNM_J|>G4tk9Zk8~V=DC^n|`u(svYU^|dY?Qi! zlPj1V1ob?Ch98M^z4n*Q6mLM_1>G`e&I;IEuoBnsKMIRa@e=^bi96NQ8sy%iga&>K z$u!*CGI`N1-9pqCEhhTP;~oi~)I?6Ox%3aQQ7+1lxw?vl<8>BSRnG4jQwi@^)7me6 zv!~4%5SHDZ2KxRuqhvJ-_3kx8|1u(qZ5~POa-`)13gX<-Bl~_QdF9aTbqW(T|Jbe5 z0bnmGpn}z{Owx;>T#ZHSaI;#}^OUz4*89!Y;M47pgvK?MqqB-u zbFBL{*F1+tf~R#^?XBwL2Fc?}GcEw5s-R6ORuH(Cn@i)^$B-On;5G>t*_G-`IcNz=N*sFO4~0T93gZYwwXJ5Sc{S&RdZy-FDxn1}%{bXI7@IW54!VaNU{F zX>umC)dqp7Rny9w16%R8Q!dnYfZp_z)W;LVv{6}`DSEooL#wM@_b7B(fgV9-_?pP= zUT)CArV>2L)_7!mZy1-Pu9J1fYg9f(gFb+Vfa__lyL9^wXyJwsWAVQ@E9QJ>pli2E z0@9uG6D=;)1{v2jzo2w1@QR-$>PgHiSpNst3nwSzKFmNZ^c}$}UzQudUsAJp-?Cw4L4|M54&bl8ykYT7 zIX5D&&w{&&y4{yGR1$lv?Q)T5(S|VRYwj|M{4BoK3to?ZxO7BV{2FxDPqs}uqHR8; zY6UR|z*rplz#Otbir`L{2`hI<^9DV*u)&#dt^wIL7&SoqhM)9%(U6-Uf5;%z7-&=b zfS&nu?HUZh4lS{+5SUh6o*GhJNuO(KtlBTU9Y&EC9&D|gk{_lanGTJwh(TG6yNmql zUqd;O`vgF;UsdeDKwUFD1e)R`$zCtXZxIkm$Cx2HL*5ZEGY$`VH_Ha>1JcvEVpNXb zbNlV^ZU%cGf1*hOcLv>@ADrDYOg1RUU+dUh^Gf~!>?cb3J>UA8Q%rUvh2tGEer*Uc zw^l_M;qqqJrJ;F0%FN3!76VT?wf1JFIG(UCxexftujbFAsMKM}y1fgaqog>GFi8$q zp#4%yl@4&hKjJB@@3jv&pqzhS_x7ix?Mp!AY0sl&&+n9)qFU`WH)bNIuQY9C#p5VO zRJyQn&Pye0UDx^hgU_igo(I~0p3p?LuDE#nO=63iK7QX<&v-YcVB94YAGJPk-w;0( z60zTBgs?DP9h*|J3?{mzhO%F&U+%SM5^hibaB<*xVp<#Bqr_VVu))OZVgU;UPM9S| z$Y#1GKdsF3?rR+nC{^#5!BeuheCW5QO@1{eUEF?4d23V2ccEl$JX5!I`*>TgwR8x& z#J?iwHVwVz0kkix*EG(}#oZk_Q8r62TV_3&ipt59&AvPqgwJI-As9 zkyR4WvfS{>IBWOmsqK>tkr-p^1mMV9(cWQYGK`Ud=bxM#}G5 zDN~3Lh{1z=*vQ@y?6l7RD$VUOwgm+C109*fa*E zGk$5Gxw%n8ggQkD!gL|%kmO>KEiczCtqXN`DjSN)su-&qR32C17wmFg<}KrRK9 zA{`AM=#?2sdRgcvhNv?D|BA?1)R>09M~21Bpy-5{f_d(grcKXG4f#Z`@#*O(;Gcg? zQ^BFw?z3)T;K3LoKT2&UsTx&2yr59p;*GD#0UdAJZSxzT!3)(Kp>m~d^6hS|Rky3u zc95Ybvt{cNxp`?e5gS$xkg3v|rug1?yQk~VmAB0UoAV0uFBCDc!N?)=aTrABW$l@{ z(x{++nhs2~FL{WrBIE_ef0iqt(x zbAMfGBA_e50o}1;QLphiB20lWps+pvr3ShbU}|5~@}gMs^(3o4xBKjT^;%?96k`as zayd%vImoeol0)kcRaO&&$}*X{eJx&|#d_Ww&jUxB&d>GE*D{W-5YJ1H z{K?&Sgn_QShuOFKy<~sD-Z>pVOZy^ug2}lm?xVy0WJn*&treGP`b#KKbRGYRL9yIh zvD90QXj3t`565mb_npk@LEnMpf0!v!=;mkTy`oufON-GW=ymvXP)xe&$=UgrfD+3M z*W{H{fJENMcr(x|dV2{Lodyg6C>}kTgPo>71lS2OeB(i4DF*>_$SCf(lb^Sr8$5ou zB=`L%?xsCM0qSVvBHqE?oeK9VHzUkFk^#CzkK)IhgD^XZz{a}%k5KM;qZ6=c}b6M>pxEz=TtI zZtN$MPfU}%RC(UWIi!|+ipyyNsbDpj9F^nmUfv?1cy0ZQR^$E`9xu-5!yopm?SwFv zF0@c`m#0nl+})Fmq0~*kSL-%V6lWe-18~f~Ll0^ubDV)qaVTQG;>na(r=qA>4v=BZ zucmp-X-?Cx)E4^`)h0`tZSw?eJre4k{W#<6&0al$^PSIL$zKXSYIu1j&QSx%sUUC_ zp&x>;+{I96ip_%pL7L9B2htBkVl+6p;3sMZ8>@p#kM}IchZPdpM1SeoU&dP@Y=Kvj^YnO*OVQTn&_}c7afP&KA zOfQgJsvy1u&`MZ?g6m*{s{U@IOQc32^8`6H{W@EcIyJSX7ML;#Y zM%kfnrI+?F?kf;Q2<7egZ)KPDWk=}yyR>Wp&oq=w4nOlOh@zgc`!)3-wS>8F#lll# z%qe{Q@qVQ1zqsFwSzS)adi-rLW4~9M!Vi0|O4W%Dxv5>}J466+FSV>ZLg5xBbrB`K z%6#;m$#=~^IQn**|IRsl?z!+Ny9$xK>CJ_i+EecllMJW2NO|IaL`{ipj`w6QI)+y- zH2jki{oB!fAXU)Gv7foI8_?%3@veFpY;rGYbfKJXD+QuEd8cG2t8!ru`O;oV;c19^6UC80nLHLgl8Kr}DQ{eUxGa(YObzKMh#a0qU0xvN9`RIa zA9CICT#Rqr8Rz}+llgN$#pk@r=A|m0toE~NDMS>RdAS=pr&sU3v>x+7r#7R(LFg*u z1Q$Tf>)bjB3`zK{1=4HXTpWr_0CG8b*MvRxI_h`R`Nd~f^S3I#*=c!)t=fdUem+-4 zW;;Z+UZ?mtBuq}MP+8lSQWk3+nT4KN;dSauM|-mOK0Vx~ZZBkE>4(&M+6Ki+_e`+A zR@;NW+++*e-5&#S51;R;US_cMz>fB^utrXC+(P|Mp)3S~@k8B4L+KwSH?NS=ZDB-Imhny8Bx;LCOCn z3XB)@Bs_9x=Vw=?|B?=!i&J_s@SB4Ipz*w|n2XYdwD`Cnij|cSkugnjv&79R*XDlp z?+@ad_;)-+;e_wCKRp*dCw znHR+>r)IWZrilKW9MEn|f>*~s!SDwK2dl_o2v*xe9nhq$LFbr+{9@<{ldQ9Gk;Nw_ zpFW>8>`+aL4g$NrF`*qCet7Bo6_c_)xf$*%=)D?f0cENQ+G1Vig2K+6f!|qqlmiPB zGDzQZpGIrqh-dw2A)u;frDcJ~ymeaek!uGc4~Xl0avEH`)B3mV1mR|STr^=$O9U1O z1_>5n4R=@Q6FV=B%<(2P_<>N2`sKWLJT~HKs;}s&ZQ6Mv5KVkayq2!>Cp6lx$kj6` z{0i#u%+p7LS`Pj2L6gFCY>G`xEJ!-1_yTkH(Unoq^ zXtu4iLxs($R(V!WVn_Adxc$Ds*$LOC$U8ztLtEr2R(m8mw0Ry#pc@dQjQ}}l%V_l+ zm$=O@5)!kXIa$8zde%{Od#uP5k2p-@kDr}F7*YE^!SV`4F99P$f-X^?EOgAVOQa*4 zqsr7J8{7V&k8lC9{(q{>6>5rXt*`^}OVr(ZEBud;3h+)y?CA42<6VD5oj z_}s|_zsVOQhzWWXJ$ImKvXkOEyd#~9m}k!nopYdhUd|c^$-=;J1%uV44w=nM-=ve( z;m!H^#%a?oV+XU54SNmE8Y{*f50geghgl8Umqcyg(lPubx0X5$^hgzrbqh8wVCQ$XGa$R&ZDw0tfMN2=6*3(Ea)}jR-v9JuBzu03f zEwY-wr#7?kVF%iJ>!8XPYeD7;Bf z+ju1`yPh`UM-w9@3>vUeL+ z>^j`$5Okp0i?!aPfy+@_1J%~>_2IxTlzf?nzg2G z_NinG(!7?55;Ums2)+GTttaEP4oE|#0SJ^V0d{x(fiIQOn@E!}1LgELyGx9C7wDQV zVh#qem_!&|g}r;B7J+W*C{lQJCuYdMba(!e{TE_Bmis zZT2;e{u2|$7rXz%O#fA>{YHT&Lq8v2=|VdG)y0j}TpFgDKkmcbiGytB`QLMJ;~3+$ z{#c^@dUM7h)D6273y(~LrmslcbiePiUy{1dw@ur)nVJeby&+wjLJp2Xny|s?PG4IG zFf*LRSxR$)UZBtCmLqqnc7MThzL_%bMWM2^XfG1h$)EsgGK)@T)5j{10m`kWcq_fq z_C@rqf6XA(v^B41^VZ}*i{Da%NE)5=m?Ns1rx22Q5Mxj6ttkTq1LK4~HK*$8!Fa~8 z)yatkU}ElmpIXjkwNz}X$1!wSBsNZ3dM6BAhSy3a*4-x8pA=bIK_nY^)`UOaj;b{C zLoS5y8zh=a2YWT&cJO;TW-=zO7KgGj$Xs)lMg&C7Avuv>G%&6$H9PrJ)5z5mCE#_T zcYd^}sSfUS_S-;T$v#`S`KwtM7I#y}Nbfdv#XSZFY{Zy0W1p;i;AI7A+Pt!OCKIo* zeHLR0!q)5zOAl7(8Rq7IJGoh&XnX>Dl@roY^Z=ANB;$*eXx5;r)J1=&;<+WI9dz@7 z5F#pcA9f2lY z)=$0x&_dgUA1)C*ZA1`IK?PlV`Lb(<-|FNuf9#X|i$$Uk_u;(^f172vByO!N_4$hQ zmYW+T*5ivtFgJ#1L^ZRh{kT2nnC*JGP~ej-e7tBDHsj-!G`bW5ahU_Md(a_usfP0ESNvFzB_8vLxlhN*XGi;XtL>#x4%30){N<|t0()=;VH zu__%7pPV=Gxcp*t z_Kxe!2&-O7K9#5MB0%XJ-$?d|E?f+AL|jN*DAozRm4E*@X??WRAYtw!cTZUBPGwVmN9mRZCsQv9okbaY6{gC3gcI6SaFL< z;iZ|K85j){jMIWYp7>HjjvtwEOdyJ8DK{(NE+sa(o;xBlF1W}-;iIlol|rQvE6v3E zuHGh0ka)+fS$~_nDT^oJtajt4FM;-4Z(FJ*9p>hBoCH^F<&V`km5mEA1jV32Nm$G) zQi`ihAd)uh3kK_DTE;AR<(mB(KuWQo|DqJRm181xPS3p0eyGZXgO;JX_-FY+AW z!B5-UPI){vYwf|$)dS={?NBOsXy2=5y43#3K~JD zqF4_@N6VO3T3M^VrW-aKj#Ymxun8Spp9Y!E$j%q_L8_+6Q|r&Vi#!Qr7O6~mwg|)t zcMi9Jt#+_^QAH}^${efTJ02Z* zDxh~xSxZFk)263T_xbSMP5RnLnv@?Dsax;rw<3G>{&)9s?1q$&KZuT#D`C z--i8^L+HF=rIDu1W60eZb_^pNwR^!`}vHI%E&)7%33b3I&AaX-|RCB3p&?KRSkv6%D>mSl%+ zMK@3$PB-qC3e_{kTl%dHs4h<1_`xz_kZAHx7TH>l?gdZmC#%d*Z%_da{c;=eD$MM8 z(~|l^!k_k@M8(OanQ_8-IM50^L_3%iaYl8Qith)g2S5MBZVW+zaHV4l7@v_Sl-d)k zuvzII{`@S~$Uw$ctU&*l*b0ub3U}eIv!vlt!@(exTz*ACF5E0+SoazH(zvF&q-Iw> zH}>l&>QdL57d@TQ>*zhye0T{NR7zznm)C{#~&a?q{(!vqA3pSuq z(%>^V!2mq36Mpg>4q=E**)^X}BLZ)`OJTT)lUv-iM z)a#+My?u+AU=@r<9kr=7BcdTwChYU4qU@}Vd}lpl#o1<0s@S^Z^0iXPmhN&l`WH3% z220vNcB){}zdB+!3~Mu_KhkuP^;%xy@s?UZ2U&8+c3sG8RSFVF{ejJ;0~omaTj@H{MuKx0ui-d6S3I==J4r^&P3g zg|D9}OQoEUtnpqv<9>VVVrs{%$Y%KQG=c>Iao??g3r-7Ydd_GlRG;EmIX1R$AJ64~ ze-}VH9A87u=t^DP2WqIg5kd0X4*!m98?A_*Jv(( z*b_In-^FIt{gDL;ruY)?O_X*aX5G5sREZY}u8@c0yE<&v;GVU-SG3L5C&vjPm*BJL z_RUle&fZB2uvlXuqwKX`r}|mA-Yj+J<0bw@mvQx8f^kB!L?KvZ>lUOjSZ+2HGn-kE zPfkwkhSl}M#+P+HvhamSKsdx5ShS=hI@S9**TrjSeD$uL;*m}BfTw7O$ta0t&2H8M zqsDXh8jUOFo<$k$4~>|Mm|K9*17ElfXY?*-duBoE3h8Ut^G2F@%yCmY4d$O~ZE&pC z3P#YOrDR`{ow7?3+4p2AOS11OQnpdXzKoI? zMu;(FY@ch;^Yy*mzW>7ecGE92^SG9Co$G$?=YGzkSVMg+I_k63L_|b%+Sk;LiHL|d z3I8ZhflvBIk#R&sJVe^+s-};Kf42oTa%yC3t?ce!ppZ>ZdPH>I`jmC-6E*gXxtKqv z^?81;>OHKty3r;>+_fI|F{XF!2QG;9e9IWgNAc5-@h8kxBv7Qk@GPt3^k ze}DgHf&a6>|IY%88AR5bg@@q=q4>k49d!j_s^>&Uf1PubDlwsvDGjuhr&=%n`&7k% zjA!BUwOG_4EPG=3>))R9DkR5$Pk4!-+}f1*qpbF}R%Nr3QE)e!5=7B!AFm*c46mgU>aJ9k-t)?b zcXYeTv6b<^4OxHFC9^>B>%qc zY$fCA%6xXTk}Rxs*#Fk}C5@=aa`H(!{1VNwf60P{PHxV>ZQ1IRJonOpKNjt(mlgXM zdj8*Od`ToK+Dpp?gCTa)Hr_(vy8pIidm+DrKN=Evnc2rSC@2vx;GE65RJ6|?@^c~COx*hz?}e)ALY z*5mYsUQeHf%RfSP`~UNY^F#8)lT)nsnR<@%Dky{`5uXZ>WKwD=@G$N-NWp-4BA428Y?$Wru4i+)vaL7x<%?LoveHk z<-#$HLr7n>P-I)^*+58N^TSX*-mS$8&Tk*^);~QNaFU0Jx83Bn1TvKAePu%NhTzGV zhI(M1{^a1Geb)TnXPVi)J#h}((vJvCNaCx6+0@%EN_dEdH zq#G|8u=_I1Hg9uZkMo_J>^)UrqKbTAblSe#SF*bQ%_Ty(43XsfNzS+8D|k=t0u6AQ z0_u3+G>1=LFJ0n0{y5wjGK74f7TVBunBjdX?1pY)MbyYR^~nU{z;S%l_)tSS6K4mU zQcw2SYd{41tHzJob>f+|3hApRKbiMkTG)XS2VQ<Nn zQbxJBR?fTdkUC)MaXabVyENAw?9GmCnU4pfNik!kkI=3d6L`wv+R-Wu&w@pt>26h% zNYt3Y>I)|n+%h^|xHA**e#4;_W*^@A8&mS*a>pMV1KV=HO80}J)`hHM#8N(vm!ChL zUW%#{%WBjg$c(*-=;=u_9o8AYc>K&qrZC7dbny&Tb4nJCyNB8Hqw~!VAe-}NGwhdS z!alk+zI_93a^i2{AA$QDsKw+kZ}yW1H(1Lxyo++`K0d=Y;If8f+2vNJ_rSkL{#C7O zjXZHYq4UJonzHS3lfgw^^}ic=MM+rWc#Cu-AfWMdB{OekgF`L=hiv}o!HMgIe`E-Q zm>c(dvtj>|H$9+16O}`bx3fb9;F_wt)qnt8I!)z!xt~~%;gmU!sAyVOb*&{|*;q)> z*5P<%y#VHd!t7@KBoa$zmCfAN?yMZ27qu$UTQg27-Yvr+it~#B_!%rK%f~Y}sg7;4 zL4Y|S6e+W}$ReygDikLxN$$tMbvJ|Y?V;7Om*tX`T$!dA47(NI$I~41)RTz=2*d zW50s{gN$4S0TPK?OJi*C zPdE9GpP8mo>3z+N?Tzujgz5A0J7;}DeQp9sWTg&RAlpx}^FQ-I?Z~B`3j-3v5a+*e ze5*^me7SP7kH=>RYl;^_v9MtG^WJk>(7N9hIo^gB2(zOmL&BnqFqhte@JA>3@BtVF zZPG6+dUT)sPS4KYBt2e{C9u4*$xyWBP+lauO@U48r}nYAPU!%4U_2D9KIEi{X`mgQ zf8=|z2TGOeiG@n0h3yJ5PCsM+_v>6WBW^|`wx8OMW{IERu38_{86FvM_sKPa@2Lmt zxjpM*I*!Ym(L}|Aa;L^}L*A`88)9hV@XuqKQ*>OFIxip3@&ckV7eXAqOM@^!XZQAU ziX02+y#1-|+v>ZvUW|Bmg-9 zmwKl&$IeYmmA@*glQ4K!)xCId&0}Dqs{RJ&{Cqrb*Dy0KF0Q%#4z^Xz^}nBgpycr= zL9uS4;=MY05MLg++(5PrU`&{Cy>WOt>6U-f+mb;Vr_d}uipFCdG@6MuH@I{Cu0%Y_ zF0?i>+HM_odH8R7RmyxNFJ}t?FdrxeL+>WEbeRPU;jU9wClp$%8nM}B4WKR^QvxAM zVnp@>ImXQlCk)&c^Sh&3iumqbEtTF1X595SJlaM2-IPsW+w{k;^iusPQ$D0fk*xh^7d z+`dNU#PX-E5lL<1xyY?Lgc>MJ2AAp`ZM`-il%C2OZFYeAYe)y4Y~EIdtU1pODFe&9 zB~ZU}YHv|_GBbSSE&XEv2pASdU*3nGwkw1wCxgNqj$z~sgNoXIj{ik6ey1%3xkO}8 z;xPV8j6Y{iG@6qLss@fs-KuxWrQY%6$n?p?MbZi+JnQ`f5ZP5uqKlI5orxJ^aoFb@ z`h*|PU^VYfUA}M>*}r5E@!=$=EqKtW45Wd*y{NFBxzve_EedOk^(n zZymuTNq8Bo52dA`3;=N?DLMz`JFigG`m)gaj8TVNy|Bsg4LC>1lhVVt5&q?n0=r2$ z?Yh`CD+ceEwWuepMTG`Pl4}hOTaEV>g z4%d%Sh?qFy02blwWHO}Y`^Nt|*`P=;+vXY1j_>6<+0hW-m((%MXpuPAla=Iaft82^ z=H_1{)MlSi&rE%%0#jRkN?})$RIl zn&w;v%53*qXX>KXm_lni;MOIxu2nCCZ=KK}dSXRBq#&vw;NzZb`(^wKSj>X)@A~zGNa>Np?1|KnDZVI+R5J7qxv8M)zB;)zZ#EE-!lltx1OQ384Qe1f_5K*a zJ9)yQ4#jG^TA7DeK5xAZ4@~=39(?PX|7nf3+QNf{;HY263AV}ulJPkaSkYELzl>RWw~q7N=R9FwY}{jH#YJrL{%T_( zQrm)&Y@4V)ufgEPEuZC8D+1yMKsADEmJ<7q0r@;7&n8?i*1w2LZ?DKdrj9>4O|5@h z{}aWkn4yhl%*5%OXyX!Cj3V>acdFeB(t+=fur(@|=wmfGiKPB6b+y$1RpBC7)fxRu}bEgHx@0-NvxfiT0F6h`m;rpemv+f201`ZwiN-Q${D z$ZdQNdtDL-KVkJBr?d!RE-?B+$0l699$`NFZIXL`OKx>57$1&B7&k9%1;-Em&I$=- z|F8(Fv64$%UuWX!s$s@4#x_Idr7cbt`GN>mH*j&0?(mfWglsiXM^?^&I&e-QN^0L-QPXN{1j;Wh1>_iDtB04e`H8Dz)ieOyzn8#4ddWsYZ^Gll_RjV)Mt zOYg0>16xZ9pL^KUdP~3}8GLywYbKJ8;70rk+0dV~T4t=_{ztNck~eR8QR|y6*g)Nc z*3T{_U}MjsAc-aj9X^b)=zq^JlR?S`D#&LJpHmS_q4!?`5N2Db=VwB^i980L?MzuN9QC z-9~iWt>HYLbCUr117av8k2Q5?QYJ+9p?u2YN{u$9%inwdKEGU4%61d++H$t{;Uz*M z1~M*{Qm*@ILu;2gVx(SGaoz@4KKEcS{p$sABjuqOau#3STtp4Dx+0wbDJn};JjQv> zYW?9+%|kAiNVb!iefFOQt7o|U1kewh@21hAvx{Ic;gAfSNhlYpU1OU%MSH^%0a@S0gHzM5N|NV|pB48FKh5c25V9&ia>pg@T1- zK{1icIFagv<3&KJ@_CUXseqZ!v`@O6Cqa8N`=||Nxp&C3WtIN~Kp0+Vea-05)`kCg zAs0ykJlWNq{iYrt41Y^Yrz0zwati%izjmRi_=}C>1_*~$=JL>yMW)JO-qA+a=5#J$y z4mjd7gB=s)SBf2V=) zcFTFpMdNL7-+7@eU8=~Ec;Bk_($jZ$VwSyJP972W0H=VnqQraG$9Xrm2{PohLG_Ws>1O}I3{eW>a-}2OzE%ElrC2UwoCP8Wm zP9hcfXjc2V7n?@#oFvPd^ezdO;H*RDB$Pd@Jf`w3jGLdkG8`R@yenVjJ6&3o6ziRd z#>c7Y&i)UUiQ}3fEkAZ?T(|BV-PO%VUiWHfwh?$cIH`kXkchQ9@SnC-K9+ z3d_!IL(7|6TuSdzB)Z`B-=hpeI(2-eQ_XBX>mB{QydH-+k8^ACW%9~e==s^}^w&OO zSyc&$P6}9LZUmasWs43)U>zWhq1+d4X2qHfuY~WOBX6UBtb{F`zS}ryW$z7nH2V$m z(=!g=sov&TLvsWuJl-Hw=>$N$d5p^v)D67{i_^EY`?n1`loK**#!)`b}7BnCHBjFKs_aQB5=3&?uDI;Wk0Ii5(}DyGl%v+F7Kl~8 zim^@BEsB{}Ct8I_w&j^Oi2(WP35xYVdAaVbDLYMGFG6+)eOtzksWp5wrQ%2lPL$y}m z#|_4a-SZfjOMH{$5kNenDl)6Y@nwFtjIDT(TR6rA zAl3KdCH#&q{SYg0#Kw4k*R=G^!OL%HhW$ExsCDL`;9uX(21$9{@=Le1fdpnE(^K*L zqZc7(c*Fp!gG#Epml~vBW4MYo8qyJ1)Vt$noTzU%1?#P2#>?Xh|k~1 z(XO&&s-$f!lTCeKM~ph)40&4<(Ll<5_%TceLwtE>*(9BgVA>2^fXEocYCoCcX6Suu zN^nC<($h@)=a;rKy`S741CJ=_8NqLXl#UMBuzH_+Djb-#^Es1Bkz1c`zmt;r?z>p# z@LjO$33973WYa{gD=<@M7=vNk;MGkZ~t@MQjgEXavcdT`^7oQ{?}x+jo?Oz$VTa}S%i|6 znl8u#GMmzu(daw)*PLX7SIFC7kGZZk)DHXdyw)Q8_I2&51M->cUk(rhs0lIPtM$-` zfu1_0CYy1;@OhNfHu3Oh?*-lkS!Ucc2mwvwSR@b{qQZz^ha11UTbp?ME(j=*tpX9| zE#Kl`p13bcSeFBy7H#Zgi122@9V&8%9>{}hph0E(q(MkfqQRhHEPGdIk%eKi+53Hk z1&=Ye=3<`RZbKvO^&Dy-0FHY@9>Oh{y&b`^iK_z+yX?)dX{eXQ?l~f>Wz;XshpJ1$ z>g{3_&SIXP2{p!w>QO^Mh(bd#XX2Y7%dYhVTv3Un0tvx@M!yqlvVUUi=?`C`!IY5A zPQceI2Kloe(ZTAx^9-bd?(AB1c-T2m*~ehNyNdk;13PZj|1r0 zFGas+V?XHyQBK1}@v2(qc{?`lOK5+SIVjdB1?(0$~ zAq?zy*Z4R|vJ@fUu$%{Z!xj0zjzXxU(6rd6!z&JK8>l!B1KWWNF)7!#*ce34!%*Y2 zi4gbH57=kX5RitSiEB9qQZe_&<-;rH?z9;;<2@zYMz(* zWHS*gbYZW(s2o0BfyTCKJl}xE1!w|xM5uMTloOIOLobjWv!Uoa6`vy1aCey)MxVB) z+F9F4RLR<0aeaeLPp#4_+NxbJ;1FP)8oVR?1kFjuTW^GrWWou%W*?1UM~Ewv)a{B1 z-$$I87@Ya@^7GS7he)^ehjZnsO1?3?5uDr>9Qh`l$Ic;7V(bP?cpfu9ujBI2o^Y<( zfQV*I(RW%s`yMKXXJn!$*){?=M6T$;^$F*$h^Q?YJk7OD zm8p`Dt|x*is2jo65ojy6PDfG#Ix{~fg5lbqk$RNy;|O%GIV360&Kf3B)zrf9l*?(- z1kF%ZXNCXHy1e5&NLQMWZNJ~}FRVxcbWyUs+Yd6ka7Kn9`CMM(mKNHNE1_vz*B$Is z=YpO+wuTW5n_iFbk2u&-+%Z}#F-!l1xYfhsk*5Jd6l>JiuWCG9`^-2l4J)y_7Uz*9 z=@vrLmwz6FDDF+>h^ez>ZxzSWL$XqbS1vGPGqFZl5BM{$HaRI%@0Umfe~2Hz@_SUO zV27`wX|YHjuD9mgWW3Twh(KmS*7^5nEHW@wb?y@ni^r=-=btLUo9fR70kVcC2e=fj z#03rplk?$9bc3Lq${AZPS)zG74yHHL%xVDnBr0l+E}VT;8kl=!=g9j!NCMO5r*pL- zxdsT1gT`3W@LdP$fDE#k9jVbGr(3mF3~wkdV_zBWlP`aMc5O#zz~APh&7j1gBH;4R z3um8{F0Ei&&*GWiWlDZKk?8pGB1;QW#p$n z%a;Ctob~dkD@s$?W7fs{PQ<}N(~i+6gJ6z?PG83zy_t;K5uIa=OEI z+0N^6mSv(<}@9Q+pz)Y@?ZGh0_0DuDU|WSM+hvUeXW5Lyz^vr!}JVP)Ss49M-N z1Bl7nlG_HbXJ7AH=pBCZ2l`$~P^pqv*-FCzQl}}k5DdUQ6(`&WFboKiNgeG~kGZ}g zNY0lw;Y`Jg^VxKUNE_nY2D}+@*NQcf>2g>WGj8f>v098Z#4fIIHWK6=Wh^EH!_vkO z@%gHTc29?X9yQf(VB5RU>9#Emcg~zqFep8Eny1HQhi7yiEU}2er|e7}$ImqwfKb2w z=Y-H1M#Xc^BFu2*cTIkYQn0G)uh3gh#I+W?&lGIeHuzh=^1(UO(EJG4Rv}v*ei-Ui zp4YbItOzhU^!LMnT10~>d@j@8Rc>MO=5Q6e{3h*gQLgneUE{H%*w<&Dn z@G2Ab^+!uNl&=?!=$!ff!eXvC_1rT!F|TM*sh<##I1+ax0RLH(ByQuShT+ta`Q<`X_>xVDc*R65Z~w>8K8OU z8zbZaWB^uvYyZ3>L2l#`B#?5vC`yh}W^cG(prENrO6vzU#^8`#+2f&|^Dn95HlOzB zwt*?6naqVr@27v%LSNP8hVQ}Rx3yr-+ z5QV>l6Yr=hJ*|% ziugS>AxLslh3(zP!1@a)kfBQ|U9D$=MzX-pP$Aw2wmPb7!mm1;x@(gMU)l6EXToN* zl{S^_M>!u120@(%E2Y`pKECXuzkJv}@$j{NBqUJiw<~1}gu7ni2>PSQ#1;L|^VK=2 zJRV%y@=_3e)QR#+-#V@NOId*jnGyI#5&E~FgNCCmEz?zHpsAHkmBM*&KmE3U`p&i- z{Ru~KC8)jcj|bEwBmz%pzF@A`oB?FKp3Pnswni_AHV9ZU9oCmiyf!0y6`3*b&{&k- z)03(tKOjxE>OS@BFQoE}h3&$ezo2Tb6*JBsBqliN5JHCVC5(t~iS7!I!P^e0tnf_i zj5MhWw+$J`&#Y*`b?)=$XcZ~R7$RdMo0EM$b=lR3P@bD;?~t|doY~;-_rBu)k2Jvf zF}V$spA!knl_I2bX0{+^Z-tbm7wEs`?IFZe19cywZ>rEpkt=((6GmOEI+u?0;hRIb zt2yj{J{Mlo6l~#8=g+}mcqp_gRza=@PiwP^e_kkp(pe z_wjLpP6ql$(o_3RT|~(d$^7Ezf5Y$LzI^GO*ZDkpv0a(*#-6|l6*I=)>hI8p6*#7g zi0L)oFB?$HmD@HOkI^EJ32Gu5;tSUi5peiSN=R2HdBO}8FW*!(Ps|+YGS{^Een(}2 zQZbPs0H~hJ$beF?M6F+kLGMC_f5dxKbhSx%=1wT-=J@LN9E?r34QMN-xxY6)PEgwj zfHqJ|OdwyJAU#!*`jdYSpC|D)p;>amQ)NJHC~7zRIM{2V8Qx@_T9v6FYaZS(RE%7r zv399v3isAvQ)(o|vGio2oiB5oaFPayFSNsc{xc!?OA{&`tb9Vz))$EiQG4&=ZF`LZ zQe}8dR;jZ%cum)YBal89r@arXgQs1DF~X-+=k}WJD$`CoF#u+gYkT|s1T z`#Nzuv0+v(Td;NYzKmAwrGR40jF2vOj}|CP$z!~BNz=Ce>C@tAnr529AgCylTex@2 zfbTn1&Pc=kCialzC#6~kW92KAvKB2r-zYbggFILfeWGtR-#~~}OArLgut-1>d0}-m zkZ}LWz=~TMi0M2Z78Q#G>07fOUQwdQ>gldIruyFK5RHJJ*BZUWEwhJ65yL#2%2KW$ zPjbUyp73-D#5MB*QvCVN4XsSiaDR~c(GW*qQek8$!`Gu^J(EeF0%>y3z4jY0P!?+k z`tBuBlS&7xK!zPdJ&P&stHV~_BQhx!vs#3Pg#|@sUo{|XI@OR8WD7<@F(wj%bsV}; z#+e4>{NGRyuPd`3?oBuDP-V*qS~?(kVTY34>lv2ClL-k{_8141a?dpTxis|eC(h?o z084R;0owa10Y~75b|mAAoMB%=-;00ne{wzHl#%yGS`w%2Lg{iDGF(!;ow9cEYpc<^ z5~Jo0sjoarnsc+`HSVYQt%H;#BMHg2Q!M`BquCDvf&`z=qzNyTie$ikIc&PmO!kT< z@O1j8i(lI6b_Ir6~2ke(K$p&`k<-#yEJ?9 ziwdyAS=f$_*sB${!_oRXL%7xOd!p-^AN1_wd(P<{z@}AiobIk=@|*aDKv$IIozq$Wq4$ zDU5*yeMG&z#2;S37e+VBr|xV}O*Ug7axNVI<#h)6SN%v=_E7Gt-$xlBU*oA!!ZQY0 z+X5)bF}nS%ifj@;9mj*SjVARE7B5puXc!@7jFszI1XGhc4IwJOs>CeAPP-1TtQPl^ z{1T`3(Q(ua@Qj&%uhLgf+v1z~RXJ_q3J(q9GdTacMJOp4`W;1Y9QMR|n ztTi!oO&1p$q?|mrLY8lq{KbLJ01Y)UaAy$mqZ#$KxnvjhfHs8t8QVlZE$`V##U+J4 z_)W^M5zIQ=IT2fyG?xC`m#ZK&kT>`>8}Z67l)Bo4jksdCnW>T`aBgQP#+9N?+R# zX)92AzJ-*uiOZ}Jrr>V>)M^HNG@04ANdfgCg|M!INwY6JoNo|VF*1JcK3?|vZO(@a z>MaZ9prs+2yO(g0cZZO#*`_6K6{0M2bsWj1L{W4|hq@UK*y(DV)e1 zC^@);mHf@&nTrP)Ou8#4**QQIhU2FFsd zhwt7CAr{~M=3cGQwwmp-G3u}G?CR-`4`7*8ItT`m$VPXH_w~wya&7~?3Wfewx8X`- zuthk1S!s!Mzs%I0-CsGCa_?fZ%0s#J2_p!cn@b8e?$hnC4wp#4Q4wBup@&*lZK^f9+d|DZiG z%JttvvCS#VE($;ne$5vabylP{eL3Ndbg{CS>YP$i=p0A=eLu8y7S;<4)Mc5tsD@)M z7JF3v^&E(@+(CgHm0Wr&WF^ZaaCt7+Z+4iG0o^&>Dc}u}7mh?S%yu`ck8&M`#hG6a z$2&(iMrnoQsgRY#CDcB&2I{~1FJ*HNW#}yI@`~=KXv|&5upzN3UXO$9jzsxHevfC; zRmj?%zkltn?H~+eo7-d8LE{Qxt&xIZ*Q~7m^343{(R&_z_5*EVKE)~*LetvaOCQDH zUj-{#6>jaQy;in_PtxS{-v#UG91*;q2*cpwcysjA<4tc{?;-i1hGp6ne2q<0ng6s#{We${#cG>3M?4`C~ACzl{Dn7RdebT34tx~rfx%fA% z)DLPdte4q!TpdflD%tIq&{9-&z;zYAd`rBBOD-`s_7m>{3ak?@Kl?cxcp*EX_Vb#u zy`cGPMAY2xhE1tP7S{lUxA;Yw(keN$P;ym9G{k9*Zr0n)F8u!5Ld+iB>pyQVX^q^< z6W(mdlpgXYTTKYY1@6DF5B_EPz)C=nJ{+=5PoRQdKZB+kLrRi+y4mKr`NHPuLy*R? zEva`77xFda4dz-W>>u>0^u}F(VYJF?v);6^F#gM+Wa#6a>OykWR(fKFAI{<@}t|-6tR?)jfQ2ySsP> zw7&>XeOAXzKqLM>XDGD+U9S!M!In``L8N@Rd)j;Dk4*j)9M*;?^P(!gN zxh%KT*2TM^7!_)2FisGsZP*FYH1lK7w==*DxixNdZp_WZ{>VGiQlu zVL?qAou_eG@ZyA@t=9Xr1);97dlmOS9_*w$Wd8(N_Z4NaBu-d&FgmWOgZk& zZCE1;c^*r-UjxC*Utb8krOdXr8YDI+`to)vixhf=beZ%+Oj@n$2D|a>x9BbPG?4A07dCE6@gI&~D=I|B=+d^yZCh=Qoza)xU>*#EtHI43|Gb-B! zD7pD-fRTu$38%lQ6F83;RC-{mdg*~DXV(rW>}Kj7MahpcqEexxywde&inu@hZN!~j zmu2`G&#)tQzS5le$0ZNNU*FShvh8|hgY$z6l)Q{A*#NzhX;>0lV1CGpam>mP3~y&p z`DA0n&q)&8fza~dD#?5++_S}8%s}Y#HH9`C-$*47+u2-Do_jr}ua``iQqGG!# z_iYB6-TX|xle7MyMds4)CuoPPOOR}9k+R|t1IYH~0bF|V+2OUatn$VcU2Pt7fX%2E z}0eC0{POwX2|v-Yc_eRtCj#J>C#~H0wNto4>67QzQn#ZGtOJVR!OOE)3z2 z)G02N9b;BOl)Ui0&ZQPp#5WHyKHviqR8bM`UMFa5Z{y zYj1CCH7~f6q9rU3M-lCVfGO236kp^(l)s$M_nW{3BYZf(4VG{{i6M#+qz8hGF=z!W*)j{OylbM*$7PdsBg9tv&G+u66@uv z(XR3rQ0G;X?nVB=fv*7<5&`MUz#DsZ?YG*?`|FZqFm~4ZiSva9LZAGs#al3)BH13b zBYXRTGGH!b+`be``7(Q05cYEN@Qm@B{wMDs9UnoYv*4V+9D$+n9s2Kgn;O$*I zeb2bgq{t=y@W2O_V!)G=Gj@mbBdt2F$Vu0Cx(H*>gBBijOtbN+Plk$jNalP3qh%Pv zz2mi%3 zn#2pOz1Oadiult)+bn~e-+eq7u;(Cb1=3k-X*Nm3{zfblV#r}cPHjvcoE)6DsBF72 zyn~8TC^(Gv-{&(`mOkWy1xb(133nNwEa?xNlB0QyAD}7%?{qzUmgJl2OS^AkT%YNs z4>hx?4!!ejQyU8ZI#P9tcQrTdw*04B2JHDE29R4OeLvL^74&4{CfVRhH?{F@SB>q@ z)jcW`r}mG(O0Y3|c@1bLj3#!Cx68MQ!n%#C6`kW?EuVo#yP^^vPiFsO;(mn+sk*t} z-J}J7-;L8Le(t5Q+CE}%2ehBg28&K;0w^0J8#2thC+3m$p@7mKS32PlxkSVE4*tA*e-G_)DPZMmS^03XFBRBI3T`}_eU8f}e-_ef?f1CgS?4YV?ufs`L{EMP|7kVP@V$;=C`p^S%uu? zdU6xSd-0%kME*6s{qXFA0np@CF6=1o?DQ)e>z>&+_pEi+zL-!6wA%d?HL+~`Y5VYv zc=(MNx<9RHQ~D14QLzY^A~|yC9^lWxuLp9E8o7rf*V|U} z=BsMzfQ)E+0rV(|1ryQSz)aouXbxVz6$bTtJ-`cj1K;{ZKRo;4E7mS(FO>CS>EP38 zvkS}jxcE2Wu*J2-ST$(3)y)0(fKd(lc)CB$>G7<4Ff&b-+dyfobVKNHS#w-*%~iSu zuuDgVPR%tUP{>}Ox4c-V3k2Q3IF1M}u3-3fwvva54e7lemBp&xBW#Yc=}ILbyNy{q zsfS3=?Dab<(S8Oe=@5YtY}*i;K_V;k4ZQB*yU+S*7wj6(DOGIy$}G>6w-9QHTw#69aM}IN`<$~H79yT0u zHPX*d4m;_^KIj3qcuhIN)V8Y@upn`5XO)rcJG?~hS|JgrjM7i6x*n$73&9#HzttS@ zFBsat2nvGY0grF_<&5c&s`XY}_|x6g6_Qcm3bfj`6m;q==_6vz^@6?p`}&A!9cs=; z%`CsH4@8fP1Omc90Y+xUibWh8KFH;U1+|`6-3*t0=)P+wkR9hO>C%yLdcVZ8Bha=t zRSG@ay=FcriqO=*yga*!1>H5ImP~g<3*5GmT-9~)4r=;D1$(v*BqobuR|aA`_wTQh z8g!J*U*U)!<0A9Q&?eJ;7!DxKSoBt2K*y&Rkj$V4VT-+**EJ&SnA zzvHVmTB2sJEI4I!M98>8qKXvSVbwe*TN7x}*H-ooD7%MdQ8KVIlRP!IYS=u|t_L#` z>OIDrV*9ro0ns1k&I(aJYTW7Y32CyKPOKwr2o%9?n-{3~#NLM?vguR#<~A<}&rTp` z?Js}#S7#D*TBWGJpdQ)yHYMC7^pgT|)s?P^x}ZbY`8^-lv00T8CCuewnY-KjiAV=EO`-zRm()u2OIw*(>;@T!_xT)O=_P0q5fS$z!v6~} ztKBAEYQ?pky-Tf*F_8TW!#iB$4Y}2uesHPt4vVIBDe`JA+G|D&8?c$&_Dcq|mX zX^g9e`bADeIVuxGPKTSuhq^c^3yxc6Mg$$H);QiF!g=xvOUV?I3|aUt1;V_w-@XXj z)K)$79GyP)1kxdPzf-rp^vdC?Yf^Lhi$X3uhhCO+u#iWn=|v-YO$7`2OxL81!S6ez z5cwY^NN(p5%~bQJ0Cm?!sIOkcZ7`B-e|k-QIY%$YZ>!7#l#H`zK$*?IhyT;2%*<<= zl=UyusnqKd9K(&Yeu1G0hWo|0_xmYaWSoI!wS>PtlkwEmq4@d6Ge$f{#8Ez-n}kpw zxX5O|nsasf3MUCV?KRD1|CoTeYsm?N{KnjaBlJ(}Iwj=rFBngK{q<$~VGuWgz9zU^ zMj4KHEljT`m{q~YM%?G?FPM<6*1cYK$0gAuyI$i`>$T8Xtm;d4om+=MKu2qtS=jp8 z<$);B(kf!*@l~ULrYh6Lhbzf4sfAt_K3j73tyx=f1o3x)pN4On=U? zfF2AK%=H)3Ynn?L6v~2Enidt#&U%6_qt^Y-R_PRci!r&@)Ro<7hlZuK%z=kJ{<>8P zv`L4bTAQ{P_6qPd2bN-P7TG`tv+fNs=We><_9O!q!*P9~0am2MCN@ME=RKeV zWVT*0LrcwSJdxv#(x&h3Z-as0H`&4P9iQ*|U+-RPs<&^?!k^`x;Nmg|?L2j5^f|4_ zys^Es`)8ShWlGD{TX6ml2`${e3b8S!SmG3>2=Wgk%`h|DqT%KYA{Y$2?dt=RI`qQk%7X-tGLj9JWMEiDC znPnBL4(zzxC%yh4$?dRW%M3XYb-=QZC4QN?8VyNH6XMwd?PAD-Q^xIsn{a!sB)sXN z%lG9zkJJwOk?rJ_SP<5;`lcvy>OAH9Yt;i87GKAUNiM?)7TaVqvyT%~{Qh@oQAd}F zmmRhA5dBJk9`wf8=S9x9I4bfiu&4q`40O1DLtk; z;GH9=AJ5Vcx*wg|>q9{v4RW4DTU8}4{0j-MDI-?&%6eY?*)ryp?!;QVU5>>D$kc39 z(k0CbG%Wc^Z3ix_#t#lu2lq3>EN@cVu-diV@|bojB; zWqe()0q^nt0MCwNiN4}~wPk%7wx1BJ7wyRLTA_q2WZ!pKx<3sxGBnjHgxjVXHptk4 zB&}E$(rCk}q{{7|_Ei-Pl6?y2!>G{ATVj;!mQ*A*f$**x#vooLC$HN|E4utRgw zI)|2zT%h3gYWutWK2CxJ{nL3<2hBBgw*t&$Y4ixW)oPk|#kD&~m5OJhA%%~!mgAD+ ze&vGC?v^vzvxj-Frj)Bb)E=V3r<4GNZqfWfYHh|hv*XV-)b}&9^@aZ?F8PzF+q-R? zfy4eU8bBg7@F)SxroH?k*PnNVd^HP#En{3gtuj&p5kK4rr`QbUY)zXR!ngxz(A(B| zb4{30d@?9xb^5{OA%7P`Emg6;4BBN!zA+=6 z_1Ph&%K}n{q@S+G?xa>j4Ti;*pIPJ_EKOH&U+8+MU2KRkU4(a^S}~}oI^3}j_P6vP z^l@Jdip~?`>EB+XlXA`^mTIuh36p?RT6O$wK=p4TPy`Z)8Pj+d+6zj>f~;4+I0z3` zJXzWsWB4fu{=Wg+jd*OpzK=Slo^QO-ffI(CZ)^w2qNG)Z(%D9OyWVy;d&^)p7 zj>JSU?a9aR$+cqJ;P!`iJaLP{ceBo$fFk@*-cy#o z5E$I#qd%j88d-;bdsLsVsTkOLmM9jL>j}*S zVkbv?$B>y*Doa?WN{4K&Nf?vZb=*OVlS`?QcPW=HV1j>TQS!sv*l8{%z6e3e6Q8Nl$+E{T}L5(G%{?6sw;aw>A;IcK9rt2u$qH z32Upg_sJ<^Y~uMWzNWKptheusyfM#mnQyf&qoecHE7;@RRAw3z)Awu(cvet zW@UGK$|pk=*6vZRKl*_WL%+5b*B{O5Aq5QsuEM5*gE~WM>t;-1B-=<~Hue|rZ=_Cw z#hT?vAjQZ%E6n@Gut_zV9?Uel!6)xF& z*0b+9k}hthe;k6vB$jD;S6}D%nenf=^}2t$zUyR?x!*4@g%RwIp~Gknb7%U}PK&(! z-4ecg>G3zFa#yaSz5PZ94(PIXKUz(D!^; zAno;HKsq1?{I3&+AMl@yEv#;hY7ykba6d$A@z?-%a`Y5^w!5j>5Gk59OPCk582*uV zFGZBY!j+NG)N6i{BVt|VuI-FzyoA=MG1#%r3#`EISz)9NQPppy>dR`A-T$b1Zc$>$Zl;`D3q) zqz3&sPWzjp_r39jcpdg+5Ib zgX(s^PjrhH=xrMnj6kvLoL`nYzo(j})sD&lg~x~yl`0JCva zD;sNfQe6KP|bgA16*zbrsmDv>=tO~!Bi{S+X6{!YCAubp%M zXZnBtxDu^{P!44w6d~l0$>ucF%K22}P{^4ka~hV&snB@mY)ItLVb14M2Zw}B5izH7 z7?aZsZ8Q76yx)Jq_t(#F`)#-9uGj0f>-o6u_lxnAZ$u;Qojg|c++~!<3UbZou&-I* zChn10+ju_;v& zfn>~~rj4AS0Mp@%w`SKT56)Htmw|^ZK#itg>)NyE((El4ZK-`;6hrZ3UhjD#v}KO@ z))}7zwJ1FCZ@BCZ4iZakt|{I&t!Pl1|G~*?DnJW$|8?Nq9XH3D8r%9Ydp4Mjwn3c> z?e6=-h1YOa_;&vBsuYxwU2X0kzXKL zAbt&ow%DhEDkFVQB~_` ze5|&Asqu-2VsK*~>wV3mZ@W9lbzA~C1;YVqZKNk!IY_E^e3y*7`}8>=B#3Q6F*xP)7O0s_o+wGD3rO1s0ZDoqJnajLYeLgP@> zCYidkb9E}PA(1hl%&986J}S8eS^gYx_|W!)NZV?&jB1e|_9djz=VzT5g!i1oHsrHz zC{BYtBI-y+Z*Fn>*}h@CiNL0DgUP@qx0`%-k6A61Z0jh98twju8ytm^s4E8}Ad(f9#7urbS;ml0;2@-a ztofw%8GEF8%>&!*Rk`$1(8k0{(g!Gg0G1o5Ce9PPH)4#bFT_`E0|r+ejnxjesAwuD z>bPpB1O%J|G_K~x6UNYenUPpqLvy=7lI-8`H`i;ePFisoS2--&(gi66v373tj>xto zW0l0r*9!+jB!4eWPsQ83b@CfmCAc!8g{3S<-6$Q;J=o1 zHA}Xb#ywndN72z|Bo)od*!(Xr%nnXdITBW8X&?V;cW4DNh64iv*;_Y{I+CvpY%T2o z7ofP@DTcaLAA=V=1sIQ?E{qfcd@AWjXWxilAc~4h`Ip4s8urz8VyS_;!dHT~I-8P77ar3llxp1xh7l%}ZOHMw;bYiO&Me=&OZR^Ksjh zr0D{?ePq`Wvy6mCe7%J&RQgPtOHCNg$3u0M^coS7 zV8$^M58WBxOLt|0EpxxBK^@NDwkCBiVek_35T?*QhJ<_TwTL0w!D1Y$ir;`@lCV1+ zMBbFUAhaJ+Mll2FzxC{`D4WQ+>b33>?m%n!G63T1TJB;Tpi5$X8xG44tycTQ58oIiD!f;VX{ZO!}4l|9) zrd_C{nSBVYxmP_R1($FbT6=+3o09{83(@VU3K=3l@OCt$$tliBEdq4{IGe!!3)%1r z_RG;Fh+q13r-Tsc3a#3ntRu9sh|gnfDMGUgAJiwEP^(HNJv_L)ERm{J)nr7hD0j8X z&tj{Bqpw|q-4XPb_Cx8ggM!4drGdWDU+8l1_)nS3D{W^}I|_^12iL8j7-!((r1nq( zqC5>5k34$2CDElDIP3n@AU&LY%0*_EHv9d)W8Cl4sf>o_xRxOf`;6&$Akz$`myq&Q z`_&g=l6CH$z7yt=9x7k7vdjaaf|IS0708(>Pllcfc93mO!hAXj&SAY-*vop=zGm1| zYJswR`{&A4^uElxF~Kb4n>HE`U3{*rkXg4f?8d)g zdDD|WKeba8@So&F@tzl;wQ@DLY#!UNvoA>K^KaN01$u#TigK9 zRwyf1w5$TLVm_9nf3xi*+;>_3u7emUWYH3sHP+*rd{S{+5zRlX8h1?3KG5W#kEfeedAK+4nau!(Kgo5l?6bIItW4aM2bYN0ai--Fiaew5{_VF>(5?Ii9dro0Kp7t0yn>Yl<+et=@TcW^g z*0JPal{>BO!ctjz5IM-+pV(BPTfC>{t8O~>ZPWDYmB-flv^R=>iLUvXC4=T3Kr4=` z>4@12x7w>)=+At$@JH^(I|MIx0Dstk>)iPlHmZur$qJA8c<0AuVza6pTdLgv!5^i$ zx(=fEztbz5auxw!AzO8=75~S>0SAoWmUj~jzt*){WR`Pj2JJB~0=u|4-*ko#hyZGo z62AXY9g^qOpJP|L`3^J1b}zN%!@LReU06X!v!alYQ+&YWKs(^gn0PMkHXe2yK2d<` zaPV#kzR(AdcnWzVVi)^PPmn5VFwLxQpF+L^Z2f>9U<<{4Z?AWx!jM6Ps3KT&cP_7* zP7$@W#+N0ns6W>sn##u2@y1f+H`e~0v&UkEo07W8Ef$^|uU7w~c&=`nEQjsh$h83; z%dpZP7V#EeJjQWOOeUp<-TXHXH5ZYq|Ef^?a_hFuvaxgu^~qIko@Zz~R~XKG`Q4L5?}U^_0xDQ1?w|5UBRsNey;YHWM%1u$=(R;9 zgR^er*Lj_I&C?Hdr4cK56DJwQH`TU6be zI!a{v40-$mm}N2w8`PHG>CPSyZXotKnDb2GTFVU$nT7i$DJuQ1v6N&Nz`4S-+QnM} zY2Y*ffikUz><-UJ;)tR0cfdkXOoNBe5>&+iozJv24&w8O;&vKAuXBhtY>G{SNYunr_vx*9S=uYjLBhRO*SN&Bc$2LjGVZa|T_Z!jd5@ z=Z(56d=g}UXCncWq-k2-clQfCEnMsRWVSK&rkGSm`#3eHcR6$6G`NkEk8XAX^8w;< z2wrY11uV*Vog%8@zw=U^8dlT3Z*SZnQF5=O-yxo{oVu=8`m$l3GmLI7L8#lR0+59@ zN!pHLx`LII>ApB1F?+oNj+>tzy{?#q&XEN$A_e<_$S3ve5)Y7@t6JGv=$K7fRP)kL*QitLI?}6io!p#s9>j0Up@k7Ua%UTkO$xu;|<_@ZmJjo^NUQ|C9OKa){?Liw& z#>`5-yPiSs;hC;a8bEg%x1VP5@5VJdU`T5DP9>G*d1hu~jzI(qcddPxOKBx%+v7$0 zUbn{3J>743yeAP!5D@Z zz+f+on-w^SRPida7&71InBHu15poAKDZqbGMsVGj4eQ$+7m*on%=vLwFEWb@X*(`P z$rc|Py))0RF%Ds6dHx9 zs(a2uR~)a4b<}6ymR1eT@4pN>4pcR7bF%Ts#-$j4lgOI+{T?%`(b7|S8mh|ISe}{g zis$?Nsh?59x0Xn>Ula)a#{S99yPr4%=^t7FNJ+JGB@>Ys%}b4bn2ibp4Yce6ih)Y2 zew!xjA;ht8jJmty+{*t~6Sj75Xsv&jJxypMT|N%C;MnqLPy1<@;T+?eqHPo0$DtH( zUc|*n@hq27sV+zul;$qe&*q4+pSC-)!(M6wMmi-`$>V~ST%1j z_qVN}&;lcKDfg}MuNsoPDwAIVdllOu9)XVHvdJP(3Rs-b6R{bTK09wjdAJWNfKNP? zJU-FRe@^!?$AN=~Kz#rE@V_77|NkVA`9e8^0;mrz-(!3NR`he+Ff=v5!|z4^AJ-Vh AyZ`_I literal 0 HcmV?d00001 diff --git a/logos/logo-white-text.png b/logos/logo-white-text.png new file mode 100644 index 0000000000000000000000000000000000000000..91800742936c4092a50b2b528d7dc8dafea0bca7 GIT binary patch literal 18337 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 literal 0 HcmV?d00001 diff --git a/logos/small-logo.png b/logos/small-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9fc9aa1801a2a5d20a6525f31fe7a619fb39f044 GIT binary patch literal 3088 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 D^yu%Z literal 0 HcmV?d00001 diff --git a/mix.exs b/mix.exs index d6baf3d..ab9c7b9 100644 --- a/mix.exs +++ b/mix.exs @@ -1,14 +1,33 @@ defmodule AshJsonApiWrapper.MixProject do use Mix.Project + @description """ + A data layer for building resources backed by external JSON APIs + """ + + @version "0.1.0" + def project do [ app: :ash_json_api_wrapper, - version: "0.1.0", + version: @version, + consolidate_protocols: Mix.env() != :test, elixir: "~> 1.12", aliases: aliases(), start_permanent: Mix.env() == :prod, - deps: deps() + deps: deps(), + package: package(), + elixirc_paths: elixirc_paths(Mix.env()), + dialyzer: [plt_add_apps: [:ash]], + test_coverage: [tool: ExCoveralls], + preferred_cli_env: [ + coveralls: :test, + "coveralls.github": :test + ], + docs: docs(), + description: @description, + source_url: "https://github.com/ash-project/ash_json_api", + homepage_url: "https://github.com/ash-project/ash_json_api" ] end @@ -19,17 +38,103 @@ defmodule AshJsonApiWrapper.MixProject do ] end + defp extras() do + "documentation/**/*.md" + |> Path.wildcard() + |> Enum.map(fn path -> + title = + path + |> Path.basename(".md") + |> String.split(~r/[-_]/) + |> Enum.map(&String.capitalize/1) + |> Enum.join(" ") + |> case do + "F A Q" -> + "FAQ" + + other -> + other + end + + {String.to_atom(path), + [ + title: title + ]} + end) + end + + defp groups_for_extras() do + "documentation/*" + |> Path.wildcard() + |> Enum.map(fn folder -> + name = + folder + |> Path.basename() + |> String.split(~r/[-_]/) + |> Enum.map(&String.capitalize/1) + |> Enum.join(" ") + + {name, folder |> Path.join("**") |> Path.wildcard()} + end) + end + + defp docs do + [ + main: "AshJsonApiWrapper", + source_ref: "v#{@version}", + logo: "logos/small-logo.png", + extra_section: "GUIDES", + spark: [ + extensions: [ + %{ + module: AshJsonApiWrapper.DataLayer, + name: "AshJsonApiWrapper", + target: "Ash.Resource", + type: "DataLayer" + } + ] + ], + extras: extras(), + groups_for_extras: groups_for_extras(), + groups_for_modules: [ + AshJsonApiWrapper: [ + AshJsonApiWrapper, + AshJsonApiWrapper.DataLayer + ], + Introspection: [ + AshJsonApiWrapper.DataLayer.Info + ], + Internals: ~r/.*/ + ] + ] + end + defp aliases do [ + sobelow: "sobelow --skip", + credo: "credo --strict", + docs: ["docs", "ash.replace_doc_links"], "spark.formatter": "spark.formatter --extensions AshJsonApiWrapper.DataLayer" ] end + defp package do + [ + name: :ash_json_api_wrapper, + licenses: ["MIT"], + files: ~w(lib .formatter.exs mix.exs README* LICENSE* + CHANGELOG* documentation), + links: %{ + GitHub: "https://github.com/ash-project/ash_json_api_wrapper" + } + ] + end + # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ash, "~> 2.5"}, - {:finch, "~> 0.14"}, + {:ash, ash_version("2.11.0-rc.3")}, + {:tesla, "~> 1.7"}, {:exjsonpath, "~> 0.1"}, # Dev/Test dependencies {:ex_doc, "~> 0.22", only: :dev, runtime: false}, @@ -43,4 +148,21 @@ defmodule AshJsonApiWrapper.MixProject do {:parse_trans, "3.3.0", only: [:dev, :test], override: true} ] end + + defp ash_version(default_version) do + case System.get_env("ASH_VERSION") do + nil -> default_version + "local" -> [path: "../ash"] + "main" -> [git: "https://github.com/ash-project/ash.git"] + version -> "~> #{version}" + end + end + + defp elixirc_paths(:test) do + elixirc_paths(:dev) ++ ["test/support"] + end + + defp elixirc_paths(_) do + ["lib"] + end end diff --git a/mix.lock b/mix.lock index ca7df20..df4b6e0 100644 --- a/mix.lock +++ b/mix.lock @@ -1,16 +1,16 @@ %{ - "ash": {:hex, :ash, "2.5.2", "018dfaeb78b97810dfbc12d13842dd3fe4b8a797deebd4b9fbe32a9cd4471238", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 0.3.0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69df569a557f55bb0479cf270ecaf80ef58780fa607e118e2889af1ae4448b45"}, + "ash": {:hex, :ash, "2.10.2", "f2bc2d0238fa658df9d84ce776d3f04c2926e87a517e95b72ce96d7539f3028a", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, "~> 1.0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d7bccc87e1d972ce0bfcd06af5d97828c2fc83b8cf1b024e2650f88820d3321"}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, "castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"}, "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, - "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, + "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, "earmark_parser": {:hex, :earmark_parser, "1.4.16", "607709303e1d4e3e02f1444df0c821529af1c03b8578dfc81bb9cf64553d02b9", [:mix], [], "hexpm", "69fcf696168f5a274dd012e3e305027010658b2d1630cef68421d6baaeaccead"}, - "ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"}, - "elixir_make": {:hex, :elixir_make, "0.7.3", "c37fdae1b52d2cc51069713a58c2314877c1ad40800a57efb213f77b078a460d", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "24ada3e3996adbed1fa024ca14995ef2ba3d0d17b678b0f3f2b1f66e6ce2b274"}, + "ecto": {:hex, :ecto, "3.10.2", "6b887160281a61aa16843e47735b8a266caa437f80588c3ab80a8a960e6abe37", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6a895778f0d7648a4b34b486af59a1c8009041fbdf2b17f1ac215eb829c60235"}, + "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ets": {:hex, :ets, "0.8.1", "8ff9bcda5682b98493f8878fc9dbd990e48d566cba8cce59f7c2a78130da29ea", [:mix], [], "hexpm", "6be41b50adb5bc5c43626f25ea2d0af1f4a242fb3fad8d53f0c67c20b78915cc"}, "ex_check": {:hex, :ex_check, "0.12.0", "c0e2919ecc06afeaf62c52d64f3d91bd4bc7dd8deaac5f84becb6278888c967a", [:mix], [], "hexpm", "cfafa8ef97c2596d45a1f19b5794cb5c7f700f25d164d3c9f8d7ec17ee67cf42"}, @@ -18,7 +18,6 @@ "excoveralls": {:hex, :excoveralls, "0.13.4", "7b0baee01fe150ef81153e6ffc0fc68214737f54570dc257b3ca4da8e419b812", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "faae00b3eee35cdf0342c10b669a7c91f942728217d2a7c7f644b24d391e6190"}, "exjsonpath": {:hex, :exjsonpath, "0.9.0", "87e593eb0deb53aa0688ca9f9edc9fb3456aca83c82245f83201ea04d696feba", [:mix], [], "hexpm", "8d7a8e9ba784e1f7a67c6f1074a3ac91a3a79a45969514ee5d95cea5bf749627"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "finch": {:hex, :finch, "0.14.0", "619bfdee18fc135190bf590356c4bf5d5f71f916adb12aec94caa3fa9267a4bc", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5459acaf18c4fdb47a8c22fb3baff5d8173106217c8e56c5ba0b93e66501a8dd"}, "gettext": {:hex, :gettext, "0.19.1", "564953fd21f29358e68b91634799d9d26989f8d039d7512622efb3c3b1c97892", [:mix], [], "hexpm", "10c656c0912b8299adba9b061c06947511e3f109ab0d18b44a866a4498e77222"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.5.4", "1f303c9952eccfc183631b7c3cceeb6604cb641a40dd29269bcd622416395de9", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "85d1fe718cacad67a7ca1e9e809a6cbb9771c2e9238c96e9aebbb286114f64e0"}, @@ -34,17 +33,18 @@ "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"}, "mix_test_watch": {:hex, :mix_test_watch, "1.1.0", "330bb91c8ed271fe408c42d07e0773340a7938d8a0d281d57a14243eae9dc8c3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "52b6b1c476cbb70fd899ca5394506482f12e5f6b0d6acff9df95c7f1e0812ec3"}, - "nimble_options": {:hex, :nimble_options, "0.5.2", "42703307b924880f8c08d97719da7472673391905f528259915782bb346e0a1b", [:mix], [], "hexpm", "4da7f904b915fd71db549bcdc25f8d56f378ef7ae07dc1d372cbe72ba950dce0"}, + "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "picosat_elixir": {:hex, :picosat_elixir, "0.2.3", "bf326d0f179fbb3b706bb2c15fbc367dacfa2517157d090fdfc32edae004c597", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f76c9db2dec9d2561ffaa9be35f65403d53e984e8cd99c832383b7ab78c16c66"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"}, - "sourceror": {:hex, :sourceror, "0.11.2", "549ce48be666421ac60cfb7f59c8752e0d393baa0b14d06271d3f6a8c1b027ab", [:mix], [], "hexpm", "9ab659118896a36be6eec68ff7b0674cba372fc8e210b1e9dc8cf2b55bb70dfb"}, - "spark": {:hex, :spark, "0.3.4", "0084ce931c0e444194d5198b6f872c74c85607b6e5672436056e9f5b6fa41139", [:mix], [{:nimble_options, "~> 0.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "7816d3a43916c5fac2bb2308f9a3442644950e5e269014e4cdc5b2cab0bab5e0"}, + "sourceror": {:hex, :sourceror, "0.12.3", "a2ad3a1a4554b486d8a113ae7adad5646f938cad99bf8bfcef26dc0c88e8fade", [:mix], [], "hexpm", "4d4e78010ca046524e8194ffc4683422f34a96f6b82901abbb45acc79ace0316"}, + "spark": {:hex, :spark, "1.1.18", "349ad7ec69b389294fd3f17a4e49e772cafbbb71d3571add652a80f7b3c44990", [:mix], [{:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "964b46866e01b39810a82f9ee538f7f25d450cb3223af58b0f4717ce69d6f167"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"}, - "telemetry": {:hex, :telemetry, "1.2.0", "a8ce551485a9a3dac8d523542de130eafd12e40bbf76cf0ecd2528f24e812a44", [:rebar3], [], "hexpm", "1427e73667b9a2002cf1f26694c422d5c905df889023903c4518921d53e3e883"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "tesla": {:hex, :tesla, "1.7.0", "a62dda2f80d4f8a925eb7b8c5b78c461e0eb996672719fe1a63b26321a5f8b4e", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2e64f01ebfdb026209b47bc651a0e65203fcff4ae79c11efb73c4852b00dc313"}, "timex": {:hex, :timex, "3.7.8", "0e6e8bf7c0aba95f1e13204889b2446e7a5297b1c8e408f15ab58b2c8dc85f81", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8f3b8edc5faab5205d69e5255a1d64a83b190bab7f16baa78aefcb897cf81435"}, "typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, diff --git a/test/hackernews_test.exs b/test/hackernews_test.exs index d396bab..72e00a4 100644 --- a/test/hackernews_test.exs +++ b/test/hackernews_test.exs @@ -9,8 +9,6 @@ defmodule AshJsonApiWrapper.Hackernews.Test do data_layer: AshJsonApiWrapper.DataLayer json_api_wrapper do - finch MyApp.Finch - endpoints do base "https://hacker-news.firebaseio.com/v0/" @@ -88,8 +86,6 @@ defmodule AshJsonApiWrapper.Hackernews.Test do end json_api_wrapper do - finch MyApp.Finch - endpoints do base "https://hacker-news.firebaseio.com/v0/" @@ -124,8 +120,6 @@ defmodule AshJsonApiWrapper.Hackernews.Test do end json_api_wrapper do - finch MyApp.Finch - endpoints do base "https://hacker-news.firebaseio.com/v0/" @@ -156,8 +150,6 @@ defmodule AshJsonApiWrapper.Hackernews.Test do end test "it works" do - Finch.start_link(name: MyApp.Finch) - assert [top_story] = TopStory |> Ash.Query.limit(1)