Release v5.0.0 #206
System-Glitch
started this conversation in
Announcement
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Introduction
Goyave v5 has been in the oven for over two years. With the first production applications deployed, a ton of issues and flaws revealed themselves, preventing real long-lived projects from cleanly evolving. The initial goals of the framework started to weaken the more the applications were developed and changed: the strong basis it promised to provide wasn't actually that strong. From this invaluable experience, I decided to go back to the drawing board and redesign the framework.
This new major version is an almost entire rewrite of the framework. In order to limit the frequency of releases containing breaking changes, all accumulated ideas for reworks and improvements that would introduce those were grouped in v5. This new major version not only aims at fixing the outstanding issues and design flaws, but also improves on existing features by upgrading them to the latest available technology. Expect v5 to feel modern and to neatly integrate with all the new language features such as generics, file systems, structured logging, and more.
These release notes will be organized in categories. They will explain the overall change of direction for each area of the framework as well as shortly showcase the new or improved features. The list of changes may be incomplete as many features were rewritten entirely.
Motivations
Among the many aspects that needed to be reworked, some of them stood out and fueled the initial drive to rewrite the framework.
Dependency coupling
Every layer and components of a v4 application had strong dependency coupling. The HTTP, business and database layers were all mixed up together. The framework was systematically imposing a dependency to itself, direct or indirect. Its locked-up architecture, entirely based on globals was hindering the more business-oriented applications. Those struggled to detach their domains from the rest of the application, and encountered obstacles every time they needed to handle more complex business logic.
For example: to access the database, you were forced to use the framework, which was loaded from a configuration system that also was handled by the framework. This created a long chain of dependencies that was hard to separate from the rest, even more so when it came to writing tests.
On top of that, the all-global architecture required a ton of synchronization, which were detrimental to the overall performance of the application.
Locked architecture
All components of the framework were strongly linked together and quite opaque, despite an initial effort made to make the framework flexible and hackable. In the end, many non-elegant workarounds had to be made in real-world scenarios. This made it harder to adapt an application to the constraints often encountered by companies developing their ecosystem and trying to solve real-world issues that needed deeper access to the inner-workings.
The validation system was one of the biggest, if not the biggest, culprit. It was very inconvenient to use compared to the new one brought with v5. The hacks required to make some advanced field comparison or business-logic validation were very fragile and hard to maintain. This design made it impossible to re-use code, and forced the hassle of creating a new validator for every single specific use-case.
The framework was also a bit too reliant on magic in some aspects. Many functions were using weak typing (
any
) and reflection, or even string identifiers, all for the sake of conciseness. But this came at a cost: no compile-time checks, hard code navigation, no completion, the need to rely on documentation all the time, etc. In the end, by trying to be concise for a better DX (developer experience), the framework sacrificed code cleanliness, reliability, readability, maintainability and actually ruined its DX this way.Testing
All these issues accumulate and become a huge pain the moment you start trying to add tests to your project. They were very difficult to write, which is the exact opposite of what you want. Tests should be painless and as easy to read and maintain as possible. Also suffering from the locked architecture and mandatory dependency to the framework, they couldn't even be run in parallel. Almost nothing could be mocked because of the dependency coupling. This was in turn forcing you to use the database for your tests, which made tests even slower and complicated.
In short, the design of the framework prior to v5 treated tests as an after-thought despite how important they are.
Streamlining
There were many smaller and non-blocking issues as well. Together they made the entire development flow awkward by moments, like there was a missing piece to make the whole process fluid from start to finish.
The first one was the relation between the user-sent data and the internal data. Making use of the data sent by users was inconvenient and also quite unsafe, requiring a ton of type assertions and map navigation. This is very subpar compared to most Go applications which use structures. It also caused problems when interacting with the database.
Another issue was error handling and debugging. The framework was relying too much on
panic
, which is not a very sane nor idiomatic way to handle errors. Although it allowed for a pretty good debugging experience for developers with more precise stacktraces, a much better solution was possible. This solution wouldn't compromise on code quality for the sake of DX, again.The last major one was the low interoperability and control, notably with the lack of access over the
context
API, or some missing configuration options and settings.Philosophy
The overall philosophy of the framework stays the same. Goyave remains an opinionated framework, focused on DX (developer experience) by enabling quick and enjoyable development by being expressive, reliable and complete. The goal is to make business logic development as painless as possible by handling as many recurring things for developers as possible so they can focus on implementing what actually creates value for their company.
New direction
However, there are some important evolutions in the general direction of the framework:
Architecture
The framework goes even further than before when it comes to architecture recommendations for your projects. The goal is to answer more of the questions any team will inevitably encounter when setting up their project. By providing tools that will works seemlessly with this architecture, the frameworks aims at saving you a lot of time and make the development process as fluid as possible.
A project build with Goyave v5 will be split in three distinct layers:
Each layer doesn't directly depend on the others because they define interfaces representing their own needs. The following chart describes the usual flow of a request into a Goyave application.
This architecture has several advantages:
In Goyave v5, nothing is global anymore. The costly need for goroutine synchronization is eliminated. The overall design is now interface-focused. On top of that, the entire framework now takes full advantage of the standard
context
API and encourages it use.Components
Because nothing is global, a mechanism is necessary so the server's essential resources (such as the configuration, logger, etc) can be distributed to every component of the server. This mechanism is actually called Components, and described by the interface
goyave.Composable
. Most structures in the presentation layer actually are Goyave components: controllers, middleware, validators, etc.Server
Changing all global resources to non-global required a central element that would hold them all. A server contains all the base resources for an application: the HTTP server, the router, configuration, language, the database pool, services, etc. It is the parent of all components. Previous versions were a bit too focused on how short the code could be. It was detrimental to its flexibility. A typical
main()
function for v5 will be longer than before but will offer more ways for developers to tweak and configure their application as they see fit, without adding a lot of complexity.goyave.New(options)
. The server doesn't start listening on the network right away, which is different from the previous version'sgoyave.Start()
.server.Start()
is called.goyave.Options
is a structure containing new settings allowing to use manually loaded config, a custom logger, and more. It also allows to set options on the underlyingnet/http.Server
that were previously not available, such asConnState()
,BaseContext()
,ConnContext()
andMaxHeaderBytes
.net/http.Server
's logger is now unified with the Goyave logger.goyave.ServerFromContext(ctx)
.main.go
.server.Host()
andserver.Port()
new methods return the hostname and port the server is running on.BaseURL()
andProxyBaseURL()
are now methods of*Server
.*Server
as parameter.server.RegisterSignalHook()
.server.RegisterRoutes()
instead of passing the main route registrer onStart()
.server.Router()
.server.Stop()
doesn't attempt to stop the server a second time if it was already stopped.server.Stop()
won't attempt to close the signal channel again if the server has already been stopped. This method can this be called several times safely now.File systems
Go 1.16 introduced the
io/fs
package as well as support for embedded files and directories. These additions add a number of important benefits ranging from distribution packaging to storage abstraction. As v5 was already going to contain many breaking changes, the opportunity to integrate these new systems into the framework was taken. Every system or feature interacting with files in any way should now use file system interfaces. This allows developer maximum flexibility when working with files and makes it easier to write tests.Thanks to file systems, static resources can be embedded into the compiled executable and external resources or storage can be seemlessly used.
fsutil.FS
: combinesfs.ReadDirFS
andfs.StatFS
. This is used to require versatile read-only file systems.fsutil.WorkingDirFS
: a file system that has a working directory for relative paths, get by theGetwd()
method.fsutil.MkdirFS
: a writable file system able to create directories.fsutil.WritableFS
: a writable file system with anOpenFile()
method.fsutil.RemoveFS
: a writable file system supporting file or directory deletion.osfs.FS
is a complete implementation of the above interfaces for the local OS file system.fsutil.Embed
is a wrapper aroundfs.ReadDirFS
used to enrich theembed.FS
file system so they also implementfs.StatFS
andSub()
. This is useful for serving embedded static resources, loading embedded language files, etc.fsutil.GetMIMEType()
now takes afs.FS
as parameter and returns an error instead of panicking.fsutil.FileExists()
andfsutil.IsDirectory()
now take afs.StatFS
as parameter.fsutil.File.Data
was removed. You now have to open theFile.Header
yourself.fsutil.File.Save()
now takes afsutil.WritableFS
as parameter and returns an error instead of panicking.fsutil.ParseMultipartFiles()
now takes a[]*multipart.FileHeader
instead of a*http.Request
.osfs.FS
, but options are available in all these features if you want to use a different file system.fsutil.Delete(path)
was removed. Useosfs.FS.Remove()
instead.Configuration
Apart from moving from global to scoped in a structure, the configuration system didn't receive a lot of changes. Many configuration entries and defaults changed though.
*config.Config
.config.Set()
should only be used right after the config was loaded or inside tests.config.LoadDefault()
new function loads the default configuration without.Options
with theoptions.Config
field.server.protocol
,server.httpsPort
andserver.tls
were removed: protocol is onlyhttp
as TLS/HTTPS support has been removed because Goyave applications are most of the time deployed behind a proxy.server.timeout
has been split:server.writeTimeout
,server.readTimeout
,server.idleTimeout
,server.readHeaderTimeout
,server.websocketCloseTimeout
.server.maintenance
was removed.database
entries do not have a default value anymore. They were previously using default values for MySQL.database.defaultReadQueryTimeout
anddatabase.defaultWriteQueryTimeout
add a timeout mechanism to your database operations. If you have long queries, increase their values. Set to0
to disable the timeouts.auth.jwt.rsa.password
was removed.server.port
to0
will now assign an automatically chosen port that is available. The actual port used can be retrieved withserver.Host()
orserver.Port()
.int
,float64
,bool
andstrings
are now correctly supported by the configuration system.Routing
Without dramatically changing how routing works, the simple addition of metadata assigned to routers and routes opened up a lot of possibilities and helped smooth out the route definition. It is now much easier to control middleware settings with the greatest granularity. Without this feature in previous versions, developers were limited in their API design and sometimes had to either sacrifice the consistency of their route tree, or duplicate a ton of code for fine-grained control.
Meta
. Metadata are string-identified and can be inherited from parents thanks to theLookupMeta()
method.Meta
map, or usingSetMeta()
/RemoveMeta()
.route.BuildProxyURL()
new method builds a full URL pointing to this route using the proxy base URL.route.GetParent()
androuter.GetParent()
new methods returns the parent router.route.GetValidationRules()
was removed. Validation rules are now stored in struct fields of the validation middleware.route.Validate()
was replaced byroute.ValidateBody()
androute.ValidateQuery()
.goyave.RuleSetFunc
as parameter. Rule sets are now generated per-request./test
and/test-2
won't conflict anymore)/
route defined at the main router being matched if a subrouter matches but none of its routes do and a trailing slash is present in the request URI.goyave.StatusHandler
interface.goyave.GetRoute()
was removed. Use the new methodrouter.GetRoute()
instead. Route naming is global to a main router. If you canGetRoute()
from a subrouter, it will be able to return routes registered in a parent router.goyave.Registrer
interface, using the new methodrouter.Controller()
. This is encouraged so route definition for a feature will be located in the same package as the feature itself. This makes things cleaner and helps group related code together.router.Route()
now takes a slice of methods instead of a pipe-separated string.router.Static()
doesn't take a variadic slice of middleware as last parameter and now returns a*Route
. You can then apply middleware, a name, meta, etc to that route as usual.request.Params
becomesrequest.RouteParams
.request.Route()
becomesrequest.Route
.func Register(server *goyave.Server, router *goyave.Router)
.server.RegisterRoutes()
instead of passing the main route registrer on server start.Requests
request.ToStruct()
was removed, usetypeutil.Convert()
instead.request.Data
is nowany
instead ofmap[string]any
. You should use safe type assertions before use.request.Data
anymore, it is now split inrequest.Query
.request.Context()
. The context can be replaced withrequest.WithContext()
.request.URI()
was renamedrequest.URL()
.Has()
,String()
,Numeric()
, etc were all removed.request.CORSOptions()
was removed. You can access CORS options via the route meta:request.Route.Meta[goyave.MetaCORS].(*cors.Options)
.request.Lang
is now of type*lang.Language
instead ofstring
. See the localization section below for more details.request.Now
is a new field indicating the time at which the request was received (after routing).request.Extra
is now amap[any]any
. You are encouraged to use empty struct keys instead of literals.request.Params
was renamedrequest.RouteParams
.request.Route
is now exported. It is not a method anymore.request.Body()
new method returns the request's body reader.Responses
goyave.NewResponse()
is now exported to make testing easier.response.HandleDatabaseError(db)
was removed and replaced byresponse.WriteDBError(err)
WriteDBError()
returnstrue
if there is an error and that you shouldreturn
. This is the opposite ofHandleDatabaseError
.response.Error()
,response.JSON()
,response.String()
etc do not return an error anymore.response.Redirect()
was removed. You can replace byhttp.Redirect(response, request.Request(), url, http.StatusPermanentRedirect)
.response.Render()
andresponse.RenderHTML()
are not available anymore.response.GetError()
now returns*errors.Error
instead ofany
. See the error handling section below for more details.response.GetStacktrace()
was removed. You can now access the stacktrace from the error itself.response.File()
andresponse.Download()
now take a file system as first parameter.Validation
Validation is a complex and very broad topic. That's why every single major release since since the birth of the framework changed the system in one way or another. Previous changes were mostly incremental, but this time, v5's validation system is very different. This new system learned from all the mistakes of the previous one.
validation.Validator
interface. Validators are components. This new approach alos allows for completion, compile-time checks and better discoverability.ComparesFields
option in order to work. This option doesn't exist anymore. Rules comparing fields now use a*walk.Path
.validation.Errors
is nowvalidation.FieldsErrors
.validation.FieldErrors
is nowvalidation.Errors
.validation.CurrentElement
now works everywhere, not only on composed rule sets.Here is an example of a new rule set function:
validationError
, the key is nowerror
to be consistent with the rest of the error handlers.ctx.Data
will not be equal to the root data but to the parent element linked to the composed rule set. This affects comparison rules, whose comparison paths will now be relative. This effectively allows to re-use rule sets everywhere easily, even if they involve field comparison.PostValidationHooks
were removed.integer
becomes:Int()
,Int8()
,Int16()
,Int32()
,Int64()
,Uint()
,Uint8()
,Uint16()
,Uint32()
,Uint64()
.numeric
becomes:Float32()
,Float64()
.Array()
doesn't have type parameters anymore. To validate array elements, add a path entry matching the array elements.Size()
validator and its derivatives such asMin()
,Max()
, etc now also work with objects and will validate its number of keys.validation.Trim()
validator trims strings.net/mail
standard package instead of using a regex.nil
).validation.RequiredIf()
validator allows to conditionally mark a field as required.object
type too.request.go
tovalidation.go
.Exists()
/Unique()
now take a Gorm scope as parameter, letting you defined the table and condition yourself. You can preferably use a scope returned by a repository.ExistArray()
andUniqueArray()
efficiently validate an entire array against a database.[]
path is now valid.*
in the path (e.g:object.*
).Manual validation
validation.Validate
andvalidation.ValidateWithExtra
becomesvalidation.Validate(opts)
.validation.Options
contains several options, as well as external dependencies such as the language, the database, the config, etc. Those will be passed to the validators so they can access them just like any regular component.isJSON
becomesConvertSingleValueArrays
, which does the same but the logic is different so the bool value will be the opposite.Custom validators
validation.Validator
interface. They compose withvalidation.BaseValidator
for the base values, but can override methodsIsType()
andIsTypeDependent()
.ctx.Data
is nowany
instead ofmap[string]any
.ctx.Extra["request"]
becomesctx.Extra[validation.ExtraRequest{}]
ctx.Extra
is not scoped to the current validator only anymore. The same reference given inOptions.Extra
is shared between all validators.ctx.Valid()
becomesctx.Invalid
(the boolean value is thus inverted).ctx.Rule
was removed.ctx.Rule.Params
becomes validator struct fields. The values are passed to the validator constructor.panic
inside validators. If you need to report an error, usectx.AddError()
. This error will be brought up in the returned values ofvalidation.Validate()
.MessagePlaceholders(*validation.Context) []string
.:field
placeholder remains unchanged.validation.Context
now allow nested validation and batch array validation:ctx.AddArrayElementValidationErrors()
marks a child element to the field currently under validation as invalid.ctx.AddValidationError()
adds a validation error message at the given path.ctx.AddValidationErrors()
adds a*validation.Errors
entire object to be merged into the errors bag of the parent validation.ctx.Path()
returns the exact path to the element under validation.validation.GetFieldType()
now returns constants. Thebool
field type is now supported too.Structure conversion and mapping
The use of DTO (Data Transfer Object) and model mapping is now encouraged. This mechanism will help separate the data layer, reduce the risk of sensitive information leaks, and ease the communication between the different layers of your application. Working with strongly-typed structures
In short:
Changes:
request.ToStruct()
was removed.reflectutil
package was removed.typeutil.ToFloat64()
andtypeutil.ToString()
were removed.typeutil.Convert()
new function converts anything into the desired type using JSON marshaling and unmarshaling. This allows raw validated request data to be safely converted to a DTO, or a model to be converted to a DTO.typeutil.MustConvert()
does the same but panics in case of error.typeutil.Copy()
new function deep-copies a DTO's non-zero fields into the given model. It is used for model mapping.Examples:
typeutil.Undefined
is a utility type wrapping a generic value that can be used to differentiate between the absence of a field and its zero value, without using pointers. This handy type will help you easily handle optional fields in requests, because "undefined" is different fromnil
, which is also different from the zero-value of a type. All fields that are not required in a validated request should use this type in their corresponding DTO. A field that wasn't provided by the user in their request will not be copied by model mapping.Authentication
Authentication was one of those features in Goyave that was using a bit too much magic and unnecessary reflection. After re-designing the architecture with layer separation in mind, it became clear that authentication wasn't meeting the new criteria. Indeed, authentication is part of the HTTP layer, but it was using the database, and made it impossible to detach this logic and move it to a repository. The package was therefore redesigned to respect the new requirements.
auth.Authenticator
and the authentication middleware now take a generic parameter representing the authenticated user's DTO.UserService[T]
interface. This service should implement a method that fetches the user by it's "username". This allows moving the database logic to repositories and keeping the single responsibility of authenticators correctly scoped to their own layer. Struct tagsauth:"username"
andauth:"password"
are not used anymore and can be removed.Authenticate()
method now return a*T
instead of taking a model as parameter and updating it in place.auth.FindColumns
was removed.*auth.JWTService
, identified by the nameauth.JWTServiceName
.JWTAuthenticator
orJWTController
.JWTAuthenticator
now stores a valid token's claims inrequest.Extra[auth.ExtraJWTClaims{}]
.auth.MetaAuth
route meta to know if the route matched requires authentication. This means the authentication middleware can be used as a global middleware and you have easy fine control over which route or subrouter requires or doesn't require authentication.auth.JWTController
:auth.TokenFunc
instead of a user of typeany
.UsernameField
andPasswordField
were renamed toUsernameRequestField
andPasswordRequestField
respectively.PasswordField
defines the name of theT
's struct field that holds the user's hashed password.auth.JWTRoutes()
was removed. Theauth.JWTController
implementsRegisterRoutes()
to make the login route register automatically.Database
Database is an important component that can be used in many places. It was important to make sure this dependency was not global anymore. It has been decided to remove some of its features to incentivise developers to use better and more sustainable solutions.
database.GetConnection()
,database.Conn()
,database.Close()
anddatabase.SetConnection()
were removed.database.Migrate()
was removed. Using automatic migrations is now discouraged.database.RegisterModel()
,database.GetRegisteredModels()
,ClearRegisteredModels()
were removed. Registering models was only useful for auto-migrations and the test suite, which was also removed in favor of better solutions.database.View
was removed because it isn't of any use anymore after the testing changes.database.AddInitializer()
anddatabase.ClearInitializers()
) were removed.database.New()
creates a new database instance using the given*config.Config
.database.NewFromDialector()
creates a new database instance using a custom dialector. This can be used when writing tests involving database mocking.database.TimeoutPlugin
is a new plugin added by default. It defines a timeout for Gorm operations. It can be configured with thedatabase.defaultReadQueryTimeout
anddatabase.defaultWriteQueryTimeout
configuration entries. Using a timeout context for the query will override the default timeout.context.Context
for database queries execution.database.Paginator
:database.NewPaginator()
.database.PaginatorDTO[T]
is a new structure that can be used to write a pagination response.database.Paginator
can be converted to adatabase.PaginatorDTO
easily withtypeutil.Convert()
.paginator.Find()
now returns anerror
instead of a*gorm.DB
.paginator.UpdatePageInfo()
now returns anerror
.Session
The new session mechanism is an abstraction over a transaction system allowing services to define and control business transactions without directly interacting with the database. This system allows services to depend on multiple repositories and/or services, and call them seemlessly in the same transaction without having to develop them in a specific way and without worrying about side-effects.
Sessions can be mocked too. This will help you test those more complex cases where something fails during a business transaction.
Localization
The simple localization system in Goyave already works for most cases so it didn't went through a big overhaul. It was mostly refactored for easier use and greater openness.
*lang.Languages
contains multiple loaded languages and defines a default language.*lang.Language
represents a single language and contains the translated text.*lang.Language
or as before through*lang.Languages
by specifying the language name.request.Lang
is now a*lang.Language
instead of astring
. This means you can get a language line directly from the request language:.array
with.element
.object
type now.fields.json
is now amap[string]string
. There is no object with "name" nor "rules" anymore.Logging
Way before Goyave v5 was being designed, the question of better logs was already there for the framework. Instead of several basic loggers, a unified logger with levels would bring many benefits. At the time, many popular logging libraries existed but none of them aligned on a single interface. Go 1.21 introduced
log/slog
, a standard for structured logging. The choice of using this new standard and integrate it into the framework was made.Thanks to structured logging, logs will be easier to read both in development and in production. The framework comes with a custom
slog
handler for development, which formats the logs in a human-readable way. In production, the logs will be output in the JSON format, which is very easy to parse by cloud service providers, making your logs easily readable and filterable.All logs in Goyave v5 are now unified and can take full advantage of log levels (
DEBUG
,INFO
,WARN
,ERROR
). This means that Gorm also uses the same logger as the rest of the application. The format will now be consistent across your entire application.os.Stderr
by default.context.Context
.goyave.Logger
,goyave.ErrLogger
andgoyave.AccessLogger
were removed.goyave.dev/goyave/v5/slog
defines*slog.Logger
, a wrapper around the standard*log/slog.Logger
. This wrapper helps gracefully handle errors and print them with more details.app.debug
) is disabled, the minimum log level is bumped toInfo
and the Gorm logger is disabled.log.Formatter
now takes a*log.Context
instead of many parameters.log.Formatter
now returns a message and a[]slog.Attr
slice instead of just a message.Info
level. The message will remain the same, butslog
attributes will be added to make the log richer.Error handling
In previous versions, error handling was relying on
panic
. This had the advantage of centralizing error handling in the recovery middleware and getting precise stack traces. However, this approach wasn't very sane and not idiomatic. Going forward, the solution will be more idiomatic, easier to test and won't compromise on code quality, while improving the DX even more.Developers are now encouraged to return errors all the way up the call stack. At the highest level, the HTTP handlers will report errors with
response.Error()
. Error handling will still be centralized in a status handler to make error reporting easier.A new error wrapping mechanism was added.
goyave.dev/goyave/v5/util/errors
brings the*errors.Error
type, which provide a convenient way to enrich errors with information useful for debugging and error reporting:string
, astruct
, anothererror
,map
,[]error
,[]any
, etc.*errors.Error
is handled by the structured logger, which results in more detailed logs.Request parsing
Request parsing was one of the rigid features that were convenient in simple cases, but hard to work around for advanced usage. In its redesign, the focus was put on allowing finer control over it.
MaxUploadSize
) if you need to locally override the configuration entryserver.maxUploadSize
.request.Data
andrequest.Query
.request.Data
is nowany
. It previously always was an object (map[string]any
).request.Query
is amap[string]any
.Compression
middleware.Gzip()
was replaced by the newgoyave.dev/goyave/v5/middleware/compress
package.compress.Middleware
provides a generic basis for all types of compression and accepts multiple encoders. The encoder will be chosen depending on the request'sAccept-Encoding
header.Tests
As explained earlier in the release notes, tests were suffering from many flaws making them incredibly inconvenient, slow, hard to maintain. Proper unitary tests were not possible neither because of all the dependencies imposed by the previous versions of the framework.
GOYAVE_ENV
environment variable is not set automatically anymore.goyave.TestSuite
and other testing utilities were removed. The remaining ones were moved to thetestutil
package.testutil
package contains the new testing utilities.testutil.NewTestServer
ortestutil.NewTestServerWithOptions
.*goyave.Server
. They expose useful methods that allows you to test a request without starting the server and listening to a port or test a middleware. It can be passed around as the parent server for all your components.t.Log()
).*goyave.Server
can run in transaction mode withserver.Transaction()
. All the SQL queries will be executed from inside a transaction that can be rolled back. Tests involving the database should use this feature to isolate each test and avoid conflicts or race conditions at the database level.server.ReplaceDB()
.testutil.ReadJSONBody()
new function helps reading and unmarshalling a test response body.testutil.ToJSON()
new function is a shortcut to marshal anything and create a reader from the result. It can be used for HTTP request tests.testutil.CreateTestFiles
now take a file system as parameter, allowing to use embedded files or mocked file systems.any
.Save()
andGenerate()
.mergo
.http.Request
generated with the standardhttptest
package instead of multiple custom methods (Get()
,Post()
, etc).Raw data exploration
Raw data exploration is mainly used in validation. The framework now allows any type of data to be sent as the root element of a request, so this mechanism had to be slightly changed to support that. On top of that, many methods were added to make exploration and comparison more convenient in validators, where they will be used more frequently with the revamp.
Raw data exploration using the
goyave.dev/goyave/v5/util/walk
package has received minor changes:*Path.Name
is now a*string
instead ofstring
to better handle elements that don't have a name (such as array elements).[]
or[].field
).walk.Path.Walk()
callback now receives a*walk.Context
instead ofwalk.Context
walk.Path.First()
new method returns the*walk.Context
for the first final element that matches the path.walk.Path.Depth()
new method returns the depth of the path.walk.Path.Truncate()
new method returns a clone of the n first steps of the path so the returned path's depth equals the given depth.*walk.Path
now implements aString()
methods that returns a string representation of the path.walk.Context
's new methodBreak()
indicates the walker to stop early.walk.MustParse()
new function returns a*walk.Path
or panics if there is an error.*
can now be used to explore all the properties of an object.Websocket
The new architectural changes were an opportunity to make websockets more in line with how regular controllers would be implemented.
server.websocketCloseTimeout
entry.websocket.Conn.CloseWithError()
doesn't change the returned message to the error message if debug is enabled. The message will always be "Internal Server Error".websocket.Controller
instead of lone handlers. Controllers must implement a methodServe(*websocket.Conn, *goyave.Request) error
. This way, websocket handlers also benefit from the fact they are components.websocket.Registrer
interface to manually register the upgrade route if they want to.websocket.New(controller)
.UpgradeErrorHandler
,ErrorHandler
,CheckOrigin
,Headers
are replaced with interfaces that can be implemented by the controller.Filters
The filters library didn't allow decoupling of the HTTP layer and the data layer because of its dependency to the
*goyave.Request
. It was therefore impossible to move its uses to a repository, where they belongs. By creating a DTO for this specific use and changing the error handling, filters can now be properly integrated in the new architecture. They were also upgraded to take advantage of the generics.filter.Settings
,filter.Scope()
,filter.ScopeUnpaginated()
now take a generic parameter representing the model being filtered.filter.Settings.CaseInsensitiveSort
new option wraps the sort values inLOWER()
if the column is a string, resulting inORDER BY LOWER(column)
.filter.Settings.DefaultSort
new option allows to define a list of sorts to be used if the user request didn't contain any sort.filter.Settings.Scope()
,filter.Settings.ScopeUnpaginated()
,filter.Scope()
,filter.ScopeUnpaginated()
now take a*filter.Request
instead of a*goyave.Request
. This request can be created from a HTTP query with the new functionfilter.NewRequest(query)
.filter.Settings.Scope()
andfilter.Scope()
now return anerror
instead of a*gorm.DB
.filter.Filter.Scope()
,filter.Join.Scope()
,filter.Sort.Scope()
now take afilter.Blacklist
instead of*filter.Settings
.filter.Validation
is now agoyave.RuleSetFunc
, and should be used withroute.ValidateQuery()
.fields
in a request validated withfilter.Validation
will now always be a[]string
.Miscellaneous
util/sliceutil
package was removed. Usesamber/lo
instead.goyave.EnableMaintenance()
,goyave.DisableMaintenance()
andgoyave.IsMaintenanceEnabled()
were removed. Their use-case was rare. They were removed to reduce bloat. This kind of maintenance mode should be handled by the proxy, not the application.utils.Map
was removed.ratelimit
package was removed. This implementation couldn't be used in a realistic production environment and didn't cover critical aspects of a real application.path.Join()
instead of concatenation of paths.This discussion was created from the release Release v5.0.0.
Beta Was this translation helpful? Give feedback.
All reactions