Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor App.hs and related changes #1725

Merged
merged 7 commits into from
Feb 23, 2021
Merged

Conversation

monacoremo
Copy link
Member

@monacoremo monacoremo commented Jan 4, 2021

I struggled to follow what was happening in App.hs, so I tried to simplify the structure by decomposing the app function, which has grown to become quite huge over time.

  • Prefixed most functions and types by their module, making it obvious what is coming from where. No wildcard imports.
  • Took out repetition, e.g. failing on non-singular results or setting GUCs is happening in one respective function
  • Used ExceptT to combine pure error handling with e.g. IO or Hasql.Transaction, allowing us to avoid the deeply nested Either case analysis

The result is more verbose, but IMO also easier to follow and maintain. I kept the refactor purely to App.hs to keep the diff simpler, but there are some obvious simplifications in other modules that result from this.

I'm not a fan of the right-padding on imports and in type declarations, as they are a pain to maintain and lead to unclean diffs. Maybe that's something we can reconsider in our stylishhaskell settings?

@wolfgangwalther
Copy link
Member

I only skimmed through it on the surface, so far. Here's a couple of notes.

I really like this: codecov/project 82.97% (+2.27%) compared to 5223082

  • Prefixed most functions and types by their module, making it obvious what is coming from where. No wildcard imports.

In general, I like that. Although I wonder whether we should use shorter prefixes. At times this almost reads like Java ;)

One thing I noted independently of this PR: I think the whole Types.hs file is just a mess. We have some type definitions in this file, but others in the respective module's files where they belong semantically. I find myself jumping between the "feature" module and Types.hs quite often when developing one thing. I feel like most of the type definitions could go into one of the module directly. E.g. all the database structure types would be much better off in DbStructure.hs. Of course, the types are used somewhere else, too - but they originate from DbStructure.

WDYT?

  • Took out repetition, e.g. failing on non-singular results or setting GUCs is happening in one respective function

+1 - I had thought about that one before, too.

  • Used ExceptT to combine pure error handling with e.g. IO or Hasql.Transaction, allowing us to avoid the deeply nested Either case analysis

I really like the flat structure - the deep nesting was hard to follow for me, too.

I'm not a fan of the right-padding on imports and in type declarations, as they are a pain to maintain and lead to unclean diffs. Maybe that's something we can reconsider in our stylishhaskell settings?

TBH, everything that stylishhaskell does, I don't care about during development - so I don't find it hard to maintain. I never pad imports manually. I just add them and when I'm done postgrest-style will do the padding for me. Same for the unclean diffs - you can just enable "ignore whitespace" when displaying diffs (at least here in the GitHub PR) and all those won't show up anymore.

I feel like aligning things often helps with readability, especially in highly repeated patterns like imports, type declarations or pattern matching / case statements. For a couple of months now, I'm aligning things heavily in SQL, too (see #1702 for an example and https://www.sqlstyle.guide/ for the background) and I feel like this really helps readability a lot.

For me readability is more important than the additional effort needed to pad things nicely. Coming back at a piece of code after a longer time to change something, I spent the most time reading and understanding what was done before - not padding things after the change.

@wolfgangwalther
Copy link
Member

  • Prefixed most functions and types by their module, making it obvious what is coming from where. No wildcard imports.

In general, I like that. Although I wonder whether we should use shorter prefixes. At times this almost reads like Java ;)

Maybe we can do something in the middle, too.

For external modules:

  • No wildcard imports
  • Use short prefixes (we already do in most of the cases, but standardize them a bit more across files) for standard tools like L for Data.List, S for Data.Set, M for Data.Map, BS for Data.ByteString or LBS for the lazy bytestring. LazyByteString is just too much.
  • At the same time I really like JSON for aeson etc. - I think just SQL for all the hasql stuff would be just fine, too.

For internal modules:

  • No qualified imports in general. Feels way too much like Java - in other words: too much to read without meaning.
  • Exceptions would be utilities like Common.hs, that might be used across more files. Those could basically be treated similar to an external module.
  • Remove Types.hs (and possibly QueryFragments.hs) and split them up to the modules files where those are mainly used. This should remove a lot cross-file references.
  • Import things explicitely. A huge list of imported functions from one file is probably an indication that we didn't separate our modules properly and should move some stuff around.

@steve-chavez
Copy link
Member

steve-chavez commented Jan 5, 2021

Took me a while to get used to it. But now I'm linking the new structure :)

(I guess I built compression tolerance and got too used to App.hs code density)

Since this is a big refactor, one thing I like to see is if this change brings a perf downgrade, doesn't look like it but I want to be certain(maybe fusion matters here). So perhaps run this change with the load tests on #1609 and compare?

For external modules:

No wildcard imports

How about Prelude/Protolude? I think we can make an exception in this case.

Use short prefixes

Short prefixes and SQL for the Hasql import sounds good.

For internal modules:

No qualified imports in general. Feels way too much like Java - in other words: too much to read without meaning.

Strongly agree on this one. Too verbose. I feel it optimizes too much for the newcomer and makes reading code harder for more experienced postgrest devs.

Remove Types.hs (and possibly QueryFragments.hs)

I remember the main reason for putting things in Types.hs was to avoid cyclical module dependencies errors. Types.hs is like a Common.hs in this regard. Not sure if it's possible to get the types in their own module.

Also, keep in mind that QueryFragment.hs was created to avoid out of memory issues on CI. QueryBuilder was too big to compile, see #1363 (comment) and #1364 (comment).

@wolfgangwalther
Copy link
Member

How about Prelude/Protolude? I think we can make an exception in this case.

For me, without a haskell background, Prelude/Protolude is about the same as every other external module :D. I'm mostly confused by all the hiding (...) imports - but I guess those won't happen anymore, once all the other imports are qualified. So I'd be fine with wildcard importing those.

I remember the main reason for putting things in Types.hs was to avoid cyclical module dependencies errors. Types.hs is like a Common.hs in this regard. Not sure if it's possible to get the types in their own module.

Also, keep in mind that QueryFragment.hs was created to avoid out of memory issues on CI. QueryBuilder was too big to compile, see #1363 (comment) and #1364 (comment).

Ah, thanks for the background on this. Maybe we can still make the cut somewhere else and just put a few types in Common.hs.

@steve-chavez
Copy link
Member

Another thing about Protolude is that symbols would be too verbose prefixed:

<$>, <*>, =<<
-- vs
Protolude.<$>, Protolude.<*>, Protolude.=<<

Enforcing the qualifier would defeat the purpose to have symbols, I think.


I've noted that Hasql uses qualified imports for internal modules, but some of them are short(like A, B): https://github.com/nikita-volkov/hasql/blob/master/library/Hasql/Private/Decoders.hs#L9-L17.

Still, I'd prefer not qualifying internal modules.

@steve-chavez
Copy link
Member

Actually I'm finding some functions(like Auth.containsRole) more readable with qualifiers. The main issue is the modules with long names. Shortening those might be another solution.

@wolfgangwalther
Copy link
Member

Actually I'm finding some functions(like Auth.containsRole) more readable with qualifiers. The main issue is the modules with long names. Shortening those might be another solution.

I feel like this is more an exception than the norm. For many of the exported functions we currently have, it's very easy to see which module they belong to. This is most prominent with all the field lookup functions for records functions. Just look at this:

handleRequest context@(RequestContext _ dbStructure apiRequest _) =
  case (ApiRequest.iAction apiRequest, ApiRequest.iTarget apiRequest) of

The change I would make here, would be to change the field prefix for ApiRequest from i to req. This would be more in line with all the other types we have. And then I would add LANGUAGE RecordWildCards and write it like this:

handleRequest context@(RequestContext _ dbStructure ApiRequest{..} _) =
  case (reqAction, reqTarget) of

In my opinion this is muuch quicker to "parse" visually and basically just as explicit, because of the prefix in the field name.

@monacoremo
Copy link
Member Author

Agree that some parts have become quite verbose and depart a bit from the typical Haskell style. I think this is a very valuable discussion we are having here, as I think there is a huge value in having the code as easy to follow and accessible as possible. PostgREST is one of the more visible open source projects outside the Haskell ecosystem, and a entryway for some new users of the language (at least it was for me).

So let me be the devil's advocate in a few points :-)

For many of the exported functions we currently have, it's very easy to see which module they belong to.

I think ideally, we would turn this convention around, avoiding prefixes within modules and using prefixed imports to disambiguate. For example in the Elm ecosystem (another ML language), it's customary to prefix all imported variables (https://elm-lang.org/docs/style-guide). This makes Elm code very easy to follow. It also avoids polluting modules with prefixes internally.

So we would at some point use Config.jwtRoleClaimKey instead of configJwtRoleClaimKey. This makes it clearer what the origin is, while having the same length, is easier to parse and it will make the Config module terser internally.

And then I would add LANGUAGE RecordWildCards and write it like this:

That looks like a great simplification to make it more readable!

Short prefixes and SQL for the Hasql import sounds good.

I think Hasql, Aeson, and a bit longer prefixes than S or L in general are much more easy to follow for new readers of the code. Looking up JSON decode on Google yields nothing useful, while Aeson decode does. The same goes for SQL pool vs. Hasql pool. Having to refer back to the imports all the time is a significant distractions (what is S again?), while having a bit longer names within the module is not a significant cost.

How about Prelude/Protolude? I think we can make an exception in this case.

Agree that this should be one exception for wildcard import, as Protolude conceptionally replaces the Prelude that is available by default. At some point we might want to roll our own Prelude, as e.g. Hasql does.

Remove Types.hs (and possibly QueryFragments.hs)

Totally agree that we should split this up. If circular imports are a concern, we could still have PostgREST.Types.DbStructure, PostgREST.Types.ContentTypes etc. separately.

@wolfgangwalther
Copy link
Member

Thanks for adding the refactor for Auth.hs, @monacoremo. The file is much smaller than App.hs and a lot easier to discuss about regarding imports and general style. I see you did a lot less regarding those in this commit. I feel like the best way would be somewhere inbetween the two styles.

A couple of notes:

  • The first thing I note when I go through the Auth.hs file is how often I ask myself: "Where does this type come from?" Turns out all of them come from the unqualified Crypto.JWT import. I very much feel this needs a prefix, too!
  • I think we should remove the Control.Lens stuff. To me it looks like we use lenses only very inconsistently across the whole code-base. We can do just the same with basic record syntax here.
  • A where helps me a lot to immediately tell which functions are only used in a certain scope. validation, jwtClaimsError and claims2map are all only used in jwtClaims - and there are more examples in the same file. Putting those into where might look like some sort of "nesting" - but it's not. It's just indentation according to context and doesn't increase complexity. The style as it is currently (and was before, too!) is too flat!
  • In a short function like jwtClaims the blank lines are just a waste of screen space, imho. Each of those blank lines just separates a single statement. This is the same thing that indentation does visually - no gain. But the consequence is that all functions are separated by two blank lines now. Even with a where clause it will be harder to fit the whole jwtClaims functions (including dependants) on the screen at the same time (well not on my screen, but on a bigger function I would have the same problem). I'm just missing the "one look at the whole function+context" with this style - and that makes it harder for me to read.

@wolfgangwalther
Copy link
Member

I think this is a very valuable discussion we are having here, as I think there is a huge value in having the code as easy to follow and accessible as possible.

Fully agree!

I think ideally, we would turn this convention around, avoiding prefixes within modules and using prefixed imports to disambiguate. [...]
So we would at some point use Config.jwtRoleClaimKey instead of configJwtRoleClaimKey. This makes it clearer what the origin is, while having the same length, is easier to parse and it will make the Config module terser internally.

Yes, I thought about this briefly, too. It will however:

  • increase the likeliness of field name conflicts across different record types (think name or something general like this)
  • make LANGUAGE RecordWildCards harder to understand, because the automatically extracted fields do not have the prefix anymore. This might also create those conflicts mentioned across types in different files - suddenly you might not be able to unpack two unrelated record types in the same context anymore.
  • it would also mean that we'd need to prefix all value constructors. ApiRequest.ApiRequest and so on. I feel like the code-base is not too big to be able to avoid that. If we avoid wildcard imports, it will be one find in the currently open file max to see where the type comes from.

Using "proper" prefixes instead of "in-name-prefixes" felt more "proper" for me, too - at first. But now I like the field name prefixes that we have.

I think Hasql, Aeson, and a bit longer prefixes than S or L in general are much more easy to follow for new readers of the code. Looking up JSON decode on Google yields nothing useful, while Aeson decode does. The same goes for SQL pool vs. Hasql pool. Having to refer back to the imports all the time is a significant distractions (what is S again?), while having a bit longer names within the module is not a significant cost.

"Looking up JSON decode on Google yields nothing useful" - that might be true, but "haskell JSON decode" will immediately point you to Aeson. To be honest, I had quite some trouble with JSON and Aeson when starting with haskell in postgrest. Googling "haskell JSON xxx" would always yield this strange "Aeson" thing... :)
But the good thing about the JSON prefix was, that I immediately knew what the code was about. It was about JSON stuff. Without even looking up Aeson, I know whether that's something I care about right now or not, even in the very beginning. It's the same thing with SQL vs Hasql, but much easier of course, because sql is part of hasql...

Bottom line is: Start out with the PostgREST code-base and you'll have to learn some commonly used prefixes used for import. Once you understand we're using S for Set and L for List everywhere and do the same with JSON and SQL - it will be no problem, even for beginners.

How about Prelude/Protolude? I think we can make an exception in this case.

Agree that this should be one exception for wildcard import, as Protolude conceptionally replaces the Prelude that is available by default. At some point we might want to roll our own Prelude, as e.g. Hasql does.

Absolutely, I agree with both of you. It looks like liftEither and mapLeft definitely belong in our own Prelude after your refactoring :D.

@@ -70,8 +70,7 @@ spec =
`shouldRespondWith`
[json|{"details":"Results contain 4 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|]
{ matchStatus = 406
, matchHeaders = [ matchContentTypeSingular
, "Preference-Applied" <:> "tx=commit" ]
, matchHeaders = [ matchContentTypeSingular ]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the refactored way we handling errors, errors go straight through optionalRollback untouched and this header is not being added. I believe this is fine, as the transaction is always rolled back on error and it doesn't matter what the tx preference was. As it's clear what happened to the user, the header is not needed according to https://tools.ietf.org/html/rfc7240#page-7 section 3.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. I think it's possible to commit a transaction and still return a 4xx / 5xx error code via GUC: https://postgrest.org/en/latest/api.html#setting-response-status-code

This means, that you can not tell from the error code alone whether the transaction was comitted or rolled back.

Is it possible to refactor to avoid this change?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking a bit about this @wolfgangwalther : It would be relatively simple to always set the preference header (catch any errors, turn them into a response, set the header), but I wonder whether the new behavior that emerged here is more correct.

Setting a custom status via GUC still works and will set the preference header as before, regardless of what the status code is.

Setting the preference header will only be skipped when a true error happened and the transaction will definitely be rolled back. In those cases, showing tx=commit seems misleading.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting a custom status via GUC still works and will set the preference header as before, regardless of what the status code is.

Setting the preference header will only be skipped when a true error happened and the transaction will definitely be rolled back. In those cases, showing tx=commit seems misleading.

The whole thing is a bit more complicated, because we have db-tx-end=commit|commit-allow-override|rollback-allow-override|rollback settings. This creates a lot of different scenarios.

I understand RFC 7240 as follows: We should only ever return a preference that was asked for with Preference-Applied. This means we should never return a Preference-Applied: tx=rollback header, when we had a Prefer: tx=commit header before.

This basically means that we can not use the Preference-Applied header for feedback about the actual result ("committed" or "rolled back") of the transaction. A couple of examples, where this fails:

Assume an error response with 4xx status code. With db-tx-end=commit-allow-override and Prefer: tx=commit: Setting a Preference-Applied: tx=rollback seems wrong. How would we differ between the "error via RAISE" and "error via GUC" cases then? We could only return nothing for RAISE (i.e. a true rollback) and Preference-Applied: tx=commit for the GUC case (i.e. a true commit). This basically assumes that the transaction was rolled back for any 4xx error code by default. But now assume we have db-tx-end=commit and Prefer: tx=rollback. For a GUC error response this results in a commit in the end, because the override is not allowed. But we can't return Preference-Applied: tx=commit - this was not asked for. And we can't assume a rollback by default either, because that wouldn't be true.

In short: We can only use the Preference-Applied header to indicate what our transaction termination command was (COMMIT or ROLLBACK) - completely independent of whether the transaction was implicitly rolled back before because of an error. Note, that this is also what is implied by the config option db-tx-end.

There are a couple of other reasons why it makes a lot of sense to return a Preference-Applied header whenever we understand and accept that preference - regardless of whether it might be sensible to assume this behaviour by default in that specific case. See #740.

The current behaviour should be kept. Our goal should be to respond with more Preference-Applied headers instead of less.

@monacoremo monacoremo marked this pull request as ready for review January 16, 2021 21:42
@monacoremo
Copy link
Member Author

@steve-chavez There will be conflicts with #1729, let's get that PR merged first and then I'll rebase/backport the changes here

@monacoremo monacoremo changed the title Refactor App.hs Refactor (includes changed on App.hs, Main.hs, Error.hs) Jan 16, 2021
@monacoremo monacoremo changed the title Refactor (includes changed on App.hs, Main.hs, Error.hs) Refactor (includes changes on App.hs, Main.hs, Error.hs) Jan 16, 2021
main/Main.hs Outdated Show resolved Hide resolved
postgrest.cabal Outdated Show resolved Hide resolved
src/PostgREST/Auth.hs Outdated Show resolved Hide resolved
src/PostgREST/CLI.hs Outdated Show resolved Hide resolved
src/PostgREST/CLI.hs Outdated Show resolved Hide resolved
src/PostgREST/CLI.hs Outdated Show resolved Hide resolved
src/PostgREST/CLI.hs Outdated Show resolved Hide resolved
@@ -42,7 +42,7 @@ import qualified Data.Map.Strict as M

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't comment on the hidden line above (the Map import). If that is still used after the split-up into CLI, the same reasoning for naming etc. applies as I commented there.

src/PostgREST/Config.hs Show resolved Hide resolved
@@ -70,8 +70,7 @@ spec =
`shouldRespondWith`
[json|{"details":"Results contain 4 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|]
{ matchStatus = 406
, matchHeaders = [ matchContentTypeSingular
, "Preference-Applied" <:> "tx=commit" ]
, matchHeaders = [ matchContentTypeSingular ]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. I think it's possible to commit a transaction and still return a 4xx / 5xx error code via GUC: https://postgrest.org/en/latest/api.html#setting-response-status-code

This means, that you can not tell from the error code alone whether the transaction was comitted or rolled back.

Is it possible to refactor to avoid this change?

@wolfgangwalther
Copy link
Member

I glanced over most files, but not App.hs so far.

Many of my comments are about import statements - and I guess half of those
about some statements, that you didn't even change, @monacoremo. So there is a lot of inconsistency about that already currently. I'm not sure how to proceed here.

I know we should get this PR merged rather quickly, because it touches so much code - it will be a pain to keep this open.

@monacoremo
Copy link
Member Author

I know we should get this PR merged rather quickly, because it touches so much code - it will be a pain to keep this open.

Yes, but no need to hurry - I'll rebase as needed

@monacoremo monacoremo force-pushed the untangleapp branch 6 times, most recently from ef923ee to c93324e Compare January 23, 2021 07:41
@monacoremo monacoremo force-pushed the untangleapp branch 5 times, most recently from a6ba878 to 1e6c272 Compare January 31, 2021 20:58
@monacoremo
Copy link
Member Author

@wolfgangwalther Suggest that we defer a few changes to a separate PR, please see the comments above. All others should be reflected.

@monacoremo
Copy link
Member Author

@wolfgangwalther The coverage failure happens because we're no longer using the record fields in App.hs directly, instead we always unpack the records. Should probably be one of the coverage exceptions once we have full coverage.

src/PostgREST/App.hs Outdated Show resolved Hide resolved
src/PostgREST/App.hs Outdated Show resolved Hide resolved
src/PostgREST/App.hs Outdated Show resolved Hide resolved
src/PostgREST/App.hs Outdated Show resolved Hide resolved
_ ->
SQL.Write

data CreateResult = CreateResult
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does create mean here? I see it's used in all kind of modifying queries (delete, update, ...).

I think this needs a better name - and the fields a better prefix, too!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, was difficult to follow. Added a comment and renamed.

This type and the writeQuery function are some glue to adapt the DbRequestBuilder and Statements APIs to something that is easier to handle. They should move to one of those modules when we sort out the imports. I didn't want to touch those modules in this PR too, it's unwieldy enough as it is :-)

src/PostgREST/App.hs Outdated Show resolved Hide resolved
@wolfgangwalther
Copy link
Member

I now reviewed the remaining parts of App.hs.

Since this is a big refactor, one thing I like to see is if this change brings a perf downgrade, doesn't look like it but I want to be certain(maybe fusion matters here). So perhaps run this change with the load tests on #1609 and compare?

I did that and there is no difference in minimum latency with the vegeta test at all. That's only for a plain GET request, but I don't think there's reason to believe the other types of requests would make a difference.

@wolfgangwalther
Copy link
Member

I really like this: codecov/project 82.97% (+2.27%) compared to 5223082

The initial increase in code coverage was not real, but just an effect of the refactor having a lot more lines of code initially, which increased the percentage.

However I checked the open coverage TODOs in #1719 for App.hs. Because of the changes to error handling, 3 of the 6 are done with this PR.

@wolfgangwalther The coverage failure happens because we're no longer using the record fields in App.hs directly, instead we always unpack the records. Should probably be one of the coverage exceptions once we have full coverage.

Absolutely, I agree. Others have that problem, too - let's hope somebody will do something about it: https://gitlab.haskell.org/ghc/ghc/-/issues/17834

@monacoremo
Copy link
Member Author

Many thanks for your thorough review and helpful comments @wolfgangwalther !

I added a few commits to address your notes.

@monacoremo
Copy link
Member Author

The freebsd builds fails with

cabal v2-update
ld-elf.so.1: /usr/local/bin/cabal: Undefined symbol "pthread_setname_np@FBSD_1.6"

No idea what change would have caused that. Could that be some caching issue @steve-chavez ?

@steve-chavez
Copy link
Member

@monacoremo I'm also getting the same error. It could be related to caching because we cache freebsd packages in:

postgrest/.cirrus.yml

Lines 7 to 13 in 0ddd676

# caches the freebsd package downloads
# saves probably just a couple of seconds, but hey...
pkg_cache:
folder: /var/cache/pkg
install_script:
- pkg install -y postgresql95-client ghc hs-cabal-install jq git

I'll try removing this cache on #1760.

(Seems related to https://reviews.freebsd.org/D25117)

@monacoremo
Copy link
Member Author

Thank you @wolfgangwalther and @steve-chavez reviewing and helping to get this PR done! I'll merge this now so that we can push ahead on other topics, even though we'll get a CI failure for the unrelated FreeBSD issue.

@monacoremo monacoremo merged commit e6973f9 into PostgREST:main Feb 23, 2021
@monacoremo monacoremo deleted the untangleapp branch February 23, 2021 21:41
@steve-chavez
Copy link
Member

I'll try removing this cache on #1760.

Bummer, this didn't work:

It must be something else.

Perhaps we can try busting the cache with echo $CIRRUS_OS like done here:
https://cirrus-ci.org/guide/writing-tasks/#cache-instruction

I'll refrain from doing this on #1760 though, it already got a bit long.

monacoremo added a commit to monacoremo/postgrest that referenced this pull request Jul 17, 2021
* Use ExceptT to avoid 'staircasing' case analysis in App.hs
* Split large function in App.hs into individual handler functions
* Adapt API of Auth.hs, OpenApi.hs etc. to simplify the use of those modules in App.hs
* Split optional rollback functionality into Middleware
* Unify SimpleError and ApiRequestError into one Error type, so it can be used across modules
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants