parent
f0bd973011
commit
2dc991f552
@ -0,0 +1,16 @@
|
||||
package dorm
|
||||
|
||||
import (
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
)
|
||||
|
||||
type ConfigBeegoClient struct {
|
||||
Dns string // 地址
|
||||
}
|
||||
|
||||
// BeegoClient
|
||||
// https://beego.vip/
|
||||
type BeegoClient struct {
|
||||
Db *orm.Ormer // 驱动
|
||||
config *ConfigBeegoClient // 配置
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package dorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
func NewBeegoMysqlClient(config *ConfigBeegoClient) (*BeegoClient, error) {
|
||||
|
||||
var err error
|
||||
c := &BeegoClient{config: config}
|
||||
|
||||
err = orm.RegisterDriver("mysql", orm.DRMySQL)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("加载驱动失败:%v", err))
|
||||
}
|
||||
|
||||
var db *sql.DB
|
||||
o, err := orm.NewOrmWithDB("mysql", "default", db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Db = &o
|
||||
|
||||
return c, nil
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package dorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
func NewBeegoOracleClient(config *ConfigBeegoClient) (*BeegoClient, error) {
|
||||
|
||||
var err error
|
||||
c := &BeegoClient{config: config}
|
||||
|
||||
err = orm.RegisterDriver("oracle", orm.DROracle)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("加载驱动失败:%v", err))
|
||||
}
|
||||
|
||||
var db *sql.DB
|
||||
o, err := orm.NewOrmWithDB("oracle", "default", db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Db = &o
|
||||
|
||||
return c, nil
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package dorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
func NewBeegoPgsqlClient(config *ConfigBeegoClient) (*BeegoClient, error) {
|
||||
|
||||
var err error
|
||||
c := &BeegoClient{config: config}
|
||||
|
||||
err = orm.RegisterDriver("pgsql", orm.DRPostgres)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("加载驱动失败:%v", err))
|
||||
}
|
||||
|
||||
var db *sql.DB
|
||||
o, err := orm.NewOrmWithDB("pgsql", "default", db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Db = &o
|
||||
|
||||
return c, nil
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package dorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
func NewBeegoSqliteClient(config *ConfigBeegoClient) (*BeegoClient, error) {
|
||||
|
||||
var err error
|
||||
c := &BeegoClient{config: config}
|
||||
|
||||
err = orm.RegisterDriver("sqlite", orm.DRSqlite)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("加载驱动失败:%v", err))
|
||||
}
|
||||
|
||||
var db *sql.DB
|
||||
o, err := orm.NewOrmWithDB("sqlite", "default", db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Db = &o
|
||||
|
||||
return c, nil
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package dorm
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
func NewBeegoTidbClient(config *ConfigBeegoClient) (*BeegoClient, error) {
|
||||
|
||||
var err error
|
||||
c := &BeegoClient{config: config}
|
||||
|
||||
err = orm.RegisterDriver("TiDB", orm.DRTiDB)
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("加载驱动失败:%v", err))
|
||||
}
|
||||
|
||||
var db *sql.DB
|
||||
o, err := orm.NewOrmWithDB("TiDB", "default", db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Db = &o
|
||||
|
||||
return c, nil
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package dorm
|
||||
|
||||
import (
|
||||
"entgo.io/ent"
|
||||
)
|
||||
|
||||
type ConfigEntClient struct {
|
||||
Dns string // 地址
|
||||
}
|
||||
|
||||
// EntClient
|
||||
// https://entgo.io/
|
||||
type EntClient struct {
|
||||
Db *ent.Edge // 驱动
|
||||
config *ConfigEntClient // 配置
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package dorm
|
||||
|
||||
import (
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
func NewEntMysqlClient(config *ConfigEntClient) (*EntClient, error) {
|
||||
|
||||
//var err error
|
||||
//c := &EntClient{config: config}
|
||||
|
||||
//client, err := ent.Open("mysql", c.config.Dns)
|
||||
//if err != nil {
|
||||
// return nil, errors.New(fmt.Sprintf("连接失败:%v", err))
|
||||
//}
|
||||
//defer client.Close()
|
||||
|
||||
return nil, nil
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package dorm
|
||||
|
||||
import (
|
||||
"github.com/kamva/mgm/v3"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type ConfigMgmClient struct {
|
||||
Opts *options.ClientOptions
|
||||
DatabaseName string // 库名
|
||||
}
|
||||
|
||||
type MgmClient struct {
|
||||
config *ConfigMgmClient // 配置
|
||||
}
|
||||
|
||||
func NewMgmClient(config *ConfigMgmClient) (*MgmClient, error) {
|
||||
|
||||
c := &MgmClient{config: config}
|
||||
|
||||
_ = mgm.SetDefaultConfig(nil, c.config.DatabaseName, c.config.Opts)
|
||||
|
||||
return c, nil
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
run:
|
||||
go: '1.17'
|
||||
timeout: 5m
|
||||
|
||||
linters-settings:
|
||||
errcheck:
|
||||
ignore: fmt:.*,Read|Write|Close|Exec,io:Copy
|
||||
dupl:
|
||||
threshold: 100
|
||||
funlen:
|
||||
lines: 115
|
||||
statements: 115
|
||||
goheader:
|
||||
template: |-
|
||||
Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
This source code is licensed under the Apache 2.0 license found
|
||||
in the LICENSE file in the root directory of this source tree.
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- bodyclose
|
||||
- deadcode
|
||||
- depguard
|
||||
- dogsled
|
||||
- dupl
|
||||
- errcheck
|
||||
- funlen
|
||||
- gocritic
|
||||
- gofmt
|
||||
- goheader
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- misspell
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- stylecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unused
|
||||
- varcheck
|
||||
- whitespace
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- dupl
|
||||
- funlen
|
||||
- gosec
|
||||
- gocritic
|
||||
- linters:
|
||||
- unused
|
||||
source: ent.Schema
|
||||
- path: dialect/sql/schema
|
||||
linters:
|
||||
- dupl
|
||||
- gosec
|
||||
- text: "Expect WriteFile permissions to be 0600 or less"
|
||||
linters:
|
||||
- gosec
|
||||
- path: privacy/privacy.go
|
||||
linters:
|
||||
- stylecheck
|
||||
- path: entc/load/schema.go
|
||||
linters:
|
||||
- staticcheck
|
||||
- path: entc/gen/graph.go
|
||||
linters:
|
||||
- gocritic
|
||||
- path: entc/integration/multischema/multischema_test.go
|
||||
linters:
|
||||
- staticcheck
|
||||
text: SA1019
|
@ -0,0 +1,77 @@
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make 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 within all project spaces, and it also applies when
|
||||
an individual is representing the project or its community in public spaces.
|
||||
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 <opensource-conduct@fb.com>. 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
|
||||
|
@ -0,0 +1,69 @@
|
||||
# Contributing to ent
|
||||
We want to make contributing to this project as easy and transparent as
|
||||
possible.
|
||||
|
||||
# Project structure
|
||||
|
||||
- `dialect` - Contains SQL and Gremlin code used by the generated code.
|
||||
- `dialect/sql/schema` - Auto migration logic resides there.
|
||||
|
||||
- `schema` - User schema API.
|
||||
- `schema/{field, edge, index, mixin}` - provides schema builders API.
|
||||
- `schema/field/gen` - Templates and codegen for numeric builders.
|
||||
|
||||
- `entc` - Codegen of `ent`.
|
||||
- `entc/load` - `entc` loader API for loading user schemas into a Go objects at runtime.
|
||||
- `entc/gen` - The actual code generation logic resides in this package (and its `templates` package).
|
||||
- `integration` - Integration tests for `entc`.
|
||||
|
||||
- `privacy` - Runtime code for [privacy layer](https://entgo.io/docs/privacy/).
|
||||
|
||||
- `doc` - Documentation code for `entgo.io` (uses [Docusaurus](https://docusaurus.io)).
|
||||
- `doc/md` - Markdown files for documentation.
|
||||
- `doc/website` - Website code and assets.
|
||||
|
||||
In order to test your documentation changes, run `npm start` from the `doc/website` directory, and open [localhost:3000](http://localhost:3000/).
|
||||
|
||||
# Run integration tests
|
||||
If you touch any file in `entc`, run the following command in `entc`:
|
||||
|
||||
```
|
||||
go generate ./...
|
||||
```
|
||||
|
||||
Then, in `entc/integration` run `docker-compose` in order to spin-up all database containers:
|
||||
|
||||
```
|
||||
docker-compose -f docker-compose.yaml up -d
|
||||
```
|
||||
|
||||
Then, run `go test ./...` to run all integration tests.
|
||||
|
||||
|
||||
## Pull Requests
|
||||
We actively welcome your pull requests.
|
||||
|
||||
1. Fork the repo and create your branch from `master`.
|
||||
2. If you've added code that should be tested, add tests.
|
||||
3. If you've changed APIs, update the documentation.
|
||||
4. Ensure the test suite passes.
|
||||
5. Make sure your code lints.
|
||||
6. If you haven't already, complete the Contributor License Agreement ("CLA").
|
||||
|
||||
## Contributor License Agreement ("CLA")
|
||||
In order to accept your pull request, we need you to submit a CLA. You only need
|
||||
to do this once to work on any of Facebook's open source projects.
|
||||
|
||||
Complete your CLA here: <https://code.facebook.com/cla>
|
||||
|
||||
## Issues
|
||||
We use GitHub issues to track public bugs. Please ensure your description is
|
||||
clear and has sufficient instructions to be able to reproduce the issue.
|
||||
|
||||
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
|
||||
disclosure of security bugs. In those cases, please go through the process
|
||||
outlined on that page and do not file a public issue.
|
||||
|
||||
## License
|
||||
By contributing to ent, you agree that your contributions will be licensed
|
||||
under the LICENSE file in the root directory of this source tree.
|
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -0,0 +1,58 @@
|
||||
## ent - An Entity Framework For Go
|
||||
|
||||
[![Twitter](https://img.shields.io/twitter/url/https/twitter.com/entgo_io.svg?style=social&label=Follow%20%40entgo_io)](https://twitter.com/entgo_io)
|
||||
[![Discord](https://img.shields.io/discord/885059418646003782?label=discord&logo=discord&style=flat-square&logoColor=white)](https://discord.gg/qZmPgTE6RX)
|
||||
|
||||
[English](README.md) | [中文](README_zh.md) | [日本語](README_jp.md)
|
||||
|
||||
<img width="50%"
|
||||
align="right"
|
||||
style="display: block; margin:40px auto;"
|
||||
src="https://s3.eu-central-1.amazonaws.com/entgo.io/assets/gopher_graph.png"/>
|
||||
|
||||
Simple, yet powerful entity framework for Go, that makes it easy to build and maintain applications
|
||||
with large data-models.
|
||||
|
||||
- **Schema As Code** - model any database schema as Go objects.
|
||||
- **Easily Traverse Any Graph** - run queries, aggregations and traverse any graph structure easily.
|
||||
- **Statically Typed And Explicit API** - 100% statically typed and explicit API using code generation.
|
||||
- **Multi Storage Driver** - supports MySQL, MariaDB, TiDB, PostgreSQL, CockroachDB, SQLite and Gremlin.
|
||||
- **Extendable** - simple to extend and customize using Go templates.
|
||||
|
||||
## Quick Installation
|
||||
```console
|
||||
go install entgo.io/ent/cmd/ent@latest
|
||||
```
|
||||
|
||||
For proper installation using [Go modules], visit [entgo.io website][entgo instal].
|
||||
|
||||
## Docs and Support
|
||||
The documentation for developing and using ent is available at: https://entgo.io
|
||||
|
||||
For discussion and support, [open an issue](https://github.com/ent/ent/issues/new/choose) or join our [channel](https://gophers.slack.com/archives/C01FMSQDT53) in the gophers Slack.
|
||||
|
||||
## Join the ent Community
|
||||
Building `ent` would not have been possible without the collective work of our entire community. We maintain a [contributors page](doc/md/contributors.md)
|
||||
which lists the contributors to this `ent`.
|
||||
|
||||
In order to contribute to `ent`, see the [CONTRIBUTING](CONTRIBUTING.md) file for how to go get started.
|
||||
If your company or your product is using `ent`, please let us know by adding yourself to the [ent users page](https://github.com/ent/ent/wiki/ent-users).
|
||||
|
||||
For updates, follow us on Twitter at https://twitter.com/entgo_io
|
||||
|
||||
|
||||
|
||||
## About the Project
|
||||
The `ent` project was inspired by Ent, an entity framework we use internally. It is developed and maintained
|
||||
by [a8m](https://github.com/a8m) and [alexsn](https://github.com/alexsn)
|
||||
from the [Facebook Connectivity][fbc] team. It is used by multiple teams and projects in production,
|
||||
and the roadmap for its v1 release is described [here](https://github.com/ent/ent/issues/46).
|
||||
Read more about the motivation of the project [here](https://entgo.io/blog/2019/10/03/introducing-ent).
|
||||
|
||||
## License
|
||||
ent is licensed under Apache 2.0 as found in the [LICENSE file](LICENSE).
|
||||
|
||||
|
||||
[entgo instal]: https://entgo.io/docs/code-gen/#version-compatibility-between-entc-and-ent
|
||||
[Go modules]: https://github.com/golang/go/wiki/Modules#quick-start
|
||||
[fbc]: https://connectivity.fb.com
|
@ -0,0 +1,54 @@
|
||||
## ent - Goのエンティティーフレームワーク
|
||||
|
||||
[![Twitter](https://img.shields.io/twitter/url/https/twitter.com/entgo_io.svg?style=social&label=Follow%20%40entgo_io)](https://twitter.com/entgo_io)
|
||||
|
||||
[English](README.md) | [中文](README_zh.md) | [日本語](README_jp.md)
|
||||
|
||||
<img width="50%"
|
||||
align="right"
|
||||
style="display: block; margin:40px auto;"
|
||||
src="https://s3.eu-central-1.amazonaws.com/entgo.io/assets/gopher_graph.png"/>
|
||||
|
||||
シンプルながらもパワフルなGoのエンティティフレームワークであり、大規模なデータモデルを持つアプリケーションを容易に構築・保守できるようにします。
|
||||
|
||||
- **Schema As Code(コードとしてのスキーマ)** - あらゆるデータベーススキーマをGoオブジェクトとしてモデル化します。
|
||||
- **任意のグラフを簡単にトラバースできます** - クエリや集約の実行、任意のグラフ構造の走査を容易に実行できます。
|
||||
- **100%静的に型付けされた明示的なAPI** - コード生成により、100%静的に型付けされた曖昧さのないAPIを提供します。
|
||||
- **マルチストレージドライバ** - MySQL、PostgreSQL、SQLite、Gremlinをサポートしています。
|
||||
- **拡張性** - Goテンプレートを使用して簡単に拡張、カスタマイズできます。
|
||||
|
||||
## クイックインストール
|
||||
```console
|
||||
go install entgo.io/ent/cmd/ent@latest
|
||||
```
|
||||
|
||||
[Go modules]を使ったインストールについては、[entgo.io website][entgo instal]をご覧ください。
|
||||
|
||||
## ドキュメントとサポート
|
||||
entを開発・使用するためのドキュメントは、こちら。: https://entgo.io
|
||||
|
||||
議論やサポートについては、[Issueを立てる](https://github.com/ent/ent/issues/new/choose)か、gophers Slackの[チャンネル](https://gophers.slack.com/archives/C01FMSQDT53)に参加してください。
|
||||
|
||||
## entコミュニティへの参加
|
||||
`ent`の構築は、コミュニティ全体の協力なしには実現できませんでした。 私たちは、この`ent`の貢献者をリストアップした[contributorsページ](doc/md/contributors.md)を管理しています。
|
||||
|
||||
`ent`に貢献するときは、まず[CONTRIBUTING](CONTRIBUTING.md)を参照してください。
|
||||
もし、あなたの会社や製品で`ent`を利用している場合は、[ent usersページ](https://github.com/ent/ent/wiki/ent-users)に追記する形で、そのことをぜひ教えて下さい。
|
||||
|
||||
最新情報については、Twitter(<https://twitter.com/entgo_io>)をフォローしてください。
|
||||
|
||||
|
||||
|
||||
## プロジェクトについて
|
||||
`ent`プロジェクトは、私たちが社内で使用しているエンティティフレームワークである`Ent`からインスピレーションを得ています。
|
||||
entは、[Facebook Connectivity][fbc]チームの[a8m](https://github.com/a8m)と[alexsn](https://github.com/alexsn)が開発・保守しています。
|
||||
本番環境では複数のチームやプロジェクトで使用されており、v1リリースまでのロードマップは[こちら](https://github.com/ent/ent/issues/46)に記載されています。
|
||||
このプロジェクトの動機については[こちら](https://entgo.io/blog/2019/10/03/introducing-ent)をご覧ください。
|
||||
|
||||
## ライセンス
|
||||
entは、[LICENSEファイル](LICENSE)にもある通り、Apache 2.0でライセンスされています。
|
||||
|
||||
|
||||
[entgo instal]: https://entgo.io/docs/code-gen/#version-compatibility-between-entc-and-ent
|
||||
[Go modules]: https://github.com/golang/go/wiki/Modules#quick-start
|
||||
[fbc]: https://connectivity.fb.com
|
@ -0,0 +1,365 @@
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
// Package ent is the interface between end-user schemas and entc (ent codegen).
|
||||
package ent
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"entgo.io/ent/schema"
|
||||
"entgo.io/ent/schema/edge"
|
||||
"entgo.io/ent/schema/field"
|
||||
"entgo.io/ent/schema/index"
|
||||
)
|
||||
|
||||
type (
|
||||
// The Interface type describes the requirements for an exported type defined in the schema package.
|
||||
// It functions as the interface between the user's schema types and codegen loader.
|
||||
// Users should use the Schema type for embedding as follows:
|
||||
//
|
||||
// type T struct {
|
||||
// ent.Schema
|
||||
// }
|
||||
//
|
||||
Interface interface {
|
||||
// Type is a dummy method, that is used in edge declaration.
|
||||
//
|
||||
// The Type method should be used as follows:
|
||||
//
|
||||
// type S struct { ent.Schema }
|
||||
//
|
||||
// type T struct { ent.Schema }
|
||||
//
|
||||
// func (T) Edges() []ent.Edge {
|
||||
// return []ent.Edge{
|
||||
// edge.To("S", S.Type),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
Type()
|
||||
// Fields returns the fields of the schema.
|
||||
Fields() []Field
|
||||
// Edges returns the edges of the schema.
|
||||
Edges() []Edge
|
||||
// Indexes returns the indexes of the schema.
|
||||
Indexes() []Index
|
||||
// Config returns an optional config for the schema.
|
||||
//
|
||||
// Deprecated: the Config method predates the Annotations method, and it
|
||||
// is planned be removed in v0.5.0. New code should use Annotations instead.
|
||||
//
|
||||
// func (T) Annotations() []schema.Annotation {
|
||||
// return []schema.Annotation{
|
||||
// entsql.Annotation{Table: "Name"},
|
||||
// }
|
||||
// }
|
||||
//
|
||||
Config() Config
|
||||
// Mixin returns an optional list of Mixin to extends
|
||||
// the schema.
|
||||
Mixin() []Mixin
|
||||
// Hooks returns an optional list of Hook to apply on
|
||||
// mutations.
|
||||
Hooks() []Hook
|
||||
// Policy returns the privacy policy of the schema.
|
||||
Policy() Policy
|
||||
// Annotations returns a list of schema annotations to be used by
|
||||
// codegen extensions.
|
||||
Annotations() []schema.Annotation
|
||||
}
|
||||
|
||||
// A Field interface returns a field descriptor for vertex fields/properties.
|
||||
// The usage for the interface is as follows:
|
||||
//
|
||||
// func (T) Fields() []ent.Field {
|
||||
// return []ent.Field{
|
||||
// field.Int("int"),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
Field interface {
|
||||
Descriptor() *field.Descriptor
|
||||
}
|
||||
|
||||
// An Edge interface returns an edge descriptor for vertex edges.
|
||||
// The usage for the interface is as follows:
|
||||
//
|
||||
// func (T) Edges() []ent.Edge {
|
||||
// return []ent.Edge{
|
||||
// edge.To("S", S.Type),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
Edge interface {
|
||||
Descriptor() *edge.Descriptor
|
||||
}
|
||||
|
||||
// An Index interface returns an index descriptor for vertex indexes.
|
||||
// The usage for the interface is as follows:
|
||||
//
|
||||
// func (T) Indexes() []ent.Index {
|
||||
// return []ent.Index{
|
||||
// index.Fields("f1", "f2").
|
||||
// Unique(),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
Index interface {
|
||||
Descriptor() *index.Descriptor
|
||||
}
|
||||
|
||||
// A Config structure is used to configure an entity schema.
|
||||
// The usage of this structure is as follows:
|
||||
//
|
||||
// func (T) Config() ent.Config {
|
||||
// return ent.Config{
|
||||
// Table: "Name",
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Deprecated: the Config object predates the schema.Annotation method and it
|
||||
// is planned be removed in v0.5.0. New code should use Annotations instead.
|
||||
//
|
||||
// func (T) Annotations() []schema.Annotation {
|
||||
// return []schema.Annotation{
|
||||
// entsql.Annotation{Table: "Name"},
|
||||
// }
|
||||
// }
|
||||
//
|
||||
Config struct {
|
||||
// A Table is an optional table name defined for the schema.
|
||||
Table string
|
||||
}
|
||||
|
||||
// The Mixin type describes a set of methods that can extend
|
||||
// other methods in the schema without calling them directly.
|
||||
//
|
||||
// type TimeMixin struct {}
|
||||
//
|
||||
// func (TimeMixin) Fields() []ent.Field {
|
||||
// return []ent.Field{
|
||||
// field.Time("created_at").
|
||||
// Immutable().
|
||||
// Default(time.Now),
|
||||
// field.Time("updated_at").
|
||||
// Default(time.Now).
|
||||
// UpdateDefault(time.Now),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// type T struct {
|
||||
// ent.Schema
|
||||
// }
|
||||
//
|
||||
// func(T) Mixin() []ent.Mixin {
|
||||
// return []ent.Mixin{
|
||||
// TimeMixin{},
|
||||
// }
|
||||
// }
|
||||
//
|
||||
Mixin interface {
|
||||
// Fields returns a slice of fields to add to the schema.
|
||||
Fields() []Field
|
||||
// Edges returns a slice of edges to add to the schema.
|
||||
Edges() []Edge
|
||||
// Indexes returns a slice of indexes to add to the schema.
|
||||
Indexes() []Index
|
||||
// Hooks returns a slice of hooks to add to the schema.
|
||||
// Note that mixin hooks are executed before schema hooks.
|
||||
Hooks() []Hook
|
||||
// Policy returns a privacy policy to add to the schema.
|
||||
// Note that mixin policy are executed before schema policy.
|
||||
Policy() Policy
|
||||
// Annotations returns a list of schema annotations to add
|
||||
// to the schema annotations.
|
||||
Annotations() []schema.Annotation
|
||||
}
|
||||
|
||||
// The Policy type defines the write privacy policy of an entity.
|
||||
// The usage for the interface is as follows:
|
||||
//
|
||||
// type T struct {
|
||||
// ent.Schema
|
||||
// }
|
||||
//
|
||||
// func(T) Policy() ent.Policy {
|
||||
// return privacy.AlwaysAllowReadWrite()
|
||||
// }
|
||||
//
|
||||
Policy interface {
|
||||
EvalMutation(context.Context, Mutation) error
|
||||
EvalQuery(context.Context, Query) error
|
||||
}
|
||||
|
||||
// Schema is the default implementation for the schema Interface.
|
||||
// It can be embedded in end-user schemas as follows:
|
||||
//
|
||||
// type T struct {
|
||||
// ent.Schema
|
||||
// }
|
||||
//
|
||||
Schema struct {
|
||||
Interface
|
||||
}
|
||||
)
|
||||
|
||||
// Fields of the schema.
|
||||
func (Schema) Fields() []Field { return nil }
|
||||
|
||||
// Edges of the schema.
|
||||
func (Schema) Edges() []Edge { return nil }
|
||||
|
||||
// Indexes of the schema.
|
||||
func (Schema) Indexes() []Index { return nil }
|
||||
|
||||
// Config of the schema.
|
||||
func (Schema) Config() Config { return Config{} }
|
||||
|
||||
// Mixin of the schema.
|
||||
func (Schema) Mixin() []Mixin { return nil }
|
||||
|
||||
// Hooks of the schema.
|
||||
func (Schema) Hooks() []Hook { return nil }
|
||||
|
||||
// Policy of the schema.
|
||||
func (Schema) Policy() Policy { return nil }
|
||||
|
||||
// Annotations of the schema.
|
||||
func (Schema) Annotations() []schema.Annotation { return nil }
|
||||
|
||||
type (
|
||||
// Value represents a value returned by ent.
|
||||
Value interface{}
|
||||
// Query represents an ent query builder.
|
||||
Query interface{}
|
||||
// Mutation represents an operation that mutate the graph.
|
||||
// For example, adding a new node, updating many, or dropping
|
||||
// data. The implementation is generated by entc (ent codegen).
|
||||
Mutation interface {
|
||||
// Op returns the operation name generated by entc.
|
||||
Op() Op
|
||||
// Type returns the schema type for this mutation.
|
||||
Type() string
|
||||
|
||||
// Fields returns all fields that were changed during
|
||||
// this mutation. Note that, in order to get all numeric
|
||||
// fields that were in/decremented, call AddedFields().
|
||||
Fields() []string
|
||||
// Field returns the value of a field with the given name.
|
||||
// The second boolean value indicates that this field was
|
||||
// not set, or was not defined in the schema.
|
||||
Field(name string) (Value, bool)
|
||||
// SetField sets the value for the given name. It returns an
|
||||
// error if the field is not defined in the schema, or if the
|
||||
// type mismatch the field type.
|
||||
SetField(name string, value Value) error
|
||||
|
||||
// AddedFields returns all numeric fields that were incremented
|
||||
// or decremented during this mutation.
|
||||
AddedFields() []string
|
||||
// AddedField returns the numeric value that was in/decremented
|
||||
// from a field with the given name. The second value indicates
|
||||
// that this field was not set, or was not define in the schema.
|
||||
AddedField(name string) (Value, bool)
|
||||
// AddField adds the value for the given name. It returns an
|
||||
// error if the field is not defined in the schema, or if the
|
||||
// type mismatch the field type.
|
||||
AddField(name string, value Value) error
|
||||
|
||||
// ClearedFields returns all nullable fields that were cleared
|
||||
// during this mutation.
|
||||
ClearedFields() []string
|
||||
// FieldCleared returns a bool indicates if this field was
|
||||
// cleared in this mutation.
|
||||
FieldCleared(name string) bool
|
||||
// ClearField clears the value for the given name. It returns an
|
||||
// error if the field is not defined in the schema.
|
||||
ClearField(name string) error
|
||||
|
||||
// ResetField resets all changes in the mutation regarding the
|
||||
// given field name. It returns an error if the field is not
|
||||
// defined in the schema.
|
||||
ResetField(name string) error
|
||||
|
||||
// AddedEdges returns all edge names that were set/added in this
|
||||
// mutation.
|
||||
AddedEdges() []string
|
||||
// AddedIDs returns all ids (to other nodes) that were added for
|
||||
// the given edge name.
|
||||
AddedIDs(name string) []Value
|
||||
|
||||
// RemovedEdges returns all edge names that were removed in this
|
||||
// mutation.
|
||||
RemovedEdges() []string
|
||||
// RemovedIDs returns all ids (to other nodes) that were removed for
|
||||
// the given edge name.
|
||||
RemovedIDs(name string) []Value
|
||||
|
||||
// ClearedEdges returns all edge names that were cleared in this
|
||||
// mutation.
|
||||
ClearedEdges() []string
|
||||
// EdgeCleared returns a bool indicates if this edge was
|
||||
// cleared in this mutation.
|
||||
EdgeCleared(name string) bool
|
||||
// ClearEdge clears the value for the given name. It returns an
|
||||
// error if the edge name is not defined in the schema.
|
||||
ClearEdge(name string) error
|
||||
|
||||
// ResetEdge resets all changes in the mutation regarding the
|
||||
// given edge name. It returns an error if the edge is not
|
||||
// defined in the schema.
|
||||
ResetEdge(name string) error
|
||||
|
||||
// OldField returns the old value of the field from the database.
|
||||
// An error is returned if the mutation operation is not UpdateOne,
|
||||
// or the query to the database was failed.
|
||||
OldField(ctx context.Context, name string) (Value, error)
|
||||
}
|
||||
|
||||
// Mutator is the interface that wraps the Mutate method.
|
||||
Mutator interface {
|
||||
// Mutate apply the given mutation on the graph.
|
||||
Mutate(context.Context, Mutation) (Value, error)
|
||||
}
|
||||
|
||||
// The MutateFunc type is an adapter to allow the use of ordinary
|
||||
// function as mutator. If f is a function with the appropriate signature,
|
||||
// MutateFunc(f) is a Mutator that calls f.
|
||||
MutateFunc func(context.Context, Mutation) (Value, error)
|
||||
|
||||
// Hook defines the "mutation middleware". A function that gets a Mutator
|
||||
// and returns a Mutator. For example:
|
||||
//
|
||||
// hook := func(next ent.Mutator) ent.Mutator {
|
||||
// return ent.MutateFunc(func(ctx context.Context, m ent.Mutation) (ent.Value, error) {
|
||||
// fmt.Printf("Type: %s, Operation: %s, ConcreteType: %T\n", m.Type(), m.Op(), m)
|
||||
// return next.Mutate(ctx, m)
|
||||
// })
|
||||
// }
|
||||
//
|
||||
Hook func(Mutator) Mutator
|
||||
)
|
||||
|
||||
// Mutate calls f(ctx, m).
|
||||
func (f MutateFunc) Mutate(ctx context.Context, m Mutation) (Value, error) {
|
||||
return f(ctx, m)
|
||||
}
|
||||
|
||||
// An Op represents a mutation operation.
|
||||
type Op uint
|
||||
|
||||
// Mutation operations.
|
||||
const (
|
||||
OpCreate Op = 1 << iota // node creation.
|
||||
OpUpdate // update nodes by predicate (if any).
|
||||
OpUpdateOne // update one node.
|
||||
OpDelete // delete nodes by predicate (if any).
|
||||
OpDeleteOne // delete one node.
|
||||
)
|
||||
|
||||
// Is reports whether o is match the given operation.
|
||||
func (i Op) Is(o Op) bool { return i&o != 0 }
|
||||
|
||||
//go:generate go run golang.org/x/tools/cmd/stringer -type Op
|
@ -0,0 +1,43 @@
|
||||
// Code generated by "stringer -type Op"; DO NOT EDIT.
|
||||
|
||||
package ent
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[OpCreate-1]
|
||||
_ = x[OpUpdate-2]
|
||||
_ = x[OpUpdateOne-4]
|
||||
_ = x[OpDelete-8]
|
||||
_ = x[OpDeleteOne-16]
|
||||
}
|
||||
|
||||
const (
|
||||
_Op_name_0 = "OpCreateOpUpdate"
|
||||
_Op_name_1 = "OpUpdateOne"
|
||||
_Op_name_2 = "OpDelete"
|
||||
_Op_name_3 = "OpDeleteOne"
|
||||
)
|
||||
|
||||
var (
|
||||
_Op_index_0 = [...]uint8{0, 8, 16}
|
||||
)
|
||||
|
||||
func (i Op) String() string {
|
||||
switch {
|
||||
case 1 <= i && i <= 2:
|
||||
i -= 1
|
||||
return _Op_name_0[_Op_index_0[i]:_Op_index_0[i+1]]
|
||||
case i == 4:
|
||||
return _Op_name_1
|
||||
case i == 8:
|
||||
return _Op_name_2
|
||||
case i == 16:
|
||||
return _Op_name_3
|
||||
default:
|
||||
return "Op(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
package edge
|
||||
|
||||
import "entgo.io/ent/schema"
|
||||
|
||||
// Annotation is a builtin schema annotation for
|
||||
// configuring the edges' behavior in codegen.
|
||||
type Annotation struct {
|
||||
// The StructTag option allows overriding the struct-tag
|
||||
// of the `Edges` field in the generated entity. For example:
|
||||
//
|
||||
// edge.Annotation{
|
||||
// StructTag: `json: "pet_edges"`
|
||||
// }
|
||||
//
|
||||
StructTag string
|
||||
}
|
||||
|
||||
// Name describes the annotation name.
|
||||
func (Annotation) Name() string {
|
||||
return "Edges"
|
||||
}
|
||||
|
||||
// Merge implements the schema.Merger interface.
|
||||
func (a Annotation) Merge(other schema.Annotation) schema.Annotation {
|
||||
var ant Annotation
|
||||
switch other := other.(type) {
|
||||
case Annotation:
|
||||
ant = other
|
||||
case *Annotation:
|
||||
if other != nil {
|
||||
ant = *other
|
||||
}
|
||||
default:
|
||||
return a
|
||||
}
|
||||
if tag := ant.StructTag; tag != "" {
|
||||
a.StructTag = tag
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
var (
|
||||
_ schema.Annotation = (*Annotation)(nil)
|
||||
_ schema.Merger = (*Annotation)(nil)
|
||||
)
|
@ -0,0 +1,270 @@
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
package edge
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"entgo.io/ent/schema"
|
||||
)
|
||||
|
||||
// A Descriptor for edge configuration.
|
||||
type Descriptor struct {
|
||||
Tag string // struct tag.
|
||||
Type string // edge type.
|
||||
Name string // edge name.
|
||||
Field string // edge field name (e.g. foreign-key).
|
||||
RefName string // ref name; inverse only.
|
||||
Ref *Descriptor // edge reference; to/from of the same type.
|
||||
Through *struct{ N, T string } // through type and name.
|
||||
Unique bool // unique edge.
|
||||
Inverse bool // inverse edge.
|
||||
Required bool // required on creation.
|
||||
StorageKey *StorageKey // optional storage-key configuration.
|
||||
Annotations []schema.Annotation // edge annotations.
|
||||
Comment string // edge comment.
|
||||
}
|
||||
|
||||
// To defines an association edge between two vertices.
|
||||
func To(name string, t interface{}) *assocBuilder {
|
||||
return &assocBuilder{desc: &Descriptor{Name: name, Type: typ(t)}}
|
||||
}
|
||||
|
||||
// From represents a reversed-edge between two vertices that has a back-reference to its source edge.
|
||||
func From(name string, t interface{}) *inverseBuilder {
|
||||
return &inverseBuilder{desc: &Descriptor{Name: name, Type: typ(t), Inverse: true}}
|
||||
}
|
||||
|
||||
func typ(t interface{}) string {
|
||||
if rt := reflect.TypeOf(t); rt.NumIn() > 0 {
|
||||
return rt.In(0).Name()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// assocBuilder is the builder for assoc edges.
|
||||
type assocBuilder struct {
|
||||
desc *Descriptor
|
||||
}
|
||||
|
||||
// Unique sets the edge type to be unique. Basically, it limits the edge to be one of the two:
|
||||
// one2one or one2many. one2one applied if the inverse-edge is also unique.
|
||||
func (b *assocBuilder) Unique() *assocBuilder {
|
||||
b.desc.Unique = true
|
||||
return b
|
||||
}
|
||||
|
||||
// Required indicates that this edge is a required field on creation.
|
||||
// Unlike fields, edges are optional by default.
|
||||
func (b *assocBuilder) Required() *assocBuilder {
|
||||
b.desc.Required = true
|
||||
return b
|
||||
}
|
||||
|
||||
// StructTag sets the struct tag of the assoc edge.
|
||||
func (b *assocBuilder) StructTag(s string) *assocBuilder {
|
||||
b.desc.Tag = s
|
||||
return b
|
||||
}
|
||||
|
||||
// From creates an inverse-edge with the same type.
|
||||
func (b *assocBuilder) From(name string) *inverseBuilder {
|
||||
return &inverseBuilder{desc: &Descriptor{Name: name, Type: b.desc.Type, Inverse: true, Ref: b.desc}}
|
||||
}
|
||||
|
||||
// Field is used to bind an edge (with a foreign-key) to a field in the schema.
|
||||
//
|
||||
// field.Int("owner_id").
|
||||
// Optional()
|
||||
//
|
||||
// edge.To("owner", User.Type).
|
||||
// Field("owner_id").
|
||||
// Unique(),
|
||||
//
|
||||
func (b *assocBuilder) Field(f string) *assocBuilder {
|
||||
b.desc.Field = f
|
||||
return b
|
||||
}
|
||||
|
||||
// Through allows setting an "edge schema" to interact explicitly with M2M edges.
|
||||
//
|
||||
// edge.To("friends", User.Type).
|
||||
// Through("friendships", Friendship.Type)
|
||||
//
|
||||
func (b *assocBuilder) Through(name string, t interface{}) *assocBuilder {
|
||||
b.desc.Through = &struct{ N, T string }{N: name, T: typ(t)}
|
||||
return b
|
||||
}
|
||||
|
||||
// Comment used to put annotations on the schema.
|
||||
func (b *assocBuilder) Comment(c string) *assocBuilder {
|
||||
b.desc.Comment = c
|
||||
return b
|
||||
}
|
||||
|
||||
// StorageKey sets the storage key of the edge.
|
||||
//
|
||||
// edge.To("groups", Group.Type).
|
||||
// StorageKey(edge.Table("user_groups"), edge.Columns("user_id", "group_id"))
|
||||
//
|
||||
func (b *assocBuilder) StorageKey(opts ...StorageOption) *assocBuilder {
|
||||
if b.desc.StorageKey == nil {
|
||||
b.desc.StorageKey = &StorageKey{}
|
||||
}
|
||||
for i := range opts {
|
||||
opts[i](b.desc.StorageKey)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Annotations adds a list of annotations to the edge object to be used by
|
||||
// codegen extensions.
|
||||
//
|
||||
// edge.To("pets", Pet.Type).
|
||||
// Annotations(entgql.Bind())
|
||||
//
|
||||
func (b *assocBuilder) Annotations(annotations ...schema.Annotation) *assocBuilder {
|
||||
b.desc.Annotations = append(b.desc.Annotations, annotations...)
|
||||
return b
|
||||
}
|
||||
|
||||
// Descriptor implements the ent.Descriptor interface.
|
||||
func (b *assocBuilder) Descriptor() *Descriptor {
|
||||
return b.desc
|
||||
}
|
||||
|
||||
// inverseBuilder is the builder for inverse edges.
|
||||
type inverseBuilder struct {
|
||||
desc *Descriptor
|
||||
}
|
||||
|
||||
// Ref sets the referenced-edge of this inverse edge.
|
||||
func (b *inverseBuilder) Ref(ref string) *inverseBuilder {
|
||||
b.desc.RefName = ref
|
||||
return b
|
||||
}
|
||||
|
||||
// Unique sets the edge type to be unique. Basically, it limits the edge to be one of the two:
|
||||
// one2one or one2many. one2one applied if the inverse-edge is also unique.
|
||||
func (b *inverseBuilder) Unique() *inverseBuilder {
|
||||
b.desc.Unique = true
|
||||
return b
|
||||
}
|
||||
|
||||
// Required indicates that this edge is a required field on creation.
|
||||
// Unlike fields, edges are optional by default.
|
||||
func (b *inverseBuilder) Required() *inverseBuilder {
|
||||
b.desc.Required = true
|
||||
return b
|
||||
}
|
||||
|
||||
// StructTag sets the struct tag of the inverse edge.
|
||||
func (b *inverseBuilder) StructTag(s string) *inverseBuilder {
|
||||
b.desc.Tag = s
|
||||
return b
|
||||
}
|
||||
|
||||
// Comment used to put annotations on the schema.
|
||||
func (b *inverseBuilder) Comment(c string) *inverseBuilder {
|
||||
b.desc.Comment = c
|
||||
return b
|
||||
}
|
||||
|
||||
// Field is used to bind an edge (with a foreign-key) to a field in the schema.
|
||||
//
|
||||
// field.Int("owner_id").
|
||||
// Optional()
|
||||
//
|
||||
// edge.From("owner", User.Type).
|
||||
// Ref("pets").
|
||||
// Field("owner_id").
|
||||
// Unique(),
|
||||
//
|
||||
func (b *inverseBuilder) Field(f string) *inverseBuilder {
|
||||
b.desc.Field = f
|
||||
return b
|
||||
}
|
||||
|
||||
// Through allows setting an "edge schema" to interact explicitly with M2M edges.
|
||||
//
|
||||
// edge.From("liked_users", User.Type).
|
||||
// Ref("liked_tweets").
|
||||
// Through("likes", TweetLike.Type)
|
||||
//
|
||||
func (b *inverseBuilder) Through(name string, t interface{}) *inverseBuilder {
|
||||
b.desc.Through = &struct{ N, T string }{N: name, T: typ(t)}
|
||||
return b
|
||||
}
|
||||
|
||||
// Annotations adds a list of annotations to the edge object to be used by
|
||||
// codegen extensions.
|
||||
//
|
||||
// edge.From("owner", User.Type).
|
||||
// Ref("pets").
|
||||
// Unique().
|
||||
// Annotations(entgql.Bind())
|
||||
//
|
||||
func (b *inverseBuilder) Annotations(annotations ...schema.Annotation) *inverseBuilder {
|
||||
b.desc.Annotations = append(b.desc.Annotations, annotations...)
|
||||
return b
|
||||
}
|
||||
|
||||
// Descriptor implements the ent.Descriptor interface.
|
||||
func (b *inverseBuilder) Descriptor() *Descriptor {
|
||||
return b.desc
|
||||
}
|
||||
|
||||
// StorageKey holds the configuration for edge storage-key.
|
||||
type StorageKey struct {
|
||||
Table string // Table or label.
|
||||
Symbols []string // Symbols/names of the foreign-key constraints.
|
||||
Columns []string // Foreign-key columns.
|
||||
}
|
||||
|
||||
// StorageOption allows for setting the storage configuration using functional options.
|
||||
type StorageOption func(*StorageKey)
|
||||
|
||||
// Table sets the table name option for M2M edges.
|
||||
func Table(name string) StorageOption {
|
||||
return func(key *StorageKey) {
|
||||
key.Table = name
|
||||
}
|
||||
}
|
||||
|
||||
// Symbol sets the symbol/name of the foreign-key constraint for O2O, O2M and M2O edges.
|
||||
// Note that, for M2M edges (2 columns and 2 constraints), use the edge.Symbols option.
|
||||
func Symbol(symbol string) StorageOption {
|
||||
return func(key *StorageKey) {
|
||||
key.Symbols = []string{symbol}
|
||||
}
|
||||
}
|
||||
|
||||
// Symbols sets the symbol/name of the foreign-key constraints for M2M edges.
|
||||
// The 1st column defines the name of the "To" edge, and the 2nd defines
|
||||
// the name of the "From" edge (inverse edge).
|
||||
// Note that, for O2O, O2M and M2O edges, use the edge.Symbol option.
|
||||
func Symbols(to, from string) StorageOption {
|
||||
return func(key *StorageKey) {
|
||||
key.Symbols = []string{to, from}
|
||||
}
|
||||
}
|
||||
|
||||
// Column sets the foreign-key column name option for O2O, O2M and M2O edges.
|
||||
// Note that, for M2M edges (2 columns), use the edge.Columns option.
|
||||
func Column(name string) StorageOption {
|
||||
return func(key *StorageKey) {
|
||||
key.Columns = []string{name}
|
||||
}
|
||||
}
|
||||
|
||||
// Columns sets the foreign-key column names option for M2M edges.
|
||||
// The 1st column defines the name of the "To" edge, and the 2nd defines
|
||||
// the name of the "From" edge (inverse edge).
|
||||
// Note that, for O2O, O2M and M2O edges, use the edge.Column option.
|
||||
func Columns(to, from string) StorageOption {
|
||||
return func(key *StorageKey) {
|
||||
key.Columns = []string{to, from}
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
package field
|
||||
|
||||
import "entgo.io/ent/schema"
|
||||
|
||||
// Annotation is a builtin schema annotation for
|
||||
// configuring the schema fields in codegen.
|
||||
type Annotation struct {
|
||||
// The StructTag option allows overriding the struct-tag
|
||||
// of the fields in the generated entity. For example:
|
||||
//
|
||||
// field.Annotation{
|
||||
// StructTag: map[string]string{
|
||||
// "id": `json:"id,omitempty" yaml:"-"`,
|
||||
// },
|
||||
// }
|
||||
//
|
||||
StructTag map[string]string
|
||||
|
||||
// ID defines a multi-field schema identifier. Note,
|
||||
// the annotation is valid only for edge schemas.
|
||||
//
|
||||
// func (TweetLike) Annotations() []schema.Annotation {
|
||||
// return []schema.Annotation{
|
||||
// field.ID("user_id", "tweet_id"),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
ID []string
|
||||
}
|
||||
|
||||
// ID defines a multi-field schema identifier. Note, the
|
||||
// annotation is valid only for edge schemas.
|
||||
//
|
||||
// func (TweetLike) Annotations() []schema.Annotation {
|
||||
// return []schema.Annotation{
|
||||
// field.ID("user_id", "tweet_id"),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
func ID(first, second string, fields ...string) *Annotation {
|
||||
return &Annotation{ID: append([]string{first, second}, fields...)}
|
||||
}
|
||||
|
||||
// Name describes the annotation name.
|
||||
func (Annotation) Name() string {
|
||||
return "Fields"
|
||||
}
|
||||
|
||||
// Merge implements the schema.Merger interface.
|
||||
func (a Annotation) Merge(other schema.Annotation) schema.Annotation {
|
||||
var ant Annotation
|
||||
switch other := other.(type) {
|
||||
case Annotation:
|
||||
ant = other
|
||||
case *Annotation:
|
||||
if other != nil {
|
||||
ant = *other
|
||||
}
|
||||
default:
|
||||
return a
|
||||
}
|
||||
for k, v := range ant.StructTag {
|
||||
if a.StructTag == nil {
|
||||
a.StructTag = make(map[string]string)
|
||||
}
|
||||
a.StructTag[k] = v
|
||||
}
|
||||
if len(ant.ID) > 0 {
|
||||
a.ID = ant.ID
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
schema.Annotation
|
||||
schema.Merger
|
||||
} = (*Annotation)(nil)
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,244 @@
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
package field
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A Type represents a field type.
|
||||
type Type uint8
|
||||
|
||||
// List of field types.
|
||||
const (
|
||||
TypeInvalid Type = iota
|
||||
TypeBool
|
||||
TypeTime
|
||||
TypeJSON
|
||||
TypeUUID
|
||||
TypeBytes
|
||||
TypeEnum
|
||||
TypeString
|
||||
TypeOther
|
||||
TypeInt8
|
||||
TypeInt16
|
||||
TypeInt32
|
||||
TypeInt
|
||||
TypeInt64
|
||||
TypeUint8
|
||||
TypeUint16
|
||||
TypeUint32
|
||||
TypeUint
|
||||
TypeUint64
|
||||
TypeFloat32
|
||||
TypeFloat64
|
||||
endTypes
|
||||
)
|
||||
|
||||
// String returns the string representation of a type.
|
||||
func (t Type) String() string {
|
||||
if t < endTypes {
|
||||
return typeNames[t]
|
||||
}
|
||||
return typeNames[TypeInvalid]
|
||||
}
|
||||
|
||||
// Numeric reports if the given type is a numeric type.
|
||||
func (t Type) Numeric() bool {
|
||||
return t >= TypeInt8 && t < endTypes
|
||||
}
|
||||
|
||||
// Float reports if the given type is a float type.
|
||||
func (t Type) Float() bool {
|
||||
return t == TypeFloat32 || t == TypeFloat64
|
||||
}
|
||||
|
||||
// Integer reports if the given type is an integral type.
|
||||
func (t Type) Integer() bool {
|
||||
return t.Numeric() && !t.Float()
|
||||
}
|
||||
|
||||
// Valid reports if the given type if known type.
|
||||
func (t Type) Valid() bool {
|
||||
return t > TypeInvalid && t < endTypes
|
||||
}
|
||||
|
||||
// ConstName returns the constant name of an info type.
|
||||
// It's used by entc for printing the constant name in templates.
|
||||
func (t Type) ConstName() string {
|
||||
switch {
|
||||
case !t.Valid():
|
||||
return typeNames[TypeInvalid]
|
||||
case int(t) < len(constNames) && constNames[t] != "":
|
||||
return constNames[t]
|
||||
default:
|
||||
return "Type" + strings.Title(typeNames[t])
|
||||
}
|
||||
}
|
||||
|
||||
// TypeInfo holds the information regarding field type.
|
||||
// Used by complex types like JSON and Bytes.
|
||||
type TypeInfo struct {
|
||||
Type Type
|
||||
Ident string
|
||||
PkgPath string // import path.
|
||||
PkgName string // local package name.
|
||||
Nillable bool // slices or pointers.
|
||||
RType *RType
|
||||
}
|
||||
|
||||
// String returns the string representation of a type.
|
||||
func (t TypeInfo) String() string {
|
||||
switch {
|
||||
case t.Ident != "":
|
||||
return t.Ident
|
||||
case t.Type < endTypes:
|
||||
return typeNames[t.Type]
|
||||
default:
|
||||
return typeNames[TypeInvalid]
|
||||
}
|
||||
}
|
||||
|
||||
// Valid reports if the given type if known type.
|
||||
func (t TypeInfo) Valid() bool {
|
||||
return t.Type.Valid()
|
||||
}
|
||||
|
||||
// Numeric reports if the given type is a numeric type.
|
||||
func (t TypeInfo) Numeric() bool {
|
||||
return t.Type.Numeric()
|
||||
}
|
||||
|
||||
// ConstName returns the const name of the info type.
|
||||
func (t TypeInfo) ConstName() string {
|
||||
return t.Type.ConstName()
|
||||
}
|
||||
|
||||
// ValueScanner indicates if this type implements the ValueScanner interface.
|
||||
func (t TypeInfo) ValueScanner() bool {
|
||||
return t.RType.Implements(valueScannerType)
|
||||
}
|
||||
|
||||
// Valuer indicates if this type implements the driver.Valuer interface.
|
||||
func (t TypeInfo) Valuer() bool {
|
||||
return t.RType.Implements(valuerType)
|
||||
}
|
||||
|
||||
// Comparable reports whether values of this type are comparable.
|
||||
func (t TypeInfo) Comparable() bool {
|
||||
switch t.Type {
|
||||
case TypeBool, TypeTime, TypeUUID, TypeEnum, TypeString:
|
||||
return true
|
||||
case TypeOther:
|
||||
// Always accept custom types as comparable on the database side.
|
||||
// In the future, we should consider adding an interface to let
|
||||
// custom types tell if they are comparable or not (see #1304).
|
||||
return true
|
||||
default:
|
||||
return t.Numeric()
|
||||
}
|
||||
}
|
||||
|
||||
var stringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
||||
|
||||
// Stringer indicates if this type implements the Stringer interface.
|
||||
func (t TypeInfo) Stringer() bool {
|
||||
return t.RType.Implements(stringerType)
|
||||
}
|
||||
|
||||
var (
|
||||
typeNames = [...]string{
|
||||
TypeInvalid: "invalid",
|
||||
TypeBool: "bool",
|
||||
TypeTime: "time.Time",
|
||||
TypeJSON: "json.RawMessage",
|
||||
TypeUUID: "[16]byte",
|
||||
TypeBytes: "[]byte",
|
||||
TypeEnum: "string",
|
||||
TypeString: "string",
|
||||
TypeOther: "other",
|
||||
TypeInt: "int",
|
||||
TypeInt8: "int8",
|
||||
TypeInt16: "int16",
|
||||
TypeInt32: "int32",
|
||||
TypeInt64: "int64",
|
||||
TypeUint: "uint",
|
||||
TypeUint8: "uint8",
|
||||
TypeUint16: "uint16",
|
||||
TypeUint32: "uint32",
|
||||
TypeUint64: "uint64",
|
||||
TypeFloat32: "float32",
|
||||
TypeFloat64: "float64",
|
||||
}
|
||||
constNames = [...]string{
|
||||
TypeJSON: "TypeJSON",
|
||||
TypeUUID: "TypeUUID",
|
||||
TypeTime: "TypeTime",
|
||||
TypeEnum: "TypeEnum",
|
||||
TypeBytes: "TypeBytes",
|
||||
TypeOther: "TypeOther",
|
||||
}
|
||||
)
|
||||
|
||||
// RType holds a serializable reflect.Type information of
|
||||
// Go object. Used by the entc package.
|
||||
type RType struct {
|
||||
Name string // reflect.Type.Name
|
||||
Ident string // reflect.Type.String
|
||||
Kind reflect.Kind
|
||||
PkgPath string
|
||||
Methods map[string]struct{ In, Out []*RType }
|
||||
// Used only for in-package checks.
|
||||
rtype reflect.Type
|
||||
}
|
||||
|
||||
// TypeEqual reports if the underlying type is equal to the RType (after pointer indirections).
|
||||
func (r *RType) TypeEqual(t reflect.Type) bool {
|
||||
tv := indirect(t)
|
||||
return r.Name == tv.Name() && r.Kind == t.Kind() && r.PkgPath == tv.PkgPath()
|
||||
}
|
||||
|
||||
// RType returns the string value of the indirect reflect.Type.
|
||||
func (r *RType) String() string {
|
||||
if r.rtype != nil {
|
||||
return r.rtype.String()
|
||||
}
|
||||
return r.Ident
|
||||
}
|
||||
|
||||
// IsPtr reports if the reflect-type is a pointer type.
|
||||
func (r *RType) IsPtr() bool {
|
||||
return r != nil && r.Kind == reflect.Ptr
|
||||
}
|
||||
|
||||
// Implements reports whether the RType ~implements the given interface type.
|
||||
func (r *RType) Implements(typ reflect.Type) bool {
|
||||
if r == nil {
|
||||
return false
|
||||
}
|
||||
n := typ.NumMethod()
|
||||
for i := 0; i < n; i++ {
|
||||
m0 := typ.Method(i)
|
||||
m1, ok := r.Methods[m0.Name]
|
||||
if !ok || len(m1.In) != m0.Type.NumIn() || len(m1.Out) != m0.Type.NumOut() {
|
||||
return false
|
||||
}
|
||||
in := m0.Type.NumIn()
|
||||
for j := 0; j < in; j++ {
|
||||
if !m1.In[j].TypeEqual(m0.Type.In(j)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
out := m0.Type.NumOut()
|
||||
for j := 0; j < out; j++ {
|
||||
if !m1.Out[j].TypeEqual(m0.Type.Out(j)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
package index
|
||||
|
||||
import "entgo.io/ent/schema"
|
||||
|
||||
// A Descriptor for index configuration.
|
||||
type Descriptor struct {
|
||||
Unique bool // unique index.
|
||||
Edges []string // edge columns.
|
||||
Fields []string // field columns.
|
||||
StorageKey string // custom index name.
|
||||
Annotations []schema.Annotation // index annotations.
|
||||
}
|
||||
|
||||
// Builder for indexes on vertex columns and edges in the graph.
|
||||
type Builder struct {
|
||||
desc *Descriptor
|
||||
}
|
||||
|
||||
// Fields creates an index on the given vertex fields.
|
||||
// Note that indexes are implemented only for SQL dialects, and does not support gremlin.
|
||||
//
|
||||
// func (T) Indexes() []ent.Index {
|
||||
//
|
||||
// // Unique index on 2 fields.
|
||||
// index.Fields("first", "last").
|
||||
// Unique(),
|
||||
//
|
||||
// // Unique index of field under specific edge.
|
||||
// index.Fields("name").
|
||||
// Edges("parent").
|
||||
// Unique(),
|
||||
//
|
||||
// }
|
||||
//
|
||||
func Fields(fields ...string) *Builder {
|
||||
return &Builder{desc: &Descriptor{Fields: fields}}
|
||||
}
|
||||
|
||||
// Edges creates an index on the given vertex edge fields.
|
||||
// Note that indexes are implemented only for SQL dialects, and does not support gremlin.
|
||||
//
|
||||
// func (T) Indexes() []ent.Index {
|
||||
//
|
||||
// // Unique index of field under 2 edges.
|
||||
// index.Fields("name").
|
||||
// Edges("parent", "type").
|
||||
// Unique(),
|
||||
//
|
||||
// }
|
||||
//
|
||||
func Edges(edges ...string) *Builder {
|
||||
return &Builder{desc: &Descriptor{Edges: edges}}
|
||||
}
|
||||
|
||||
// Fields sets the fields of the index.
|
||||
//
|
||||
// func (T) Indexes() []ent.Index {
|
||||
//
|
||||
// // Unique "name" and "age" fields under the "parent" edge.
|
||||
// index.Edges("parent").
|
||||
// Fields("name", "age").
|
||||
// Unique(),
|
||||
//
|
||||
// }
|
||||
func (b *Builder) Fields(fields ...string) *Builder {
|
||||
b.desc.Fields = fields
|
||||
return b
|
||||
}
|
||||
|
||||
// Edges sets the fields index to be unique under the set of edges (sub-graph). For example:
|
||||
//
|
||||
// func (T) Indexes() []ent.Index {
|
||||
//
|
||||
// // Unique "name" field under the "parent" edge.
|
||||
// index.Fields("name").
|
||||
// Edges("parent").
|
||||
// Unique(),
|
||||
// }
|
||||
//
|
||||
func (b *Builder) Edges(edges ...string) *Builder {
|
||||
b.desc.Edges = edges
|
||||
return b
|
||||
}
|
||||
|
||||
// Unique sets the index to be a unique index.
|
||||
// Note that defining a uniqueness on optional fields won't prevent
|
||||
// duplicates if one of the column contains NULL values.
|
||||
func (b *Builder) Unique() *Builder {
|
||||
b.desc.Unique = true
|
||||
return b
|
||||
}
|
||||
|
||||
// StorageKey sets the storage key of the index. In SQL dialects, it's the index name.
|
||||
func (b *Builder) StorageKey(key string) *Builder {
|
||||
b.desc.StorageKey = key
|
||||
return b
|
||||
}
|
||||
|
||||
// Annotations adds a list of annotations to the index object to be used by codegen extensions.
|
||||
//
|
||||
// func (T) Indexes() []ent.Index {
|
||||
//
|
||||
// // Partial index on name where the entity is not deleted.
|
||||
// index.Fields("name").
|
||||
// Annotations(entsql.Prefix(100))
|
||||
//
|
||||
// }
|
||||
//
|
||||
func (b *Builder) Annotations(annotations ...schema.Annotation) *Builder {
|
||||
b.desc.Annotations = append(b.desc.Annotations, annotations...)
|
||||
return b
|
||||
}
|
||||
|
||||
// Descriptor implements the ent.Descriptor interface.
|
||||
func (b *Builder) Descriptor() *Descriptor {
|
||||
return b.desc
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
// Copyright 2019-present Facebook Inc. All rights reserved.
|
||||
// This source code is licensed under the Apache 2.0 license found
|
||||
// in the LICENSE file in the root directory of this source tree.
|
||||
|
||||
package schema
|
||||
|
||||
// Annotation is used to attach arbitrary metadata to the schema objects in codegen.
|
||||
// The object must be serializable to JSON raw value (e.g. struct, map or slice).
|
||||
//
|
||||
// Template extensions can retrieve this metadata and use it inside their templates.
|
||||
// Read more about it in ent website: https://entgo.io/docs/templates/#annotations.
|
||||
type Annotation interface {
|
||||
// Name defines the name of the annotation to be retrieved by the codegen.
|
||||
Name() string
|
||||
}
|
||||
|
||||
// Merger wraps the single Merge function allows custom annotation to provide
|
||||
// an implementation for merging 2 or more annotations from the same type.
|
||||
//
|
||||
// A common use case is where the same Annotation type is defined both in
|
||||
// mixin.Schema and ent.Schema.
|
||||
type Merger interface {
|
||||
Merge(Annotation) Annotation
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
@ -0,0 +1,145 @@
|
||||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// Go versions prior to 1.4 are disabled because they use a different layout
|
||||
// for interfaces which make the implementation of unsafeReflectValue more complex.
|
||||
// +build !js,!appengine,!safe,!disableunsafe,go1.4
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||
// not access to the unsafe package is available.
|
||||
UnsafeDisabled = false
|
||||
|
||||
// ptrSize is the size of a pointer on the current arch.
|
||||
ptrSize = unsafe.Sizeof((*byte)(nil))
|
||||
)
|
||||
|
||||
type flag uintptr
|
||||
|
||||
var (
|
||||
// flagRO indicates whether the value field of a reflect.Value
|
||||
// is read-only.
|
||||
flagRO flag
|
||||
|
||||
// flagAddr indicates whether the address of the reflect.Value's
|
||||
// value may be taken.
|
||||
flagAddr flag
|
||||
)
|
||||
|
||||
// flagKindMask holds the bits that make up the kind
|
||||
// part of the flags field. In all the supported versions,
|
||||
// it is in the lower 5 bits.
|
||||
const flagKindMask = flag(0x1f)
|
||||
|
||||
// Different versions of Go have used different
|
||||
// bit layouts for the flags type. This table
|
||||
// records the known combinations.
|
||||
var okFlags = []struct {
|
||||
ro, addr flag
|
||||
}{{
|
||||
// From Go 1.4 to 1.5
|
||||
ro: 1 << 5,
|
||||
addr: 1 << 7,
|
||||
}, {
|
||||
// Up to Go tip.
|
||||
ro: 1<<5 | 1<<6,
|
||||
addr: 1 << 8,
|
||||
}}
|
||||
|
||||
var flagValOffset = func() uintptr {
|
||||
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
||||
if !ok {
|
||||
panic("reflect.Value has no flag field")
|
||||
}
|
||||
return field.Offset
|
||||
}()
|
||||
|
||||
// flagField returns a pointer to the flag field of a reflect.Value.
|
||||
func flagField(v *reflect.Value) *flag {
|
||||
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
|
||||
}
|
||||
|
||||
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
||||
// the typical safety restrictions preventing access to unaddressable and
|
||||
// unexported data. It works by digging the raw pointer to the underlying
|
||||
// value out of the protected value and generating a new unprotected (unsafe)
|
||||
// reflect.Value to it.
|
||||
//
|
||||
// This allows us to check for implementations of the Stringer and error
|
||||
// interfaces to be used for pretty printing ordinarily unaddressable and
|
||||
// inaccessible values such as unexported struct fields.
|
||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||
if !v.IsValid() || (v.CanInterface() && v.CanAddr()) {
|
||||
return v
|
||||
}
|
||||
flagFieldPtr := flagField(&v)
|
||||
*flagFieldPtr &^= flagRO
|
||||
*flagFieldPtr |= flagAddr
|
||||
return v
|
||||
}
|
||||
|
||||
// Sanity checks against future reflect package changes
|
||||
// to the type or semantics of the Value.flag field.
|
||||
func init() {
|
||||
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
||||
if !ok {
|
||||
panic("reflect.Value has no flag field")
|
||||
}
|
||||
if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
|
||||
panic("reflect.Value flag field has changed kind")
|
||||
}
|
||||
type t0 int
|
||||
var t struct {
|
||||
A t0
|
||||
// t0 will have flagEmbedRO set.
|
||||
t0
|
||||
// a will have flagStickyRO set
|
||||
a t0
|
||||
}
|
||||
vA := reflect.ValueOf(t).FieldByName("A")
|
||||
va := reflect.ValueOf(t).FieldByName("a")
|
||||
vt0 := reflect.ValueOf(t).FieldByName("t0")
|
||||
|
||||
// Infer flagRO from the difference between the flags
|
||||
// for the (otherwise identical) fields in t.
|
||||
flagPublic := *flagField(&vA)
|
||||
flagWithRO := *flagField(&va) | *flagField(&vt0)
|
||||
flagRO = flagPublic ^ flagWithRO
|
||||
|
||||
// Infer flagAddr from the difference between a value
|
||||
// taken from a pointer and not.
|
||||
vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A")
|
||||
flagNoPtr := *flagField(&vA)
|
||||
flagPtr := *flagField(&vPtrA)
|
||||
flagAddr = flagNoPtr ^ flagPtr
|
||||
|
||||
// Check that the inferred flags tally with one of the known versions.
|
||||
for _, f := range okFlags {
|
||||
if flagRO == f.ro && flagAddr == f.addr {
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("reflect.Value read-only flag has changed semantics")
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is running on Google App Engine, compiled by GopherJS, or
|
||||
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// +build js appengine safe disableunsafe !go1.4
|
||||
|
||||
package spew
|
||||
|
||||
import "reflect"
|
||||
|
||||
const (
|
||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||
// not access to the unsafe package is available.
|
||||
UnsafeDisabled = true
|
||||
)
|
||||
|
||||
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
||||
// that bypasses the typical safety restrictions preventing access to
|
||||
// unaddressable and unexported data. However, doing this relies on access to
|
||||
// the unsafe package. This is a stub version which simply returns the passed
|
||||
// reflect.Value when the unsafe package is not available.
|
||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||
return v
|
||||
}
|
@ -0,0 +1,341 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
||||
// the technique used in the fmt package.
|
||||
var (
|
||||
panicBytes = []byte("(PANIC=")
|
||||
plusBytes = []byte("+")
|
||||
iBytes = []byte("i")
|
||||
trueBytes = []byte("true")
|
||||
falseBytes = []byte("false")
|
||||
interfaceBytes = []byte("(interface {})")
|
||||
commaNewlineBytes = []byte(",\n")
|
||||
newlineBytes = []byte("\n")
|
||||
openBraceBytes = []byte("{")
|
||||
openBraceNewlineBytes = []byte("{\n")
|
||||
closeBraceBytes = []byte("}")
|
||||
asteriskBytes = []byte("*")
|
||||
colonBytes = []byte(":")
|
||||
colonSpaceBytes = []byte(": ")
|
||||
openParenBytes = []byte("(")
|
||||
closeParenBytes = []byte(")")
|
||||
spaceBytes = []byte(" ")
|
||||
pointerChainBytes = []byte("->")
|
||||
nilAngleBytes = []byte("<nil>")
|
||||
maxNewlineBytes = []byte("<max depth reached>\n")
|
||||
maxShortBytes = []byte("<max>")
|
||||
circularBytes = []byte("<already shown>")
|
||||
circularShortBytes = []byte("<shown>")
|
||||
invalidAngleBytes = []byte("<invalid>")
|
||||
openBracketBytes = []byte("[")
|
||||
closeBracketBytes = []byte("]")
|
||||
percentBytes = []byte("%")
|
||||
precisionBytes = []byte(".")
|
||||
openAngleBytes = []byte("<")
|
||||
closeAngleBytes = []byte(">")
|
||||
openMapBytes = []byte("map[")
|
||||
closeMapBytes = []byte("]")
|
||||
lenEqualsBytes = []byte("len=")
|
||||
capEqualsBytes = []byte("cap=")
|
||||
)
|
||||
|
||||
// hexDigits is used to map a decimal value to a hex digit.
|
||||
var hexDigits = "0123456789abcdef"
|
||||
|
||||
// catchPanic handles any panics that might occur during the handleMethods
|
||||
// calls.
|
||||
func catchPanic(w io.Writer, v reflect.Value) {
|
||||
if err := recover(); err != nil {
|
||||
w.Write(panicBytes)
|
||||
fmt.Fprintf(w, "%v", err)
|
||||
w.Write(closeParenBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// handleMethods attempts to call the Error and String methods on the underlying
|
||||
// type the passed reflect.Value represents and outputes the result to Writer w.
|
||||
//
|
||||
// It handles panics in any called methods by catching and displaying the error
|
||||
// as the formatted value.
|
||||
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
||||
// We need an interface to check if the type implements the error or
|
||||
// Stringer interface. However, the reflect package won't give us an
|
||||
// interface on certain things like unexported struct fields in order
|
||||
// to enforce visibility rules. We use unsafe, when it's available,
|
||||
// to bypass these restrictions since this package does not mutate the
|
||||
// values.
|
||||
if !v.CanInterface() {
|
||||
if UnsafeDisabled {
|
||||
return false
|
||||
}
|
||||
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
|
||||
// Choose whether or not to do error and Stringer interface lookups against
|
||||
// the base type or a pointer to the base type depending on settings.
|
||||
// Technically calling one of these methods with a pointer receiver can
|
||||
// mutate the value, however, types which choose to satisify an error or
|
||||
// Stringer interface with a pointer receiver should not be mutating their
|
||||
// state inside these interface methods.
|
||||
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
if v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
|
||||
// Is it an error or Stringer?
|
||||
switch iface := v.Interface().(type) {
|
||||
case error:
|
||||
defer catchPanic(w, v)
|
||||
if cs.ContinueOnMethod {
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(iface.Error()))
|
||||
w.Write(closeParenBytes)
|
||||
w.Write(spaceBytes)
|
||||
return false
|
||||
}
|
||||
|
||||
w.Write([]byte(iface.Error()))
|
||||
return true
|
||||
|
||||
case fmt.Stringer:
|
||||
defer catchPanic(w, v)
|
||||
if cs.ContinueOnMethod {
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(iface.String()))
|
||||
w.Write(closeParenBytes)
|
||||
w.Write(spaceBytes)
|
||||
return false
|
||||
}
|
||||
w.Write([]byte(iface.String()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// printBool outputs a boolean value as true or false to Writer w.
|
||||
func printBool(w io.Writer, val bool) {
|
||||
if val {
|
||||
w.Write(trueBytes)
|
||||
} else {
|
||||
w.Write(falseBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// printInt outputs a signed integer value to Writer w.
|
||||
func printInt(w io.Writer, val int64, base int) {
|
||||
w.Write([]byte(strconv.FormatInt(val, base)))
|
||||
}
|
||||
|
||||
// printUint outputs an unsigned integer value to Writer w.
|
||||
func printUint(w io.Writer, val uint64, base int) {
|
||||
w.Write([]byte(strconv.FormatUint(val, base)))
|
||||
}
|
||||
|
||||
// printFloat outputs a floating point value using the specified precision,
|
||||
// which is expected to be 32 or 64bit, to Writer w.
|
||||
func printFloat(w io.Writer, val float64, precision int) {
|
||||
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
||||
}
|
||||
|
||||
// printComplex outputs a complex value using the specified float precision
|
||||
// for the real and imaginary parts to Writer w.
|
||||
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
||||
r := real(c)
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
||||
i := imag(c)
|
||||
if i >= 0 {
|
||||
w.Write(plusBytes)
|
||||
}
|
||||
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
||||
w.Write(iBytes)
|
||||
w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
|
||||
// prefix to Writer w.
|
||||
func printHexPtr(w io.Writer, p uintptr) {
|
||||
// Null pointer.
|
||||
num := uint64(p)
|
||||
if num == 0 {
|
||||
w.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
||||
buf := make([]byte, 18)
|
||||
|
||||
// It's simpler to construct the hex string right to left.
|
||||
base := uint64(16)
|
||||
i := len(buf) - 1
|
||||
for num >= base {
|
||||
buf[i] = hexDigits[num%base]
|
||||
num /= base
|
||||
i--
|
||||
}
|
||||
buf[i] = hexDigits[num]
|
||||
|
||||
// Add '0x' prefix.
|
||||
i--
|
||||
buf[i] = 'x'
|
||||
i--
|
||||
buf[i] = '0'
|
||||
|
||||
// Strip unused leading bytes.
|
||||
buf = buf[i:]
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
||||
// elements to be sorted.
|
||||
type valuesSorter struct {
|
||||
values []reflect.Value
|
||||
strings []string // either nil or same len and values
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
||||
// surrogate keys on which the data should be sorted. It uses flags in
|
||||
// ConfigState to decide if and how to populate those surrogate keys.
|
||||
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
||||
vs := &valuesSorter{values: values, cs: cs}
|
||||
if canSortSimply(vs.values[0].Kind()) {
|
||||
return vs
|
||||
}
|
||||
if !cs.DisableMethods {
|
||||
vs.strings = make([]string, len(values))
|
||||
for i := range vs.values {
|
||||
b := bytes.Buffer{}
|
||||
if !handleMethods(cs, &b, vs.values[i]) {
|
||||
vs.strings = nil
|
||||
break
|
||||
}
|
||||
vs.strings[i] = b.String()
|
||||
}
|
||||
}
|
||||
if vs.strings == nil && cs.SpewKeys {
|
||||
vs.strings = make([]string, len(values))
|
||||
for i := range vs.values {
|
||||
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
||||
}
|
||||
}
|
||||
return vs
|
||||
}
|
||||
|
||||
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
||||
// directly, or whether it should be considered for sorting by surrogate keys
|
||||
// (if the ConfigState allows it).
|
||||
func canSortSimply(kind reflect.Kind) bool {
|
||||
// This switch parallels valueSortLess, except for the default case.
|
||||
switch kind {
|
||||
case reflect.Bool:
|
||||
return true
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return true
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
return true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
case reflect.String:
|
||||
return true
|
||||
case reflect.Uintptr:
|
||||
return true
|
||||
case reflect.Array:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Len returns the number of values in the slice. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s *valuesSorter) Len() int {
|
||||
return len(s.values)
|
||||
}
|
||||
|
||||
// Swap swaps the values at the passed indices. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s *valuesSorter) Swap(i, j int) {
|
||||
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||
if s.strings != nil {
|
||||
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
||||
}
|
||||
}
|
||||
|
||||
// valueSortLess returns whether the first value should sort before the second
|
||||
// value. It is used by valueSorter.Less as part of the sort.Interface
|
||||
// implementation.
|
||||
func valueSortLess(a, b reflect.Value) bool {
|
||||
switch a.Kind() {
|
||||
case reflect.Bool:
|
||||
return !a.Bool() && b.Bool()
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return a.Int() < b.Int()
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return a.Float() < b.Float()
|
||||
case reflect.String:
|
||||
return a.String() < b.String()
|
||||
case reflect.Uintptr:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Array:
|
||||
// Compare the contents of both arrays.
|
||||
l := a.Len()
|
||||
for i := 0; i < l; i++ {
|
||||
av := a.Index(i)
|
||||
bv := b.Index(i)
|
||||
if av.Interface() == bv.Interface() {
|
||||
continue
|
||||
}
|
||||
return valueSortLess(av, bv)
|
||||
}
|
||||
}
|
||||
return a.String() < b.String()
|
||||
}
|
||||
|
||||
// Less returns whether the value at index i should sort before the
|
||||
// value at index j. It is part of the sort.Interface implementation.
|
||||
func (s *valuesSorter) Less(i, j int) bool {
|
||||
if s.strings == nil {
|
||||
return valueSortLess(s.values[i], s.values[j])
|
||||
}
|
||||
return s.strings[i] < s.strings[j]
|
||||
}
|
||||
|
||||
// sortValues is a sort function that handles both native types and any type that
|
||||
// can be converted to error or Stringer. Other inputs are sorted according to
|
||||
// their Value.String() value to ensure display stability.
|
||||
func sortValues(values []reflect.Value, cs *ConfigState) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Sort(newValuesSorter(values, cs))
|
||||
}
|
@ -0,0 +1,306 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ConfigState houses the configuration options used by spew to format and
|
||||
// display values. There is a global instance, Config, that is used to control
|
||||
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
||||
// provides methods equivalent to the top-level functions.
|
||||
//
|
||||
// The zero value for ConfigState provides no indentation. You would typically
|
||||
// want to set it to a space or a tab.
|
||||
//
|
||||
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
||||
// with default settings. See the documentation of NewDefaultConfig for default
|
||||
// values.
|
||||
type ConfigState struct {
|
||||
// Indent specifies the string to use for each indentation level. The
|
||||
// global config instance that all top-level functions use set this to a
|
||||
// single space by default. If you would like more indentation, you might
|
||||
// set this to a tab with "\t" or perhaps two spaces with " ".
|
||||
Indent string
|
||||
|
||||
// MaxDepth controls the maximum number of levels to descend into nested
|
||||
// data structures. The default, 0, means there is no limit.
|
||||
//
|
||||
// NOTE: Circular data structures are properly detected, so it is not
|
||||
// necessary to set this value unless you specifically want to limit deeply
|
||||
// nested data structures.
|
||||
MaxDepth int
|
||||
|
||||
// DisableMethods specifies whether or not error and Stringer interfaces are
|
||||
// invoked for types that implement them.
|
||||
DisableMethods bool
|
||||
|
||||
// DisablePointerMethods specifies whether or not to check for and invoke
|
||||
// error and Stringer interfaces on types which only accept a pointer
|
||||
// receiver when the current type is not a pointer.
|
||||
//
|
||||
// NOTE: This might be an unsafe action since calling one of these methods
|
||||
// with a pointer receiver could technically mutate the value, however,
|
||||
// in practice, types which choose to satisify an error or Stringer
|
||||
// interface with a pointer receiver should not be mutating their state
|
||||
// inside these interface methods. As a result, this option relies on
|
||||
// access to the unsafe package, so it will not have any effect when
|
||||
// running in environments without access to the unsafe package such as
|
||||
// Google App Engine or with the "safe" build tag specified.
|
||||
DisablePointerMethods bool
|
||||
|
||||
// DisablePointerAddresses specifies whether to disable the printing of
|
||||
// pointer addresses. This is useful when diffing data structures in tests.
|
||||
DisablePointerAddresses bool
|
||||
|
||||
// DisableCapacities specifies whether to disable the printing of capacities
|
||||
// for arrays, slices, maps and channels. This is useful when diffing
|
||||
// data structures in tests.
|
||||
DisableCapacities bool
|
||||
|
||||
// ContinueOnMethod specifies whether or not recursion should continue once
|
||||
// a custom error or Stringer interface is invoked. The default, false,
|
||||
// means it will print the results of invoking the custom error or Stringer
|
||||
// interface and return immediately instead of continuing to recurse into
|
||||
// the internals of the data type.
|
||||
//
|
||||
// NOTE: This flag does not have any effect if method invocation is disabled
|
||||
// via the DisableMethods or DisablePointerMethods options.
|
||||
ContinueOnMethod bool
|
||||
|
||||
// SortKeys specifies map keys should be sorted before being printed. Use
|
||||
// this to have a more deterministic, diffable output. Note that only
|
||||
// native types (bool, int, uint, floats, uintptr and string) and types
|
||||
// that support the error or Stringer interfaces (if methods are
|
||||
// enabled) are supported, with other types sorted according to the
|
||||
// reflect.Value.String() output which guarantees display stability.
|
||||
SortKeys bool
|
||||
|
||||
// SpewKeys specifies that, as a last resort attempt, map keys should
|
||||
// be spewed to strings and sorted by those strings. This is only
|
||||
// considered if SortKeys is true.
|
||||
SpewKeys bool
|
||||
}
|
||||
|
||||
// Config is the active configuration of the top-level functions.
|
||||
// The configuration can be changed by modifying the contents of spew.Config.
|
||||
var Config = ConfigState{Indent: " "}
|
||||
|
||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the formatted string as a value that satisfies error. See NewFormatter
|
||||
// for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
||||
return fmt.Errorf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(w, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||
// passed with a Formatter interface returned by c.NewFormatter. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(w, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
||||
return fmt.Print(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Printf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Println(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprint(a ...interface{}) string {
|
||||
return fmt.Sprint(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
||||
return fmt.Sprintf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||
// were passed with a Formatter interface returned by c.NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
||||
return fmt.Sprintln(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
/*
|
||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||
interface. As a result, it integrates cleanly with standard fmt package
|
||||
printing functions. The formatter is useful for inline printing of smaller data
|
||||
types similar to the standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Typically this function shouldn't be called directly. It is much easier to make
|
||||
use of the custom formatter by calling one of the convenience functions such as
|
||||
c.Printf, c.Println, or c.Printf.
|
||||
*/
|
||||
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
||||
return newFormatter(c, v)
|
||||
}
|
||||
|
||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||
// exactly the same as Dump.
|
||||
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
||||
fdump(c, w, a...)
|
||||
}
|
||||
|
||||
/*
|
||||
Dump displays the passed parameters to standard out with newlines, customizable
|
||||
indentation, and additional debug information such as complete types and all
|
||||
pointer addresses used to indirect to the final value. It provides the
|
||||
following features over the built-in printing facilities provided by the fmt
|
||||
package:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output
|
||||
|
||||
The configuration options are controlled by modifying the public members
|
||||
of c. See ConfigState for options documentation.
|
||||
|
||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||
get the formatted result as a string.
|
||||
*/
|
||||
func (c *ConfigState) Dump(a ...interface{}) {
|
||||
fdump(c, os.Stdout, a...)
|
||||
}
|
||||
|
||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||
// as Dump.
|
||||
func (c *ConfigState) Sdump(a ...interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
fdump(c, &buf, a...)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||
// length with each argument converted to a spew Formatter interface using
|
||||
// the ConfigState associated with s.
|
||||
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
||||
formatters = make([]interface{}, len(args))
|
||||
for index, arg := range args {
|
||||
formatters[index] = newFormatter(c, arg)
|
||||
}
|
||||
return formatters
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a ConfigState with the following default settings.
|
||||
//
|
||||
// Indent: " "
|
||||
// MaxDepth: 0
|
||||
// DisableMethods: false
|
||||
// DisablePointerMethods: false
|
||||
// ContinueOnMethod: false
|
||||
// SortKeys: false
|
||||
func NewDefaultConfig() *ConfigState {
|
||||
return &ConfigState{Indent: " "}
|
||||
}
|
@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package spew implements a deep pretty printer for Go data structures to aid in
|
||||
debugging.
|
||||
|
||||
A quick overview of the additional features spew provides over the built-in
|
||||
printing facilities for Go data types are as follows:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output (only when using
|
||||
Dump style)
|
||||
|
||||
There are two different approaches spew allows for dumping Go data structures:
|
||||
|
||||
* Dump style which prints with newlines, customizable indentation,
|
||||
and additional debug information such as types and all pointer addresses
|
||||
used to indirect to the final value
|
||||
* A custom Formatter interface that integrates cleanly with the standard fmt
|
||||
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
||||
similar to the default %v while providing the additional functionality
|
||||
outlined above and passing unsupported format verbs such as %x and %q
|
||||
along to fmt
|
||||
|
||||
Quick Start
|
||||
|
||||
This section demonstrates how to quickly get started with spew. See the
|
||||
sections below for further details on formatting and configuration options.
|
||||
|
||||
To dump a variable with full newlines, indentation, type, and pointer
|
||||
information use Dump, Fdump, or Sdump:
|
||||
spew.Dump(myVar1, myVar2, ...)
|
||||
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||
str := spew.Sdump(myVar1, myVar2, ...)
|
||||
|
||||
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
||||
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
||||
%#+v (adds types and pointer addresses):
|
||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
|
||||
Configuration Options
|
||||
|
||||
Configuration of spew is handled by fields in the ConfigState type. For
|
||||
convenience, all of the top-level functions use a global state available
|
||||
via the spew.Config global.
|
||||
|
||||
It is also possible to create a ConfigState instance that provides methods
|
||||
equivalent to the top-level functions. This allows concurrent configuration
|
||||
options. See the ConfigState documentation for more details.
|
||||
|
||||
The following configuration options are available:
|
||||
* Indent
|
||||
String to use for each indentation level for Dump functions.
|
||||
It is a single space by default. A popular alternative is "\t".
|
||||
|
||||
* MaxDepth
|
||||
Maximum number of levels to descend into nested data structures.
|
||||
There is no limit by default.
|
||||
|
||||
* DisableMethods
|
||||
Disables invocation of error and Stringer interface methods.
|
||||
Method invocation is enabled by default.
|
||||
|
||||
* DisablePointerMethods
|
||||
Disables invocation of error and Stringer interface methods on types
|
||||
which only accept pointer receivers from non-pointer variables.
|
||||
Pointer method invocation is enabled by default.
|
||||
|
||||
* DisablePointerAddresses
|
||||
DisablePointerAddresses specifies whether to disable the printing of
|
||||
pointer addresses. This is useful when diffing data structures in tests.
|
||||
|
||||
* DisableCapacities
|
||||
DisableCapacities specifies whether to disable the printing of
|
||||
capacities for arrays, slices, maps and channels. This is useful when
|
||||
diffing data structures in tests.
|
||||
|
||||
* ContinueOnMethod
|
||||
Enables recursion into types after invoking error and Stringer interface
|
||||
methods. Recursion after method invocation is disabled by default.
|
||||
|
||||
* SortKeys
|
||||
Specifies map keys should be sorted before being printed. Use
|
||||
this to have a more deterministic, diffable output. Note that
|
||||
only native types (bool, int, uint, floats, uintptr and string)
|
||||
and types which implement error or Stringer interfaces are
|
||||
supported with other types sorted according to the
|
||||
reflect.Value.String() output which guarantees display
|
||||
stability. Natural map order is used by default.
|
||||
|
||||
* SpewKeys
|
||||
Specifies that, as a last resort attempt, map keys should be
|
||||
spewed to strings and sorted by those strings. This is only
|
||||
considered if SortKeys is true.
|
||||
|
||||
Dump Usage
|
||||
|
||||
Simply call spew.Dump with a list of variables you want to dump:
|
||||
|
||||
spew.Dump(myVar1, myVar2, ...)
|
||||
|
||||
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
||||
io.Writer. For example, to dump to standard error:
|
||||
|
||||
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
||||
|
||||
A third option is to call spew.Sdump to get the formatted output as a string:
|
||||
|
||||
str := spew.Sdump(myVar1, myVar2, ...)
|
||||
|
||||
Sample Dump Output
|
||||
|
||||
See the Dump example for details on the setup of the types and variables being
|
||||
shown here.
|
||||
|
||||
(main.Foo) {
|
||||
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||
flag: (main.Flag) flagTwo,
|
||||
data: (uintptr) <nil>
|
||||
}),
|
||||
ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||
(string) (len=3) "one": (bool) true
|
||||
}
|
||||
}
|
||||
|
||||
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
||||
command as shown.
|
||||
([]uint8) (len=32 cap=32) {
|
||||
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||
00000020 31 32 |12|
|
||||
}
|
||||
|
||||
Custom Formatter
|
||||
|
||||
Spew provides a custom formatter that implements the fmt.Formatter interface
|
||||
so that it integrates cleanly with standard fmt package printing functions. The
|
||||
formatter is useful for inline printing of smaller data types similar to the
|
||||
standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Custom Formatter Usage
|
||||
|
||||
The simplest way to make use of the spew custom formatter is to call one of the
|
||||
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
||||
functions have syntax you are most likely already familiar with:
|
||||
|
||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
spew.Println(myVar, myVar2)
|
||||
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
|
||||
See the Index for the full list convenience functions.
|
||||
|
||||
Sample Formatter Output
|
||||
|
||||
Double pointer to a uint8:
|
||||
%v: <**>5
|
||||
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||
%#v: (**uint8)5
|
||||
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||
|
||||
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||
%v: <*>{1 <*><shown>}
|
||||
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||
|
||||
See the Printf example for details on the setup of variables being shown
|
||||
here.
|
||||
|
||||
Errors
|
||||
|
||||
Since it is possible for custom Stringer/error interfaces to panic, spew
|
||||
detects them and handles them internally by printing the panic information
|
||||
inline with the output. Since spew is intended to provide deep pretty printing
|
||||
capabilities on structures, it intentionally does not return any errors.
|
||||
*/
|
||||
package spew
|
@ -0,0 +1,509 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// uint8Type is a reflect.Type representing a uint8. It is used to
|
||||
// convert cgo types to uint8 slices for hexdumping.
|
||||
uint8Type = reflect.TypeOf(uint8(0))
|
||||
|
||||
// cCharRE is a regular expression that matches a cgo char.
|
||||
// It is used to detect character arrays to hexdump them.
|
||||
cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
|
||||
|
||||
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
||||
// char. It is used to detect unsigned character arrays to hexdump
|
||||
// them.
|
||||
cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
|
||||
|
||||
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
||||
// It is used to detect uint8_t arrays to hexdump them.
|
||||
cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
|
||||
)
|
||||
|
||||
// dumpState contains information about the state of a dump operation.
|
||||
type dumpState struct {
|
||||
w io.Writer
|
||||
depth int
|
||||
pointers map[uintptr]int
|
||||
ignoreNextType bool
|
||||
ignoreNextIndent bool
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// indent performs indentation according to the depth level and cs.Indent
|
||||
// option.
|
||||
func (d *dumpState) indent() {
|
||||
if d.ignoreNextIndent {
|
||||
d.ignoreNextIndent = false
|
||||
return
|
||||
}
|
||||
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
||||
}
|
||||
|
||||
// unpackValue returns values inside of non-nil interfaces when possible.
|
||||
// This is useful for data types like structs, arrays, slices, and maps which
|
||||
// can contain varying types packed inside an interface.
|
||||
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
||||
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
||||
func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||
// Remove pointers at or below the current depth from map used to detect
|
||||
// circular refs.
|
||||
for k, depth := range d.pointers {
|
||||
if depth >= d.depth {
|
||||
delete(d.pointers, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep list of all dereferenced pointers to show later.
|
||||
pointerChain := make([]uintptr, 0)
|
||||
|
||||
// Figure out how many levels of indirection there are by dereferencing
|
||||
// pointers and unpacking interfaces down the chain while detecting circular
|
||||
// references.
|
||||
nilFound := false
|
||||
cycleFound := false
|
||||
indirects := 0
|
||||
ve := v
|
||||
for ve.Kind() == reflect.Ptr {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
indirects++
|
||||
addr := ve.Pointer()
|
||||
pointerChain = append(pointerChain, addr)
|
||||
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
||||
cycleFound = true
|
||||
indirects--
|
||||
break
|
||||
}
|
||||
d.pointers[addr] = d.depth
|
||||
|
||||
ve = ve.Elem()
|
||||
if ve.Kind() == reflect.Interface {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
ve = ve.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Display type information.
|
||||
d.w.Write(openParenBytes)
|
||||
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||
d.w.Write([]byte(ve.Type().String()))
|
||||
d.w.Write(closeParenBytes)
|
||||
|
||||
// Display pointer information.
|
||||
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
||||
d.w.Write(openParenBytes)
|
||||
for i, addr := range pointerChain {
|
||||
if i > 0 {
|
||||
d.w.Write(pointerChainBytes)
|
||||
}
|
||||
printHexPtr(d.w, addr)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// Display dereferenced value.
|
||||
d.w.Write(openParenBytes)
|
||||
switch {
|
||||
case nilFound:
|
||||
d.w.Write(nilAngleBytes)
|
||||
|
||||
case cycleFound:
|
||||
d.w.Write(circularBytes)
|
||||
|
||||
default:
|
||||
d.ignoreNextType = true
|
||||
d.dump(ve)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
||||
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
||||
func (d *dumpState) dumpSlice(v reflect.Value) {
|
||||
// Determine whether this type should be hex dumped or not. Also,
|
||||
// for types which should be hexdumped, try to use the underlying data
|
||||
// first, then fall back to trying to convert them to a uint8 slice.
|
||||
var buf []uint8
|
||||
doConvert := false
|
||||
doHexDump := false
|
||||
numEntries := v.Len()
|
||||
if numEntries > 0 {
|
||||
vt := v.Index(0).Type()
|
||||
vts := vt.String()
|
||||
switch {
|
||||
// C types that need to be converted.
|
||||
case cCharRE.MatchString(vts):
|
||||
fallthrough
|
||||
case cUnsignedCharRE.MatchString(vts):
|
||||
fallthrough
|
||||
case cUint8tCharRE.MatchString(vts):
|
||||
doConvert = true
|
||||
|
||||
// Try to use existing uint8 slices and fall back to converting
|
||||
// and copying if that fails.
|
||||
case vt.Kind() == reflect.Uint8:
|
||||
// We need an addressable interface to convert the type
|
||||
// to a byte slice. However, the reflect package won't
|
||||
// give us an interface on certain things like
|
||||
// unexported struct fields in order to enforce
|
||||
// visibility rules. We use unsafe, when available, to
|
||||
// bypass these restrictions since this package does not
|
||||
// mutate the values.
|
||||
vs := v
|
||||
if !vs.CanInterface() || !vs.CanAddr() {
|
||||
vs = unsafeReflectValue(vs)
|
||||
}
|
||||
if !UnsafeDisabled {
|
||||
vs = vs.Slice(0, numEntries)
|
||||
|
||||
// Use the existing uint8 slice if it can be
|
||||
// type asserted.
|
||||
iface := vs.Interface()
|
||||
if slice, ok := iface.([]uint8); ok {
|
||||
buf = slice
|
||||
doHexDump = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The underlying data needs to be converted if it can't
|
||||
// be type asserted to a uint8 slice.
|
||||
doConvert = true
|
||||
}
|
||||
|
||||
// Copy and convert the underlying type if needed.
|
||||
if doConvert && vt.ConvertibleTo(uint8Type) {
|
||||
// Convert and copy each element into a uint8 byte
|
||||
// slice.
|
||||
buf = make([]uint8, numEntries)
|
||||
for i := 0; i < numEntries; i++ {
|
||||
vv := v.Index(i)
|
||||
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
||||
}
|
||||
doHexDump = true
|
||||
}
|
||||
}
|
||||
|
||||
// Hexdump the entire slice as needed.
|
||||
if doHexDump {
|
||||
indent := strings.Repeat(d.cs.Indent, d.depth)
|
||||
str := indent + hex.Dump(buf)
|
||||
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
||||
str = strings.TrimRight(str, d.cs.Indent)
|
||||
d.w.Write([]byte(str))
|
||||
return
|
||||
}
|
||||
|
||||
// Recursively call dump for each item.
|
||||
for i := 0; i < numEntries; i++ {
|
||||
d.dump(d.unpackValue(v.Index(i)))
|
||||
if i < (numEntries - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
||||
// value to figure out what kind of object we are dealing with and formats it
|
||||
// appropriately. It is a recursive function, however circular data structures
|
||||
// are detected and handled properly.
|
||||
func (d *dumpState) dump(v reflect.Value) {
|
||||
// Handle invalid reflect values immediately.
|
||||
kind := v.Kind()
|
||||
if kind == reflect.Invalid {
|
||||
d.w.Write(invalidAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle pointers specially.
|
||||
if kind == reflect.Ptr {
|
||||
d.indent()
|
||||
d.dumpPtr(v)
|
||||
return
|
||||
}
|
||||
|
||||
// Print type information unless already handled elsewhere.
|
||||
if !d.ignoreNextType {
|
||||
d.indent()
|
||||
d.w.Write(openParenBytes)
|
||||
d.w.Write([]byte(v.Type().String()))
|
||||
d.w.Write(closeParenBytes)
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
d.ignoreNextType = false
|
||||
|
||||
// Display length and capacity if the built-in len and cap functions
|
||||
// work with the value's kind and the len/cap itself is non-zero.
|
||||
valueLen, valueCap := 0, 0
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.Chan:
|
||||
valueLen, valueCap = v.Len(), v.Cap()
|
||||
case reflect.Map, reflect.String:
|
||||
valueLen = v.Len()
|
||||
}
|
||||
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
||||
d.w.Write(openParenBytes)
|
||||
if valueLen != 0 {
|
||||
d.w.Write(lenEqualsBytes)
|
||||
printInt(d.w, int64(valueLen), 10)
|
||||
}
|
||||
if !d.cs.DisableCapacities && valueCap != 0 {
|
||||
if valueLen != 0 {
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
d.w.Write(capEqualsBytes)
|
||||
printInt(d.w, int64(valueCap), 10)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
|
||||
// Call Stringer/error interfaces if they exist and the handle methods flag
|
||||
// is enabled
|
||||
if !d.cs.DisableMethods {
|
||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||
if handled := handleMethods(d.cs, d.w, v); handled {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Invalid:
|
||||
// Do nothing. We should never get here since invalid has already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Bool:
|
||||
printBool(d.w, v.Bool())
|
||||
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
printInt(d.w, v.Int(), 10)
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
printUint(d.w, v.Uint(), 10)
|
||||
|
||||
case reflect.Float32:
|
||||
printFloat(d.w, v.Float(), 32)
|
||||
|
||||
case reflect.Float64:
|
||||
printFloat(d.w, v.Float(), 64)
|
||||
|
||||
case reflect.Complex64:
|
||||
printComplex(d.w, v.Complex(), 32)
|
||||
|
||||
case reflect.Complex128:
|
||||
printComplex(d.w, v.Complex(), 64)
|
||||
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case reflect.Array:
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
d.dumpSlice(v)
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.String:
|
||||
d.w.Write([]byte(strconv.Quote(v.String())))
|
||||
|
||||
case reflect.Interface:
|
||||
// The only time we should get here is for nil interfaces due to
|
||||
// unpackValue calls.
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
// Do nothing. We should never get here since pointers have already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Map:
|
||||
// nil maps should be indicated as different than empty maps
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
numEntries := v.Len()
|
||||
keys := v.MapKeys()
|
||||
if d.cs.SortKeys {
|
||||
sortValues(keys, d.cs)
|
||||
}
|
||||
for i, key := range keys {
|
||||
d.dump(d.unpackValue(key))
|
||||
d.w.Write(colonSpaceBytes)
|
||||
d.ignoreNextIndent = true
|
||||
d.dump(d.unpackValue(v.MapIndex(key)))
|
||||
if i < (numEntries - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Struct:
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
vt := v.Type()
|
||||
numFields := v.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
d.indent()
|
||||
vtf := vt.Field(i)
|
||||
d.w.Write([]byte(vtf.Name))
|
||||
d.w.Write(colonSpaceBytes)
|
||||
d.ignoreNextIndent = true
|
||||
d.dump(d.unpackValue(v.Field(i)))
|
||||
if i < (numFields - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Uintptr:
|
||||
printHexPtr(d.w, uintptr(v.Uint()))
|
||||
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
printHexPtr(d.w, v.Pointer())
|
||||
|
||||
// There were not any other types at the time this code was written, but
|
||||
// fall back to letting the default fmt package handle it in case any new
|
||||
// types are added.
|
||||
default:
|
||||
if v.CanInterface() {
|
||||
fmt.Fprintf(d.w, "%v", v.Interface())
|
||||
} else {
|
||||
fmt.Fprintf(d.w, "%v", v.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fdump is a helper function to consolidate the logic from the various public
|
||||
// methods which take varying writers and config states.
|
||||
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
||||
for _, arg := range a {
|
||||
if arg == nil {
|
||||
w.Write(interfaceBytes)
|
||||
w.Write(spaceBytes)
|
||||
w.Write(nilAngleBytes)
|
||||
w.Write(newlineBytes)
|
||||
continue
|
||||
}
|
||||
|
||||
d := dumpState{w: w, cs: cs}
|
||||
d.pointers = make(map[uintptr]int)
|
||||
d.dump(reflect.ValueOf(arg))
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||
// exactly the same as Dump.
|
||||
func Fdump(w io.Writer, a ...interface{}) {
|
||||
fdump(&Config, w, a...)
|
||||
}
|
||||
|
||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||
// as Dump.
|
||||
func Sdump(a ...interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
fdump(&Config, &buf, a...)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
/*
|
||||
Dump displays the passed parameters to standard out with newlines, customizable
|
||||
indentation, and additional debug information such as complete types and all
|
||||
pointer addresses used to indirect to the final value. It provides the
|
||||
following features over the built-in printing facilities provided by the fmt
|
||||
package:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output
|
||||
|
||||
The configuration options are controlled by an exported package global,
|
||||
spew.Config. See ConfigState for options documentation.
|
||||
|
||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||
get the formatted result as a string.
|
||||
*/
|
||||
func Dump(a ...interface{}) {
|
||||
fdump(&Config, os.Stdout, a...)
|
||||
}
|
@ -0,0 +1,419 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// supportedFlags is a list of all the character flags supported by fmt package.
|
||||
const supportedFlags = "0-+# "
|
||||
|
||||
// formatState implements the fmt.Formatter interface and contains information
|
||||
// about the state of a formatting operation. The NewFormatter function can
|
||||
// be used to get a new Formatter which can be used directly as arguments
|
||||
// in standard fmt package printing calls.
|
||||
type formatState struct {
|
||||
value interface{}
|
||||
fs fmt.State
|
||||
depth int
|
||||
pointers map[uintptr]int
|
||||
ignoreNextType bool
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// buildDefaultFormat recreates the original format string without precision
|
||||
// and width information to pass in to fmt.Sprintf in the case of an
|
||||
// unrecognized type. Unless new types are added to the language, this
|
||||
// function won't ever be called.
|
||||
func (f *formatState) buildDefaultFormat() (format string) {
|
||||
buf := bytes.NewBuffer(percentBytes)
|
||||
|
||||
for _, flag := range supportedFlags {
|
||||
if f.fs.Flag(int(flag)) {
|
||||
buf.WriteRune(flag)
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteRune('v')
|
||||
|
||||
format = buf.String()
|
||||
return format
|
||||
}
|
||||
|
||||
// constructOrigFormat recreates the original format string including precision
|
||||
// and width information to pass along to the standard fmt package. This allows
|
||||
// automatic deferral of all format strings this package doesn't support.
|
||||
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
||||
buf := bytes.NewBuffer(percentBytes)
|
||||
|
||||
for _, flag := range supportedFlags {
|
||||
if f.fs.Flag(int(flag)) {
|
||||
buf.WriteRune(flag)
|
||||
}
|
||||
}
|
||||
|
||||
if width, ok := f.fs.Width(); ok {
|
||||
buf.WriteString(strconv.Itoa(width))
|
||||
}
|
||||
|
||||
if precision, ok := f.fs.Precision(); ok {
|
||||
buf.Write(precisionBytes)
|
||||
buf.WriteString(strconv.Itoa(precision))
|
||||
}
|
||||
|
||||
buf.WriteRune(verb)
|
||||
|
||||
format = buf.String()
|
||||
return format
|
||||
}
|
||||
|
||||
// unpackValue returns values inside of non-nil interfaces when possible and
|
||||
// ensures that types for values which have been unpacked from an interface
|
||||
// are displayed when the show types flag is also set.
|
||||
// This is useful for data types like structs, arrays, slices, and maps which
|
||||
// can contain varying types packed inside an interface.
|
||||
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
||||
if v.Kind() == reflect.Interface {
|
||||
f.ignoreNextType = false
|
||||
if !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
||||
func (f *formatState) formatPtr(v reflect.Value) {
|
||||
// Display nil if top level pointer is nil.
|
||||
showTypes := f.fs.Flag('#')
|
||||
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove pointers at or below the current depth from map used to detect
|
||||
// circular refs.
|
||||
for k, depth := range f.pointers {
|
||||
if depth >= f.depth {
|
||||
delete(f.pointers, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep list of all dereferenced pointers to possibly show later.
|
||||
pointerChain := make([]uintptr, 0)
|
||||
|
||||
// Figure out how many levels of indirection there are by derferencing
|
||||
// pointers and unpacking interfaces down the chain while detecting circular
|
||||
// references.
|
||||
nilFound := false
|
||||
cycleFound := false
|
||||
indirects := 0
|
||||
ve := v
|
||||
for ve.Kind() == reflect.Ptr {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
indirects++
|
||||
addr := ve.Pointer()
|
||||
pointerChain = append(pointerChain, addr)
|
||||
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
||||
cycleFound = true
|
||||
indirects--
|
||||
break
|
||||
}
|
||||
f.pointers[addr] = f.depth
|
||||
|
||||
ve = ve.Elem()
|
||||
if ve.Kind() == reflect.Interface {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
ve = ve.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Display type or indirection level depending on flags.
|
||||
if showTypes && !f.ignoreNextType {
|
||||
f.fs.Write(openParenBytes)
|
||||
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||
f.fs.Write([]byte(ve.Type().String()))
|
||||
f.fs.Write(closeParenBytes)
|
||||
} else {
|
||||
if nilFound || cycleFound {
|
||||
indirects += strings.Count(ve.Type().String(), "*")
|
||||
}
|
||||
f.fs.Write(openAngleBytes)
|
||||
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
||||
f.fs.Write(closeAngleBytes)
|
||||
}
|
||||
|
||||
// Display pointer information depending on flags.
|
||||
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
||||
f.fs.Write(openParenBytes)
|
||||
for i, addr := range pointerChain {
|
||||
if i > 0 {
|
||||
f.fs.Write(pointerChainBytes)
|
||||
}
|
||||
printHexPtr(f.fs, addr)
|
||||
}
|
||||
f.fs.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// Display dereferenced value.
|
||||
switch {
|
||||
case nilFound:
|
||||
f.fs.Write(nilAngleBytes)
|
||||
|
||||
case cycleFound:
|
||||
f.fs.Write(circularShortBytes)
|
||||
|
||||
default:
|
||||
f.ignoreNextType = true
|
||||
f.format(ve)
|
||||
}
|
||||
}
|
||||
|
||||
// format is the main workhorse for providing the Formatter interface. It
|
||||
// uses the passed reflect value to figure out what kind of object we are
|
||||
// dealing with and formats it appropriately. It is a recursive function,
|
||||
// however circular data structures are detected and handled properly.
|
||||
func (f *formatState) format(v reflect.Value) {
|
||||
// Handle invalid reflect values immediately.
|
||||
kind := v.Kind()
|
||||
if kind == reflect.Invalid {
|
||||
f.fs.Write(invalidAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle pointers specially.
|
||||
if kind == reflect.Ptr {
|
||||
f.formatPtr(v)
|
||||
return
|
||||
}
|
||||
|
||||
// Print type information unless already handled elsewhere.
|
||||
if !f.ignoreNextType && f.fs.Flag('#') {
|
||||
f.fs.Write(openParenBytes)
|
||||
f.fs.Write([]byte(v.Type().String()))
|
||||
f.fs.Write(closeParenBytes)
|
||||
}
|
||||
f.ignoreNextType = false
|
||||
|
||||
// Call Stringer/error interfaces if they exist and the handle methods
|
||||
// flag is enabled.
|
||||
if !f.cs.DisableMethods {
|
||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||
if handled := handleMethods(f.cs, f.fs, v); handled {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Invalid:
|
||||
// Do nothing. We should never get here since invalid has already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Bool:
|
||||
printBool(f.fs, v.Bool())
|
||||
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
printInt(f.fs, v.Int(), 10)
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
printUint(f.fs, v.Uint(), 10)
|
||||
|
||||
case reflect.Float32:
|
||||
printFloat(f.fs, v.Float(), 32)
|
||||
|
||||
case reflect.Float64:
|
||||
printFloat(f.fs, v.Float(), 64)
|
||||
|
||||
case reflect.Complex64:
|
||||
printComplex(f.fs, v.Complex(), 32)
|
||||
|
||||
case reflect.Complex128:
|
||||
printComplex(f.fs, v.Complex(), 64)
|
||||
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case reflect.Array:
|
||||
f.fs.Write(openBracketBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
numEntries := v.Len()
|
||||
for i := 0; i < numEntries; i++ {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(v.Index(i)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeBracketBytes)
|
||||
|
||||
case reflect.String:
|
||||
f.fs.Write([]byte(v.String()))
|
||||
|
||||
case reflect.Interface:
|
||||
// The only time we should get here is for nil interfaces due to
|
||||
// unpackValue calls.
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
// Do nothing. We should never get here since pointers have already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Map:
|
||||
// nil maps should be indicated as different than empty maps
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
|
||||
f.fs.Write(openMapBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
keys := v.MapKeys()
|
||||
if f.cs.SortKeys {
|
||||
sortValues(keys, f.cs)
|
||||
}
|
||||
for i, key := range keys {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(key))
|
||||
f.fs.Write(colonBytes)
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(v.MapIndex(key)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeMapBytes)
|
||||
|
||||
case reflect.Struct:
|
||||
numFields := v.NumField()
|
||||
f.fs.Write(openBraceBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
vt := v.Type()
|
||||
for i := 0; i < numFields; i++ {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
vtf := vt.Field(i)
|
||||
if f.fs.Flag('+') || f.fs.Flag('#') {
|
||||
f.fs.Write([]byte(vtf.Name))
|
||||
f.fs.Write(colonBytes)
|
||||
}
|
||||
f.format(f.unpackValue(v.Field(i)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Uintptr:
|
||||
printHexPtr(f.fs, uintptr(v.Uint()))
|
||||
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
printHexPtr(f.fs, v.Pointer())
|
||||
|
||||
// There were not any other types at the time this code was written, but
|
||||
// fall back to letting the default fmt package handle it if any get added.
|
||||
default:
|
||||
format := f.buildDefaultFormat()
|
||||
if v.CanInterface() {
|
||||
fmt.Fprintf(f.fs, format, v.Interface())
|
||||
} else {
|
||||
fmt.Fprintf(f.fs, format, v.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
||||
// details.
|
||||
func (f *formatState) Format(fs fmt.State, verb rune) {
|
||||
f.fs = fs
|
||||
|
||||
// Use standard formatting for verbs that are not v.
|
||||
if verb != 'v' {
|
||||
format := f.constructOrigFormat(verb)
|
||||
fmt.Fprintf(fs, format, f.value)
|
||||
return
|
||||
}
|
||||
|
||||
if f.value == nil {
|
||||
if fs.Flag('#') {
|
||||
fs.Write(interfaceBytes)
|
||||
}
|
||||
fs.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
f.format(reflect.ValueOf(f.value))
|
||||
}
|
||||
|
||||
// newFormatter is a helper function to consolidate the logic from the various
|
||||
// public methods which take varying config states.
|
||||
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
||||
fs := &formatState{value: v, cs: cs}
|
||||
fs.pointers = make(map[uintptr]int)
|
||||
return fs
|
||||
}
|
||||
|
||||
/*
|
||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||
interface. As a result, it integrates cleanly with standard fmt package
|
||||
printing functions. The formatter is useful for inline printing of smaller data
|
||||
types similar to the standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Typically this function shouldn't be called directly. It is much easier to make
|
||||
use of the custom formatter by calling one of the convenience functions such as
|
||||
Printf, Println, or Fprintf.
|
||||
*/
|
||||
func NewFormatter(v interface{}) fmt.Formatter {
|
||||
return newFormatter(&Config, v)
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the formatted string as a value that satisfies error. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Errorf(format string, a ...interface{}) (err error) {
|
||||
return fmt.Errorf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(w, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(w, format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||
// passed with a default Formatter interface returned by NewFormatter. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(w, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Print(a ...interface{}) (n int, err error) {
|
||||
return fmt.Print(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Printf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Println(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprint(a ...interface{}) string {
|
||||
return fmt.Sprint(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprintf(format string, a ...interface{}) string {
|
||||
return fmt.Sprintf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||
// were passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprintln(a ...interface{}) string {
|
||||
return fmt.Sprintln(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||
// length with each argument converted to a default spew Formatter interface.
|
||||
func convertArgs(args []interface{}) (formatters []interface{}) {
|
||||
formatters = make([]interface{}, len(args))
|
||||
for index, arg := range args {
|
||||
formatters[index] = NewFormatter(arg)
|
||||
}
|
||||
return formatters
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
.idea
|
||||
.env
|
||||
/lab
|
||||
# Compiled test files
|
||||
*.test
|
||||
|
||||
# Coverage output files
|
||||
coverage.txt
|
||||
coverage.html
|
@ -0,0 +1,39 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.14.x
|
||||
- 1.15.x
|
||||
- 1.16.x
|
||||
- 1.17.x
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
services:
|
||||
- mongodb
|
||||
|
||||
before_install:
|
||||
- go get -t -v
|
||||
|
||||
# Add user and replica set to mongo by help of https://georgeshank.com/how-to-enable-a-mongodb-replica-set-on-travis/
|
||||
before_script:
|
||||
- sleep 15 # To Fix issue of https://docs.travis-ci.com/user/database-setup/#mongodb-does-not-immediately-accept-connections
|
||||
- |
|
||||
mongo admin --eval 'db.createUser(
|
||||
{
|
||||
user: "root",
|
||||
pwd: "12345",
|
||||
roles: [ { role: "root", db: "admin" } ]
|
||||
}
|
||||
)'
|
||||
- 'echo "replication:" | sudo tee -a /etc/mongod.conf'
|
||||
- 'echo " replSetName: rs0" | sudo tee -a /etc/mongod.conf'
|
||||
- sudo systemctl restart mongod
|
||||
- sleep 20 # To Fix issue of https://docs.travis-ci.com/user/database-setup/#mongodb-does-not-immediately-accept-connections
|
||||
- mongo -u root -p 12345 --eval 'rs.initiate()'
|
||||
|
||||
script:
|
||||
- go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
@ -0,0 +1,190 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2019 Kamva.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -0,0 +1,425 @@
|
||||
<p align="center">
|
||||
<img width="250" src="https://user-images.githubusercontent.com/22454054/71487214-759cb680-282f-11ea-9bcf-caa663b3e348.png" />
|
||||
</p>
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="https://goreportcard.com/report/github.com/Kamva/mgm">
|
||||
<img src="https://goreportcard.com/badge/github.com/Kamva/mgm">
|
||||
</a>
|
||||
<a href="https://godoc.org/github.com/Kamva/mgm">
|
||||
<img src="https://godoc.org/github.com/Kamva/mgm?status.svg" alt="GoDoc">
|
||||
</a>
|
||||
<a href="https://travis-ci.com/Kamva/mgm">
|
||||
<img src="https://travis-ci.com/Kamva/mgm.svg?branch=master" alt="Build Status">
|
||||
</a>
|
||||
<a href="https://codecov.io/gh/Kamva/mgm">
|
||||
<img src="https://codecov.io/gh/Kamva/mgm/branch/master/graph/badge.svg" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
# Mongo Go Models
|
||||
|
||||
The Mongo ODM for Go
|
||||
|
||||
- [Features](#features)
|
||||
- [Requirements](#requirements)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [Bugs / Feature Requests](#bugs--feature-request)
|
||||
- [Communicate With Us](#communicate-with-us)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
|
||||
## Features
|
||||
- Define your models and perform CRUD operations with hooks before/after each operation.
|
||||
- `mgm` makes Mongo search and aggregation super easy to do in Golang.
|
||||
- Just set up your configs once and get collections anywhere you need them.
|
||||
- `mgm` predefines all Mongo operators and keys, so you don't have to hardcode them yourself.
|
||||
- `mgm` wraps the official Mongo Go Driver.
|
||||
|
||||
## Requirements
|
||||
- Go 1.10 or higher.
|
||||
- MongoDB 2.6 and higher.
|
||||
|
||||
## Installation
|
||||
|
||||
__Important Note__: We changed the package name from
|
||||
`github.com/Kamva/mgm/v3` (uppercase `Kamva`)
|
||||
to `github.com/kamva/mgm/v3` (lowercase `kamva`) starting with version 3.1.0.
|
||||
|
||||
```bash
|
||||
go get github.com/kamva/mgm/v3
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
To get started, import the `mgm` package and setup the default config:
|
||||
```go
|
||||
import (
|
||||
"github.com/kamva/mgm/v3"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Setup the mgm default config
|
||||
err := mgm.SetDefaultConfig(nil, "mgm_lab", options.Client().ApplyURI("mongodb://root:12345@localhost:27017"))
|
||||
}
|
||||
```
|
||||
|
||||
Define your model:
|
||||
```go
|
||||
type Book struct {
|
||||
// DefaultModel adds _id, created_at and updated_at fields to the Model
|
||||
mgm.DefaultModel `bson:",inline"`
|
||||
Name string `json:"name" bson:"name"`
|
||||
Pages int `json:"pages" bson:"pages"`
|
||||
}
|
||||
|
||||
func NewBook(name string, pages int) *Book {
|
||||
return &Book{
|
||||
Name: name,
|
||||
Pages: pages,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Insert new document:
|
||||
```go
|
||||
book := NewBook("Pride and Prejudice", 345)
|
||||
|
||||
// Make sure to pass the model by reference.
|
||||
err := mgm.Coll(book).Create(book)
|
||||
```
|
||||
|
||||
Find one document
|
||||
```go
|
||||
// Get the document's collection
|
||||
book := &Book{}
|
||||
coll := mgm.Coll(book)
|
||||
|
||||
// Find and decode the doc to a book model.
|
||||
_ = coll.FindByID("5e0518aa8f1a52b0b9410ee3", book)
|
||||
|
||||
// Get the first doc of the collection
|
||||
_ = coll.First(bson.M{}, book)
|
||||
|
||||
// Get the first doc of a collection using a filter
|
||||
_ = coll.First(bson.M{"pages":400}, book)
|
||||
```
|
||||
|
||||
Update a document
|
||||
```go
|
||||
// Find your book
|
||||
book := findMyFavoriteBook()
|
||||
|
||||
// and update it
|
||||
book.Name = "Moulin Rouge!"
|
||||
err := mgm.Coll(book).Update(book)
|
||||
```
|
||||
|
||||
Delete a document
|
||||
```go
|
||||
// Just find and delete your document
|
||||
err := mgm.Coll(book).Delete(book)
|
||||
```
|
||||
|
||||
Find and decode a result:
|
||||
```go
|
||||
result := []Book{}
|
||||
|
||||
err := mgm.Coll(&Book{}).SimpleFind(&result, bson.M{"pages": bson.M{operator.Gt: 24}})
|
||||
```
|
||||
|
||||
### A Model's Default Fields
|
||||
Each model by default (by using `DefaultModel` struct) has
|
||||
the following fields:
|
||||
- `_id` : The document ID.
|
||||
|
||||
- `created_at`: The creation date of a doc. When saving a new doc, this is automatically populated by the `Creating` hook.
|
||||
- `updated_at`: The last updated date of a doc. When saving a doc, this is automatically populated by the `Saving` hook.
|
||||
|
||||
### A Model's Hooks
|
||||
|
||||
Each model has the following hooks:
|
||||
- `Creating`: Called when creating a new model.
|
||||
Signature : `Creating(context.Context) error`
|
||||
|
||||
- `Created`: Called after a new model is created.
|
||||
Signature : `Created(context.Context) error`
|
||||
|
||||
- `Updating`: Called when updating model.
|
||||
Signature : `Updating(context.Context) error`
|
||||
|
||||
- `Updated` : Called after a model is updated.
|
||||
Signature : `Updated(ctx context.Context, result *mongo.UpdateResult) error`
|
||||
|
||||
- `Saving`: Called when creating or updating a model.
|
||||
Signature : `Saving(context.Context) error`
|
||||
|
||||
- `Saved`: Called after a model is created or updated.
|
||||
Signature: `Saved(context.Context) error`
|
||||
|
||||
- `Deleting`: Called when deleting a model.
|
||||
Signature: `Deleting(context.Context) error`
|
||||
|
||||
- `Deleted`: Called after a model is deleted.
|
||||
Signature: `Deleted(ctx context.Context, result *mongo.DeleteResult) error`
|
||||
|
||||
**Notes about hooks**:
|
||||
- Each model by default uses the `Creating` and `Saving` hooks, so if you want to define those hooks yourself, remember to invoke the `DefaultModel` hooks from your own hooks.
|
||||
- Collection methods that call these hooks:
|
||||
- `Create` & `CreateWithCtx`
|
||||
- `Update` & `UpdateWithCtx`
|
||||
- `Delete` & `DeleteWithCtx`
|
||||
|
||||
Example:
|
||||
```go
|
||||
func (model *Book) Creating(ctx context.Context) error {
|
||||
// Call the DefaultModel Creating hook
|
||||
if err := model.DefaultModel.Creating(ctx); err!=nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We can validate the fields of a model and return an error to prevent a document's insertion.
|
||||
if model.Pages < 1 {
|
||||
return errors.New("book must have at least one page")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
### Configuration
|
||||
The `mgm` default configuration has a context timeout:
|
||||
```go
|
||||
func init() {
|
||||
_ = mgm.SetDefaultConfig(&mgm.Config{CtxTimeout:12 * time.Second}, "mgm_lab", options.Client().ApplyURI("mongodb://root:12345@localhost:27017"))
|
||||
}
|
||||
|
||||
// To get the context, just call the Ctx() method, assign it to a variable
|
||||
ctx := mgm.Ctx()
|
||||
|
||||
// and use it
|
||||
coll := mgm.Coll(&Book{})
|
||||
coll.FindOne(ctx, bson.M{})
|
||||
|
||||
// Or invoke Ctx() and use it directly
|
||||
coll.FindOne(mgm.Ctx(), bson.M{})
|
||||
```
|
||||
|
||||
|
||||
### Collections
|
||||
Get a model's collection:
|
||||
```go
|
||||
coll := mgm.Coll(&Book{})
|
||||
|
||||
// Do something with the collection
|
||||
```
|
||||
|
||||
`mgm` automatically detects the name of a model's collection:
|
||||
```go
|
||||
book := Book{}
|
||||
|
||||
// Print your model's collection name.
|
||||
collName := mgm.CollName(&book)
|
||||
fmt.Println(collName) // output: books
|
||||
```
|
||||
|
||||
You can also set a custom collection name for your model by implementing the `CollectionNameGetter` interface:
|
||||
```go
|
||||
func (model *Book) CollectionName() string {
|
||||
return "my_books"
|
||||
}
|
||||
|
||||
// mgm returns the "my_books" collection
|
||||
coll := mgm.Coll(&Book{})
|
||||
```
|
||||
|
||||
Get a collection by its name (without needing to define a model for it):
|
||||
```go
|
||||
coll := mgm.CollectionByName("my_coll")
|
||||
|
||||
// Do Aggregation, etc. with the collection
|
||||
```
|
||||
|
||||
Customize the model db by implementing the `CollectionGetter`
|
||||
interface:
|
||||
```go
|
||||
func (model *Book) Collection() *mgm.Collection {
|
||||
// Get default connection client
|
||||
_, client, _, err := mgm.DefaultConfigs()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
db := client.Database("another_db")
|
||||
return mgm.NewCollection(db, "my_collection")
|
||||
}
|
||||
```
|
||||
|
||||
Or return a model's collection from another connection:
|
||||
|
||||
```go
|
||||
func (model *Book) Collection() *mgm.Collection {
|
||||
// Create new client
|
||||
client, err := mgm.NewClient(options.Client().ApplyURI("mongodb://root:12345@localhost:27017"))
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Get the model's db
|
||||
db := client.Database("my_second_db")
|
||||
|
||||
// return the model's custom collection
|
||||
return mgm.NewCollection(db, "my_collection")
|
||||
}
|
||||
```
|
||||
### Aggregation
|
||||
While we can use Mongo Go Driver Aggregate features, `mgm` also
|
||||
provides simpler methods to perform aggregations:
|
||||
|
||||
Run an aggregation and decode the result:
|
||||
```go
|
||||
authorCollName := mgm.Coll(&Author{}).Name()
|
||||
result := []Book{}
|
||||
|
||||
// Lookup with just a single line of code
|
||||
_ := mgm.Coll(&Book{}).SimpleAggregate(&result, builder.Lookup(authorCollName, "auth_id", "_id", "author"))
|
||||
|
||||
// Multi stage (mix of mgm builders and raw stages)
|
||||
_ := mgm.Coll(&Book{}).SimpleAggregate(&result,
|
||||
builder.Lookup(authorCollName, "auth_id", "_id", "author"),
|
||||
M{operator.Project: M{"pages": 0}},
|
||||
)
|
||||
|
||||
// Do something with result...
|
||||
```
|
||||
|
||||
Do aggregations using the mongo Aggregation method:
|
||||
```go
|
||||
import (
|
||||
"github.com/kamva/mgm/v3"
|
||||
"github.com/kamva/mgm/v3/builder"
|
||||
"github.com/kamva/mgm/v3/field"
|
||||
. "go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
// The Author model collection
|
||||
authorColl := mgm.Coll(&Author{})
|
||||
|
||||
cur, err := mgm.Coll(&Book{}).Aggregate(mgm.Ctx(), A{
|
||||
// The S function accepts operators as parameters and returns a bson.M type.
|
||||
builder.S(builder.Lookup(authorColl.Name(), "author_id", field.Id, "author")),
|
||||
})
|
||||
```
|
||||
|
||||
A more complex example and mixes with mongo raw pipelines:
|
||||
```go
|
||||
import (
|
||||
"github.com/kamva/mgm/v3"
|
||||
"github.com/kamva/mgm/v3/builder"
|
||||
"github.com/kamva/mgm/v3/field"
|
||||
"github.com/kamva/mgm/v3/operator"
|
||||
. "go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
// Author model collection
|
||||
authorColl := mgm.Coll(&Author{})
|
||||
|
||||
_, err := mgm.Coll(&Book{}).Aggregate(mgm.Ctx(), A{
|
||||
// S function get operators and return bson.M type.
|
||||
builder.S(builder.Lookup(authorColl.Name(), "author_id", field.Id, "author")),
|
||||
builder.S(builder.Group("pages", M{"books": M{operator.Push: M{"name": "$name", "author": "$author"}}})),
|
||||
M{operator.Unwind: "$books"},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
```
|
||||
|
||||
### Transactions
|
||||
|
||||
- To run a transaction on the default connection use the `mgm.Transaction()` function, e.g:
|
||||
```go
|
||||
d := &Doc{Name: "Mehran", Age: 10}
|
||||
|
||||
err := mgm.Transaction(func(session mongo.Session, sc mongo.SessionContext) error {
|
||||
|
||||
// do not forget to pass the session's context to the collection methods.
|
||||
err := mgm.Coll(d).CreateWithCtx(sc, d)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return session.CommitTransaction(sc)
|
||||
})
|
||||
```
|
||||
|
||||
- To run a transaction with your own context, use the `mgm.TransactionWithCtx()` method.
|
||||
- To run a transaction on another connection, use the `mgm.TransactionWithClient()` method.
|
||||
|
||||
-----------------
|
||||
## Other Mongo Go Models Packages
|
||||
|
||||
**We implemented these packages to simplify queries and aggregations in mongo**
|
||||
|
||||
`builder`: simplify mongo queries and aggregations.
|
||||
|
||||
`operator` : contains mongo operators as predefined variables.
|
||||
(e.g `Eq = "$eq"` , `Gt = "$gt"`)
|
||||
|
||||
`field` : contains mongo fields used in aggregations and ... as predefined variable.
|
||||
(e.g `LocalField = "localField"`, `ForeignField = "foreignField"`)
|
||||
|
||||
Example:
|
||||
```go
|
||||
import (
|
||||
"github.com/kamva/mgm/v3"
|
||||
f "github.com/kamva/mgm/v3/field"
|
||||
o "github.com/kamva/mgm/v3/operator"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
// Instead of hard-coding mongo operators and fields
|
||||
_, _ = mgm.Coll(&Book{}).Aggregate(mgm.Ctx(), bson.A{
|
||||
bson.M{"$count": ""},
|
||||
bson.M{"$project": bson.M{"_id": 0}},
|
||||
})
|
||||
|
||||
// Use the predefined operators and pipeline fields.
|
||||
_, _ = mgm.Coll(&Book{}).Aggregate(mgm.Ctx(), bson.A{
|
||||
bson.M{o.Count: ""},
|
||||
bson.M{o.Project: bson.M{f.Id: 0}},
|
||||
})
|
||||
```
|
||||
|
||||
## Bugs / Feature request
|
||||
New features can be requested and bugs can be reported on [Github issue tracker](https://github.com/Kamva/mgm/issues).
|
||||
|
||||
## Communicate With Us
|
||||
|
||||
* Create new topic at [mongo-go-models Google Group](https://groups.google.com/forum/#!forum/mongo-go-models)
|
||||
* Ask your question or request new feature by creating an issue at [Github issue tracker](https://github.com/Kamva/mgm/issues)
|
||||
|
||||
## Contributing
|
||||
|
||||
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/kamva/mgm)
|
||||
|
||||
1. Fork the repository
|
||||
1. Clone your fork (`git clone https://github.com/<your_username>/mgm && cd mgm`)
|
||||
1. Create your feature branch (`git checkout -b my-new-feature`)
|
||||
1. Make changes and add them (`git add .`)
|
||||
1. Commit your changes (`git commit -m 'Add some feature'`)
|
||||
1. Push to the branch (`git push origin my-new-feature`)
|
||||
1. Create new pull request
|
||||
|
||||
## License
|
||||
|
||||
Mongo Go Models is released under the [Apache License](https://github.com/Kamva/mgm/blob/master/LICENSE)
|
@ -0,0 +1,21 @@
|
||||
#### Upgrade Instructions
|
||||
|
||||
##### Upgrade from 2.x to 3.x
|
||||
* Change your package import paths from `github.com/Kamva/mgm/v2`
|
||||
to `github.com/Kamva/v3`.
|
||||
|
||||
- built-in `ID` field changed json tag value from `_id` to `id` [f665e15](https://github.com/Kamva/mgm/commit/f665e1592cdac43fb7fd00b1427a91590f14a9ff)
|
||||
|
||||
##### Upgrade from 1.x to 2.x
|
||||
* Change your package import paths from `github.com/Kamva/mgm`
|
||||
to `github.com/Kamva/v3`.
|
||||
|
||||
* methods `Save` and `SaveWithContext` was removed from collections.
|
||||
* To Create an entity use the `Create` or `CreateWithCtx` methods.
|
||||
* To update an entity use the `Update` or `UpdateWithCtx` methods.
|
||||
|
||||
* method `IsNew` method was removed from the `model` interface,
|
||||
don't use it.
|
||||
|
||||
|
||||
|
@ -0,0 +1,137 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
f "github.com/kamva/mgm/v3/field"
|
||||
o "github.com/kamva/mgm/v3/operator"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
// Bucket function returns a mongo $bucket operator used in aggregations.
|
||||
func Bucket(groupBy, boundaries, def, output interface{}) Operator {
|
||||
m := bson.M{}
|
||||
|
||||
appendIfHasVal(m, f.GroupBy, groupBy)
|
||||
appendIfHasVal(m, f.Boundaries, boundaries)
|
||||
appendIfHasVal(m, f.Default, def)
|
||||
appendIfHasVal(m, f.Output, output)
|
||||
|
||||
return New(o.Bucket, m)
|
||||
}
|
||||
|
||||
// BucketAuto function returns a mongo $bucketAuto operator used in aggregations.
|
||||
func BucketAuto(groupBy, buckets, output, granularity interface{}) Operator {
|
||||
m := bson.M{}
|
||||
|
||||
appendIfHasVal(m, f.GroupBy, groupBy)
|
||||
appendIfHasVal(m, f.Buckets, buckets)
|
||||
appendIfHasVal(m, f.Output, output)
|
||||
appendIfHasVal(m, f.Granularity, granularity)
|
||||
|
||||
return New(o.BucketAuto, m)
|
||||
}
|
||||
|
||||
// CollStats function returns a mongo $collStats operator used in aggregations.
|
||||
func CollStats(latencyStats, storageStats, count interface{}) Operator {
|
||||
m := bson.M{}
|
||||
|
||||
appendIfHasVal(m, f.LatencyStats, latencyStats)
|
||||
appendIfHasVal(m, f.StorageStats, storageStats)
|
||||
appendIfHasVal(m, f.Count, count)
|
||||
|
||||
return New(o.CollStats, m)
|
||||
}
|
||||
|
||||
// CurrentOp function returns a mongo $currentOp operator used in aggregations.
|
||||
func CurrentOp(allUsers, idleConnections, idleCursors, idleSessions, localOps interface{}) Operator {
|
||||
m := bson.M{}
|
||||
|
||||
appendIfHasVal(m, f.AllUsers, allUsers)
|
||||
appendIfHasVal(m, f.IdleConnections, idleConnections)
|
||||
appendIfHasVal(m, f.IdleCursors, idleCursors)
|
||||
appendIfHasVal(m, f.IdleSessions, idleSessions)
|
||||
appendIfHasVal(m, f.LocalOps, localOps)
|
||||
|
||||
return New(o.CurrentOp, m)
|
||||
}
|
||||
|
||||
// $geoNear,$graphLookup has many params, those functions
|
||||
// will have too many params and do not make readable code.
|
||||
|
||||
// Group function returns a mongo $group operator used in aggregations.
|
||||
func Group(ID interface{}, params bson.M) Operator {
|
||||
m := bson.M{}
|
||||
|
||||
appendIfHasVal(m, f.ID, ID)
|
||||
|
||||
for key, val := range params {
|
||||
appendIfHasVal(m, key, val)
|
||||
}
|
||||
|
||||
return New(o.Group, m)
|
||||
}
|
||||
|
||||
// Lookup function returns a mongo $lookup operator used in aggregations.
|
||||
func Lookup(from, localField, foreignField, as interface{}) Operator {
|
||||
m := bson.M{}
|
||||
|
||||
appendIfHasVal(m, f.From, from)
|
||||
appendIfHasVal(m, f.LocalField, localField)
|
||||
appendIfHasVal(m, f.ForeignField, foreignField)
|
||||
appendIfHasVal(m, f.As, as)
|
||||
|
||||
return New(o.Lookup, m)
|
||||
}
|
||||
|
||||
// UncorrelatedLookup function returns a mongo $lookup operator used in aggregations.
|
||||
func UncorrelatedLookup(from, let, pipeline, as interface{}) Operator {
|
||||
m := bson.M{}
|
||||
|
||||
appendIfHasVal(m, f.From, from)
|
||||
appendIfHasVal(m, f.Let, let)
|
||||
appendIfHasVal(m, f.Pipeline, pipeline)
|
||||
appendIfHasVal(m, f.As, as)
|
||||
|
||||
return New(o.Lookup, m)
|
||||
}
|
||||
|
||||
// Merge function returns a mongo $merge operator used in aggregations.
|
||||
func Merge(into, on, let, whenMatched, whenNotMatched interface{}) Operator {
|
||||
m := bson.M{}
|
||||
|
||||
appendIfHasVal(m, f.Into, into)
|
||||
appendIfHasVal(m, f.On, on)
|
||||
appendIfHasVal(m, f.Let, let)
|
||||
appendIfHasVal(m, f.WhenMatched, whenMatched)
|
||||
appendIfHasVal(m, f.WhenNotMatched, whenNotMatched)
|
||||
|
||||
return New(o.Merge, m)
|
||||
}
|
||||
|
||||
// ReplaceRoot function returns a mongo $replaceRoot operator used in aggregations.
|
||||
func ReplaceRoot(newRoot interface{}) Operator {
|
||||
m := bson.M{}
|
||||
|
||||
appendIfHasVal(m, f.NewRoot, newRoot)
|
||||
|
||||
return New(o.ReplaceRoot, m)
|
||||
}
|
||||
|
||||
// Sample function returns a mongo sample operator used in aggregations.
|
||||
func Sample(size interface{}) Operator {
|
||||
m := bson.M{}
|
||||
|
||||
appendIfHasVal(m, f.Size, size)
|
||||
|
||||
return New(o.Sample, m)
|
||||
}
|
||||
|
||||
// Unwind function returns a mongo $unwind operator used in aggregations.
|
||||
func Unwind(path, includeArrayIndex, preserveNullAndEmptyArrays interface{}) Operator {
|
||||
m := bson.M{}
|
||||
|
||||
appendIfHasVal(m, f.Path, path)
|
||||
appendIfHasVal(m, f.IncludeArrayIndex, includeArrayIndex)
|
||||
appendIfHasVal(m, f.PreserveNullAndEmptyArrays, preserveNullAndEmptyArrays)
|
||||
|
||||
return New(o.Unwind, m)
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package builder
|
||||
|
||||
// Operator is an interface that should be implemented by structs used as operators.
|
||||
type Operator interface {
|
||||
GetKey() string
|
||||
GetVal() interface{}
|
||||
}
|
||||
|
||||
// BaseOperator is a simple operator struct that implementes the Operator interface.
|
||||
type BaseOperator struct {
|
||||
key string
|
||||
val interface{}
|
||||
}
|
||||
|
||||
// GetKey function returns the operator's key.
|
||||
func (operator *BaseOperator) GetKey() string {
|
||||
return operator.key
|
||||
}
|
||||
|
||||
// GetVal function returns the operator's value.
|
||||
func (operator *BaseOperator) GetVal() interface{} {
|
||||
return operator.val
|
||||
}
|
||||
|
||||
// New function creates a new base operator with the specified key and value
|
||||
func New(key string, val interface{}) Operator {
|
||||
return &BaseOperator{
|
||||
key: key,
|
||||
val: val,
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the BaseOperator implements the Operator interace
|
||||
var _ Operator = &BaseOperator{}
|
@ -0,0 +1,28 @@
|
||||
// Package builder help us to write aggregates, filters, update maps simpler.
|
||||
package builder
|
||||
|
||||
import "go.mongodb.org/mongo-driver/bson"
|
||||
|
||||
// SMap is simple map that can be substitute of `bson.M` to
|
||||
// have a simpler map structure for queries, aggregations, etc.
|
||||
type SMap struct {
|
||||
Operators []Operator
|
||||
}
|
||||
|
||||
// ToMap function converts our SMap to bson.M for use in filters, stages, etc.
|
||||
func (s *SMap) ToMap() bson.M {
|
||||
m := bson.M{}
|
||||
|
||||
for _, o := range s.Operators {
|
||||
m[o.GetKey()] = o.GetVal()
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// S receives operators as parameters and returns a bson.M that can be used in filters, stages, etc.
|
||||
func S(operators ...Operator) bson.M {
|
||||
s := &SMap{Operators: operators}
|
||||
|
||||
return s.ToMap()
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"github.com/kamva/mgm/v3/internal/util"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
// appendIfHasVal appends the provided key and value to the map if the value is not nil.
|
||||
func appendIfHasVal(m bson.M, key string, val interface{}) {
|
||||
if !util.IsNil(val) {
|
||||
m[key] = val
|
||||
}
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
package mgm
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/kamva/mgm/v3/builder"
|
||||
"github.com/kamva/mgm/v3/field"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
// Collection performs operations on models and the given Mongodb collection
|
||||
type Collection struct {
|
||||
*mongo.Collection
|
||||
}
|
||||
|
||||
// FindByID method finds a doc and decodes it to a model, otherwise returns an error.
|
||||
// The id field can be any value that if passed to the `PrepareID` method, it returns
|
||||
// a valid ID (e.g string, bson.ObjectId).
|
||||
func (coll *Collection) FindByID(id interface{}, model Model) error {
|
||||
return coll.FindByIDWithCtx(ctx(), id, model)
|
||||
}
|
||||
|
||||
// FindByIDWithCtx method finds a doc and decodes it to a model, otherwise returns an error.
|
||||
// The id field can be any value that if passed to the `PrepareID` method, it returns
|
||||
// a valid ID (e.g string, bson.ObjectId).
|
||||
func (coll *Collection) FindByIDWithCtx(ctx context.Context, id interface{}, model Model) error {
|
||||
id, err := model.PrepareID(id)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return first(ctx, coll, bson.M{field.ID: id}, model)
|
||||
}
|
||||
|
||||
// First method searches and returns the first document in the search results.
|
||||
func (coll *Collection) First(filter interface{}, model Model, opts ...*options.FindOneOptions) error {
|
||||
return coll.FirstWithCtx(ctx(), filter, model, opts...)
|
||||
}
|
||||
|
||||
// FirstWithCtx method searches and returns the first document in the search results.
|
||||
func (coll *Collection) FirstWithCtx(ctx context.Context, filter interface{}, model Model, opts ...*options.FindOneOptions) error {
|
||||
return first(ctx, coll, filter, model, opts...)
|
||||
}
|
||||
|
||||
// Create method inserts a new model into the database.
|
||||
func (coll *Collection) Create(model Model, opts ...*options.InsertOneOptions) error {
|
||||
return coll.CreateWithCtx(ctx(), model, opts...)
|
||||
}
|
||||
|
||||
// CreateWithCtx method inserts a new model into the database.
|
||||
func (coll *Collection) CreateWithCtx(ctx context.Context, model Model, opts ...*options.InsertOneOptions) error {
|
||||
return create(ctx, coll, model, opts...)
|
||||
}
|
||||
|
||||
// Update function persists the changes made to a model to the database.
|
||||
// Calling this method also invokes the model's mgm updating, updated,
|
||||
// saving, and saved hooks.
|
||||
func (coll *Collection) Update(model Model, opts ...*options.UpdateOptions) error {
|
||||
return coll.UpdateWithCtx(ctx(), model, opts...)
|
||||
}
|
||||
|
||||
// UpdateWithCtx function persists the changes made to a model to the database using the specified context.
|
||||
// Calling this method also invokes the model's mgm updating, updated,
|
||||
// saving, and saved hooks.
|
||||
func (coll *Collection) UpdateWithCtx(ctx context.Context, model Model, opts ...*options.UpdateOptions) error {
|
||||
return update(ctx, coll, model, opts...)
|
||||
}
|
||||
|
||||
// Delete method deletes a model (doc) from a collection.
|
||||
// To perform additional operations when deleting a model
|
||||
// you should use hooks rather than overriding this method.
|
||||
func (coll *Collection) Delete(model Model) error {
|
||||
return del(ctx(), coll, model)
|
||||
}
|
||||
|
||||
// DeleteWithCtx method deletes a model (doc) from a collection using the specified context.
|
||||
// To perform additional operations when deleting a model
|
||||
// you should use hooks rather than overriding this method.
|
||||
func (coll *Collection) DeleteWithCtx(ctx context.Context, model Model) error {
|
||||
return del(ctx, coll, model)
|
||||
}
|
||||
|
||||
// SimpleFind finds, decodes and returns the results.
|
||||
func (coll *Collection) SimpleFind(results interface{}, filter interface{}, opts ...*options.FindOptions) error {
|
||||
return coll.SimpleFindWithCtx(ctx(), results, filter, opts...)
|
||||
}
|
||||
|
||||
// SimpleFindWithCtx finds, decodes and returns the results using the specified context.
|
||||
func (coll *Collection) SimpleFindWithCtx(ctx context.Context, results interface{}, filter interface{}, opts ...*options.FindOptions) error {
|
||||
cur, err := coll.Find(ctx, filter, opts...)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cur.All(ctx, results)
|
||||
}
|
||||
|
||||
//--------------------------------
|
||||
// Aggregation methods
|
||||
//--------------------------------
|
||||
|
||||
// SimpleAggregateFirst is just same as SimpleAggregateFirstWithCtx, but doesnt' get context param.
|
||||
func (coll *Collection) SimpleAggregateFirst(result interface{}, stages ...interface{}) (bool, error) {
|
||||
return coll.SimpleAggregateFirstWithCtx(ctx(), result, stages...)
|
||||
}
|
||||
|
||||
// SimpleAggregateFirstWithCtx performs a simple aggregation, decodes the first aggregate result and returns it using the provided result parameter.
|
||||
// The value of `stages` can be Operator|bson.M
|
||||
// Note: you can not use this method in a transaction because it does not accept a context.
|
||||
// To participate in transactions, please use the regular aggregation method.
|
||||
func (coll *Collection) SimpleAggregateFirstWithCtx(ctx context.Context, result interface{}, stages ...interface{}) (bool, error) {
|
||||
cur, err := coll.SimpleAggregateCursorWithCtx(ctx, stages...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if cur.Next(ctx) {
|
||||
return true, cur.Decode(result)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
|
||||
// SimpleAggregate is just same as SimpleAggregateWithCtx, but doesn't get context param.
|
||||
func (coll *Collection) SimpleAggregate(results interface{}, stages ...interface{}) error {
|
||||
return coll.SimpleAggregateWithCtx(ctx(), results, stages...)
|
||||
}
|
||||
|
||||
// SimpleAggregateWithCtx performs a simple aggregation, decodes the aggregate result and returns the list using the provided result parameter.
|
||||
// The value of `stages` can be Operator|bson.M
|
||||
// Note: you can not use this method in a transaction because it does not accept a context.
|
||||
// To participate in transactions, please use the regular aggergation method.
|
||||
func (coll *Collection) SimpleAggregateWithCtx(ctx context.Context, results interface{}, stages ...interface{}) error {
|
||||
cur, err := coll.SimpleAggregateCursorWithCtx(ctx, stages...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return cur.All(ctx, results)
|
||||
}
|
||||
|
||||
// SimpleAggregateCursor is just same as SimpleAggregateCursorWithCtx, but
|
||||
// doesn't get context.
|
||||
func (coll *Collection) SimpleAggregateCursor(stages ...interface{}) (*mongo.Cursor, error) {
|
||||
return coll.SimpleAggregateCursorWithCtx(ctx(), stages...)
|
||||
}
|
||||
|
||||
// SimpleAggregateCursorWithCtx performs a simple aggregation and returns a cursor over the resulting documents.
|
||||
// Note: you can not use this method in a transaction because it does not accept a context.
|
||||
// To participate in transactions, please use the regular aggergation method.
|
||||
func (coll *Collection) SimpleAggregateCursorWithCtx(ctx context.Context, stages ...interface{}) (*mongo.Cursor, error) {
|
||||
pipeline := bson.A{}
|
||||
|
||||
for _, stage := range stages {
|
||||
if operator, ok := stage.(builder.Operator); ok {
|
||||
pipeline = append(pipeline, builder.S(operator))
|
||||
} else {
|
||||
pipeline = append(pipeline, stage)
|
||||
}
|
||||
}
|
||||
|
||||
return coll.Aggregate(ctx, pipeline, nil)
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package mgm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/kamva/mgm/v3/internal/util"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"time"
|
||||
)
|
||||
|
||||
var config *Config
|
||||
var client *mongo.Client
|
||||
var db *mongo.Database
|
||||
|
||||
// Config struct contains extra configuration properties for the mgm package.
|
||||
type Config struct {
|
||||
// Set to 10 second (10*time.Second) for example.
|
||||
CtxTimeout time.Duration
|
||||
}
|
||||
|
||||
// NewCtx function creates and returns a new context with the specified timeout.
|
||||
func NewCtx(timeout time.Duration) context.Context {
|
||||
ctx, _ := context.WithTimeout(context.Background(), timeout)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
// Ctx function creates and returns a new context with a default timeout value.
|
||||
func Ctx() context.Context {
|
||||
return ctx()
|
||||
}
|
||||
|
||||
func ctx() context.Context {
|
||||
return NewCtx(config.CtxTimeout)
|
||||
}
|
||||
|
||||
// NewClient returns a new mongodb client.
|
||||
func NewClient(opts ...*options.ClientOptions) (*mongo.Client, error) {
|
||||
client, err := mongo.NewClient(opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = client.Connect(Ctx()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// NewCollection returns a new collection with the supplied database.
|
||||
func NewCollection(db *mongo.Database, name string, opts ...*options.CollectionOptions) *Collection {
|
||||
coll := db.Collection(name, opts...)
|
||||
|
||||
return &Collection{Collection: coll}
|
||||
}
|
||||
|
||||
// ResetDefaultConfig resets the configuration values, client and database.
|
||||
func ResetDefaultConfig() {
|
||||
config = nil
|
||||
client = nil
|
||||
db = nil
|
||||
}
|
||||
|
||||
// SetDefaultConfig initializes the client and database using the specified configuration values, or default.
|
||||
func SetDefaultConfig(conf *Config, dbName string, opts ...*options.ClientOptions) (err error) {
|
||||
|
||||
// Use the predefined configuration values as default if the user
|
||||
// does not provide any.
|
||||
if conf == nil {
|
||||
conf = defaultConf()
|
||||
}
|
||||
|
||||
config = conf
|
||||
|
||||
if client, err = NewClient(opts...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db = client.Database(dbName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CollectionByName returns a new collection using the current configuration values.
|
||||
func CollectionByName(name string, opts ...*options.CollectionOptions) *Collection {
|
||||
return NewCollection(db, name, opts...)
|
||||
}
|
||||
|
||||
// DefaultConfigs returns the current configuration values, client and database.
|
||||
func DefaultConfigs() (*Config, *mongo.Client, *mongo.Database, error) {
|
||||
if util.AnyNil(config, client, db) {
|
||||
return nil, nil, nil, errors.New("please setup default config before acquiring it")
|
||||
}
|
||||
|
||||
return config, client, db, nil
|
||||
}
|
||||
|
||||
// defaultConf are the default configuration values when none are provided
|
||||
// to the `SetDefaultConfig` method.
|
||||
func defaultConf() *Config {
|
||||
return &Config{CtxTimeout: 10 * time.Second}
|
||||
}
|
@ -0,0 +1,167 @@
|
||||
mode: set
|
||||
github.com/Kamva/mgm/builder/types.go:13.31,16.32 2 1
|
||||
github.com/Kamva/mgm/builder/types.go:20.2,20.10 1 1
|
||||
github.com/Kamva/mgm/builder/types.go:16.32,18.3 1 1
|
||||
github.com/Kamva/mgm/builder/types.go:24.38,28.2 2 1
|
||||
github.com/Kamva/mgm/builder/util.go:9.60,10.22 1 1
|
||||
github.com/Kamva/mgm/builder/util.go:10.22,12.3 1 1
|
||||
github.com/Kamva/mgm/builder/aggregate_stages.go:10.68,19.2 6 1
|
||||
github.com/Kamva/mgm/builder/aggregate_stages.go:22.77,31.2 6 1
|
||||
github.com/Kamva/mgm/builder/aggregate_stages.go:34.72,42.2 5 1
|
||||
github.com/Kamva/mgm/builder/aggregate_stages.go:45.101,55.2 7 1
|
||||
github.com/Kamva/mgm/builder/aggregate_stages.go:61.52,66.31 3 1
|
||||
github.com/Kamva/mgm/builder/aggregate_stages.go:70.2,70.24 1 1
|
||||
github.com/Kamva/mgm/builder/aggregate_stages.go:66.31,68.3 1 1
|
||||
github.com/Kamva/mgm/builder/aggregate_stages.go:74.70,83.2 6 1
|
||||
github.com/Kamva/mgm/builder/aggregate_stages.go:86.71,95.2 6 1
|
||||
github.com/Kamva/mgm/builder/aggregate_stages.go:98.77,108.2 7 1
|
||||
github.com/Kamva/mgm/builder/aggregate_stages.go:111.48,117.2 3 1
|
||||
github.com/Kamva/mgm/builder/aggregate_stages.go:120.40,126.2 3 1
|
||||
github.com/Kamva/mgm/builder/aggregate_stages.go:129.87,137.2 5 1
|
||||
github.com/Kamva/mgm/builder/operator.go:17.47,19.2 1 1
|
||||
github.com/Kamva/mgm/builder/operator.go:22.52,24.2 1 1
|
||||
github.com/Kamva/mgm/builder/operator.go:27.48,32.2 1 1
|
||||
github.com/Kamva/mgm/examples/aggregate/aggregate.go:10.13,17.2 4 1
|
||||
github.com/Kamva/mgm/examples/aggregate/aggregate.go:19.22,22.2 2 1
|
||||
github.com/Kamva/mgm/examples/aggregate/aggregate.go:24.21,38.16 6 1
|
||||
github.com/Kamva/mgm/examples/aggregate/aggregate.go:42.2,44.20 2 1
|
||||
github.com/Kamva/mgm/examples/aggregate/aggregate.go:55.2,55.12 1 1
|
||||
github.com/Kamva/mgm/examples/aggregate/aggregate.go:38.16,40.3 1 0
|
||||
github.com/Kamva/mgm/examples/aggregate/aggregate.go:44.20,47.17 3 1
|
||||
github.com/Kamva/mgm/examples/aggregate/aggregate.go:47.17,49.4 1 0
|
||||
github.com/Kamva/mgm/examples/aggregate/connection.go:8.13,10.2 1 1
|
||||
github.com/Kamva/mgm/examples/aggregate/model.go:15.71,21.2 1 1
|
||||
github.com/Kamva/mgm/examples/aggregate/model.go:28.37,32.2 1 1
|
||||
github.com/Kamva/mgm/examples/crud/crud.go:5.19,10.47 3 1
|
||||
github.com/Kamva/mgm/examples/crud/crud.go:14.2,15.45 2 1
|
||||
github.com/Kamva/mgm/examples/crud/crud.go:19.2,19.31 1 1
|
||||
github.com/Kamva/mgm/examples/crud/crud.go:10.47,12.3 1 0
|
||||
github.com/Kamva/mgm/examples/crud/crud.go:15.45,17.3 1 0
|
||||
github.com/Kamva/mgm/examples/crud/crud.go:22.13,25.2 1 0
|
||||
github.com/Kamva/mgm/examples/crud/model.go:13.44,18.2 1 1
|
||||
github.com/Kamva/mgm/examples/crud/connection.go:8.13,10.2 1 1
|
||||
github.com/Kamva/mgm/model.go:37.45,39.2 1 1
|
||||
github.com/Kamva/mgm/model.go:42.43,44.2 1 1
|
||||
github.com/Kamva/mgm/model_field.go:22.66,23.34 1 1
|
||||
github.com/Kamva/mgm/model_field.go:28.2,28.16 1 1
|
||||
github.com/Kamva/mgm/model_field.go:23.34,25.3 1 1
|
||||
github.com/Kamva/mgm/model_field.go:32.32,34.2 1 1
|
||||
github.com/Kamva/mgm/model_field.go:37.39,39.2 1 1
|
||||
github.com/Kamva/mgm/model_field.go:42.41,44.2 1 1
|
||||
github.com/Kamva/mgm/model_field.go:52.39,55.2 2 1
|
||||
github.com/Kamva/mgm/model_field.go:59.37,62.2 2 1
|
||||
github.com/Kamva/mgm/operation.go:10.68,12.55 1 1
|
||||
github.com/Kamva/mgm/operation.go:16.2,18.16 2 1
|
||||
github.com/Kamva/mgm/operation.go:23.2,25.38 2 1
|
||||
github.com/Kamva/mgm/operation.go:12.55,14.3 1 1
|
||||
github.com/Kamva/mgm/operation.go:18.16,20.3 1 0
|
||||
github.com/Kamva/mgm/operation.go:28.120,30.2 1 1
|
||||
github.com/Kamva/mgm/operation.go:32.68,34.55 1 1
|
||||
github.com/Kamva/mgm/operation.go:38.2,40.16 2 1
|
||||
github.com/Kamva/mgm/operation.go:44.2,44.43 1 1
|
||||
github.com/Kamva/mgm/operation.go:34.55,36.3 1 1
|
||||
github.com/Kamva/mgm/operation.go:40.16,42.3 1 0
|
||||
github.com/Kamva/mgm/operation.go:47.65,48.55 1 1
|
||||
github.com/Kamva/mgm/operation.go:51.2,52.16 2 1
|
||||
github.com/Kamva/mgm/operation.go:56.2,56.43 1 1
|
||||
github.com/Kamva/mgm/operation.go:48.55,50.3 1 1
|
||||
github.com/Kamva/mgm/operation.go:52.16,54.3 1 0
|
||||
github.com/Kamva/mgm/collection.go:20.69,22.2 1 1
|
||||
github.com/Kamva/mgm/collection.go:27.97,30.16 2 1
|
||||
github.com/Kamva/mgm/collection.go:34.2,34.54 1 1
|
||||
github.com/Kamva/mgm/collection.go:30.16,32.3 1 1
|
||||
github.com/Kamva/mgm/collection.go:38.103,40.2 1 1
|
||||
github.com/Kamva/mgm/collection.go:43.131,45.2 1 1
|
||||
github.com/Kamva/mgm/collection.go:48.51,50.2 1 1
|
||||
github.com/Kamva/mgm/collection.go:53.79,55.2 1 1
|
||||
github.com/Kamva/mgm/collection.go:60.51,62.2 1 1
|
||||
github.com/Kamva/mgm/collection.go:67.79,69.2 1 1
|
||||
github.com/Kamva/mgm/collection.go:72.49,74.2 1 1
|
||||
github.com/Kamva/mgm/collection.go:77.77,78.19 1 1
|
||||
github.com/Kamva/mgm/collection.go:82.2,82.33 1 1
|
||||
github.com/Kamva/mgm/collection.go:78.19,80.3 1 1
|
||||
github.com/Kamva/mgm/collection.go:88.51,90.2 1 1
|
||||
github.com/Kamva/mgm/collection.go:95.79,97.2 1 0
|
||||
github.com/Kamva/mgm/collection.go:100.113,102.2 1 1
|
||||
github.com/Kamva/mgm/collection.go:105.141,108.16 2 1
|
||||
github.com/Kamva/mgm/collection.go:112.2,112.30 1 1
|
||||
github.com/Kamva/mgm/collection.go:108.16,110.3 1 0
|
||||
github.com/Kamva/mgm/collection.go:123.91,125.16 2 1
|
||||
github.com/Kamva/mgm/collection.go:129.2,129.32 1 1
|
||||
github.com/Kamva/mgm/collection.go:125.16,127.3 1 0
|
||||
github.com/Kamva/mgm/collection.go:135.93,138.31 2 1
|
||||
github.com/Kamva/mgm/collection.go:146.2,146.45 1 1
|
||||
github.com/Kamva/mgm/collection.go:138.31,139.51 1 1
|
||||
github.com/Kamva/mgm/collection.go:139.51,141.4 1 1
|
||||
github.com/Kamva/mgm/collection.go:141.9,143.4 1 1
|
||||
github.com/Kamva/mgm/hooks.go:46.49,47.42 1 1
|
||||
github.com/Kamva/mgm/hooks.go:53.2,53.40 1 1
|
||||
github.com/Kamva/mgm/hooks.go:59.2,59.12 1 1
|
||||
github.com/Kamva/mgm/hooks.go:47.42,48.41 1 1
|
||||
github.com/Kamva/mgm/hooks.go:48.41,50.4 1 1
|
||||
github.com/Kamva/mgm/hooks.go:53.40,54.39 1 1
|
||||
github.com/Kamva/mgm/hooks.go:54.39,56.4 1 1
|
||||
github.com/Kamva/mgm/hooks.go:62.49,63.42 1 1
|
||||
github.com/Kamva/mgm/hooks.go:69.2,69.40 1 1
|
||||
github.com/Kamva/mgm/hooks.go:75.2,75.12 1 1
|
||||
github.com/Kamva/mgm/hooks.go:63.42,64.41 1 1
|
||||
github.com/Kamva/mgm/hooks.go:64.41,66.4 1 1
|
||||
github.com/Kamva/mgm/hooks.go:69.40,70.39 1 1
|
||||
github.com/Kamva/mgm/hooks.go:70.39,72.4 1 0
|
||||
github.com/Kamva/mgm/hooks.go:78.48,79.41 1 1
|
||||
github.com/Kamva/mgm/hooks.go:85.2,85.39 1 1
|
||||
github.com/Kamva/mgm/hooks.go:91.2,91.12 1 1
|
||||
github.com/Kamva/mgm/hooks.go:79.41,80.40 1 1
|
||||
github.com/Kamva/mgm/hooks.go:80.40,82.4 1 0
|
||||
github.com/Kamva/mgm/hooks.go:85.39,86.38 1 1
|
||||
github.com/Kamva/mgm/hooks.go:86.38,88.4 1 0
|
||||
github.com/Kamva/mgm/hooks.go:94.82,95.41 1 1
|
||||
github.com/Kamva/mgm/hooks.go:101.2,101.39 1 1
|
||||
github.com/Kamva/mgm/hooks.go:107.2,107.12 1 1
|
||||
github.com/Kamva/mgm/hooks.go:95.41,96.52 1 1
|
||||
github.com/Kamva/mgm/hooks.go:96.52,98.4 1 0
|
||||
github.com/Kamva/mgm/hooks.go:101.39,102.38 1 1
|
||||
github.com/Kamva/mgm/hooks.go:102.38,104.4 1 0
|
||||
github.com/Kamva/mgm/hooks.go:110.49,111.42 1 1
|
||||
github.com/Kamva/mgm/hooks.go:117.2,117.12 1 1
|
||||
github.com/Kamva/mgm/hooks.go:111.42,112.41 1 1
|
||||
github.com/Kamva/mgm/hooks.go:112.41,114.4 1 1
|
||||
github.com/Kamva/mgm/hooks.go:120.82,121.41 1 1
|
||||
github.com/Kamva/mgm/hooks.go:127.2,127.12 1 1
|
||||
github.com/Kamva/mgm/hooks.go:121.41,122.52 1 1
|
||||
github.com/Kamva/mgm/hooks.go:122.52,124.4 1 0
|
||||
github.com/Kamva/mgm/util.go:10.32,12.48 1 1
|
||||
github.com/Kamva/mgm/util.go:16.2,16.38 1 1
|
||||
github.com/Kamva/mgm/util.go:12.48,14.3 1 0
|
||||
github.com/Kamva/mgm/util.go:22.31,24.56 1 1
|
||||
github.com/Kamva/mgm/util.go:28.2,30.50 2 1
|
||||
github.com/Kamva/mgm/util.go:24.56,26.3 1 1
|
||||
github.com/Kamva/mgm/connection.go:23.52,27.2 2 1
|
||||
github.com/Kamva/mgm/connection.go:31.28,33.2 1 1
|
||||
github.com/Kamva/mgm/connection.go:35.28,37.2 1 1
|
||||
github.com/Kamva/mgm/connection.go:40.71,42.16 2 1
|
||||
github.com/Kamva/mgm/connection.go:46.2,46.45 1 1
|
||||
github.com/Kamva/mgm/connection.go:50.2,50.20 1 1
|
||||
github.com/Kamva/mgm/connection.go:42.16,44.3 1 0
|
||||
github.com/Kamva/mgm/connection.go:46.45,48.3 1 0
|
||||
github.com/Kamva/mgm/connection.go:54.101,58.2 2 1
|
||||
github.com/Kamva/mgm/connection.go:61.27,65.2 3 1
|
||||
github.com/Kamva/mgm/connection.go:68.96,72.17 1 1
|
||||
github.com/Kamva/mgm/connection.go:76.2,78.50 2 1
|
||||
github.com/Kamva/mgm/connection.go:82.2,84.12 2 1
|
||||
github.com/Kamva/mgm/connection.go:72.17,74.3 1 1
|
||||
github.com/Kamva/mgm/connection.go:78.50,80.3 1 0
|
||||
github.com/Kamva/mgm/connection.go:88.84,90.2 1 1
|
||||
github.com/Kamva/mgm/connection.go:93.72,94.37 1 1
|
||||
github.com/Kamva/mgm/connection.go:98.2,98.32 1 1
|
||||
github.com/Kamva/mgm/connection.go:94.37,96.3 1 1
|
||||
github.com/Kamva/mgm/connection.go:103.28,105.2 1 1
|
||||
github.com/Kamva/mgm/transaction.go:11.43,13.2 1 1
|
||||
github.com/Kamva/mgm/transaction.go:16.71,18.2 1 1
|
||||
github.com/Kamva/mgm/transaction.go:21.96,23.16 2 1
|
||||
github.com/Kamva/mgm/transaction.go:27.2,29.50 2 1
|
||||
github.com/Kamva/mgm/transaction.go:33.2,33.51 1 1
|
||||
github.com/Kamva/mgm/transaction.go:37.2,37.51 1 1
|
||||
github.com/Kamva/mgm/transaction.go:23.16,25.3 1 0
|
||||
github.com/Kamva/mgm/transaction.go:29.50,31.3 1 0
|
||||
github.com/Kamva/mgm/transaction.go:33.51,35.3 1 1
|
@ -0,0 +1,60 @@
|
||||
package mgm
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
// IDField struct contains a model's ID field.
|
||||
type IDField struct {
|
||||
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
|
||||
}
|
||||
|
||||
// DateFields struct contains the `created_at` and `updated_at`
|
||||
// fields that autofill when inserting or updating a model.
|
||||
type DateFields struct {
|
||||
CreatedAt time.Time `json:"created_at" bson:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
|
||||
}
|
||||
|
||||
// PrepareID method prepares the ID value to be used for filtering
|
||||
// e.g convert hex-string ID value to bson.ObjectId
|
||||
func (f *IDField) PrepareID(id interface{}) (interface{}, error) {
|
||||
if idStr, ok := id.(string); ok {
|
||||
return primitive.ObjectIDFromHex(idStr)
|
||||
}
|
||||
|
||||
// Otherwise id must be ObjectId
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// GetID method returns a model's ID
|
||||
func (f *IDField) GetID() interface{} {
|
||||
return f.ID
|
||||
}
|
||||
|
||||
// SetID sets the value of a model's ID field.
|
||||
func (f *IDField) SetID(id interface{}) {
|
||||
f.ID = id.(primitive.ObjectID)
|
||||
}
|
||||
|
||||
//--------------------------------
|
||||
// DateField methods
|
||||
//--------------------------------
|
||||
|
||||
// Creating hook is used here to set the `created_at` field
|
||||
// value when inserting a new model into the database.
|
||||
// TODO: get context as param the next version(4).
|
||||
func (f *DateFields) Creating() error {
|
||||
f.CreatedAt = time.Now().UTC()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Saving hook is used here to set the `updated_at` field
|
||||
// value when creating or updateing a model.
|
||||
// TODO: get context as param the next version(4).
|
||||
func (f *DateFields) Saving() error {
|
||||
f.UpdatedAt = time.Now().UTC()
|
||||
return nil
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package field
|
||||
|
||||
import "go.mongodb.org/mongo-driver/bson"
|
||||
|
||||
// ID field is constant for referencing the "_id" field name.
|
||||
const ID = "_id"
|
||||
|
||||
// Empty is a predefined empty map.
|
||||
var Empty = bson.M{}
|
||||
|
||||
// TODO: Extract all field names from :
|
||||
// cont.todo: https://docs.mongodb.com/manual/reference/operator/query/
|
||||
// cont.todo: https://docs.mongodb.com/manual/reference/operator/update/
|
||||
// cont.todo: https://docs.mongodb.com/manual/reference/operator/aggregation/
|
||||
// cont.todo: https://docs.mongodb.com/manual/reference/operator/query-modifier/
|
@ -0,0 +1,12 @@
|
||||
package field
|
||||
|
||||
// JeoJSON reference: https://docs.mongodb.com/manual/reference/geojson/
|
||||
const (
|
||||
Point = "Point"
|
||||
LineString = "LineString"
|
||||
Polygon = "Polygon"
|
||||
MultiPoint = "MultiPoint"
|
||||
MultiLineString = "MultiLineString"
|
||||
MultiPolygon = "MultiPolygon"
|
||||
GeometryCollection = "GeometryCollection"
|
||||
)
|
@ -0,0 +1,118 @@
|
||||
package field
|
||||
|
||||
import "go.mongodb.org/mongo-driver/bson"
|
||||
|
||||
// $bucket fields
|
||||
const (
|
||||
GroupBy = "groupBy"
|
||||
Boundaries = "boundaries"
|
||||
Default = "default"
|
||||
Output = "output"
|
||||
)
|
||||
|
||||
// $bucketAuto
|
||||
const (
|
||||
// GroupBy = "groupBy" // Declared
|
||||
Buckets = "buckets"
|
||||
// Output = "output" // Declared
|
||||
Granularity = "granularity"
|
||||
)
|
||||
|
||||
// $collStats
|
||||
const (
|
||||
LatencyStats = "latencyStats"
|
||||
StorageStats = "storageStats"
|
||||
Count = "count"
|
||||
)
|
||||
|
||||
// $currentOp
|
||||
const (
|
||||
AllUsers = "allUsers"
|
||||
IdleConnections = "idleConnections"
|
||||
IdleCursors = "idleCursors"
|
||||
IdleSessions = "idleSessions"
|
||||
LocalOps = "localOps"
|
||||
)
|
||||
|
||||
// $geoNear
|
||||
const (
|
||||
Near = "near"
|
||||
DistanceField = "distanceField"
|
||||
Spherical = "spherical"
|
||||
MaxDistance = "maxDistance"
|
||||
Query = "query"
|
||||
DistanceMultiplier = "distanceMultiplier"
|
||||
IncludeLocs = "includeLocs"
|
||||
UniqueDocs = "uniqueDocs"
|
||||
MinDistance = "minDistance"
|
||||
Key = "key"
|
||||
)
|
||||
|
||||
// $graphLookup
|
||||
const (
|
||||
From = "from"
|
||||
StartWith = "startWith"
|
||||
ConnectFromField = "connectFromField"
|
||||
ConnectToField = "connectToField"
|
||||
As = "as"
|
||||
MaxDepth = "maxDepth"
|
||||
DepthField = "depthField"
|
||||
RestrictSearchWithMatch = "restrictSearchWithMatch"
|
||||
)
|
||||
|
||||
// $group
|
||||
const (
|
||||
// ID="_id" // Declared
|
||||
)
|
||||
|
||||
// $listLocalSessions
|
||||
const (
|
||||
// AllUsers = "allUsers" // Declared
|
||||
)
|
||||
|
||||
var (
|
||||
// EmptyDoc is empty document.
|
||||
EmptyDoc = bson.M{}
|
||||
|
||||
// AllUsersDoc is document that contains "allUsers":true value.
|
||||
AllUsersDoc = bson.M{AllUsers: true}
|
||||
)
|
||||
|
||||
// $listSessions : Same as $listLocalSessions.
|
||||
|
||||
// $lookup fields
|
||||
const (
|
||||
// From = "from" // Declared
|
||||
LocalField = "localField"
|
||||
ForeignField = "foreignField"
|
||||
// As = "as" // Declared
|
||||
|
||||
Let = "let"
|
||||
Pipeline = "pipeline"
|
||||
)
|
||||
|
||||
// $merge
|
||||
const (
|
||||
Into = "into"
|
||||
On = "on"
|
||||
// Let = "let" // Declared
|
||||
WhenMatched = "whenMatched"
|
||||
WhenNotMatched = "whenNotMatched"
|
||||
)
|
||||
|
||||
// $replaceRoot
|
||||
const (
|
||||
NewRoot = "newRoot"
|
||||
)
|
||||
|
||||
// $sample
|
||||
const (
|
||||
Size = "size"
|
||||
)
|
||||
|
||||
// $unwind
|
||||
const (
|
||||
Path = "path"
|
||||
IncludeArrayIndex = "includeArrayIndex"
|
||||
PreserveNullAndEmptyArrays = "preserveNullAndEmptyArrays"
|
||||
)
|
@ -0,0 +1,219 @@
|
||||
package mgm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
// CreatingHook is called before saving a new model to the database
|
||||
// Deprecated: please use CreatingHookWithCtx
|
||||
type CreatingHook interface {
|
||||
Creating() error
|
||||
}
|
||||
|
||||
// CreatingHookWithCtx is called before saving a new model to the database
|
||||
type CreatingHookWithCtx interface {
|
||||
Creating(context.Context) error
|
||||
}
|
||||
|
||||
// CreatedHook is called after a model has been created
|
||||
// Deprecated: Please use CreatedHookWithCtx
|
||||
type CreatedHook interface {
|
||||
Created() error
|
||||
}
|
||||
|
||||
// CreatedHookWithCtx is called after a model has been created
|
||||
type CreatedHookWithCtx interface {
|
||||
Created(context.Context) error
|
||||
}
|
||||
|
||||
// UpdatingHook is called before updating a model
|
||||
// Deprecated: Please use UpdatingHookWithCtx
|
||||
type UpdatingHook interface {
|
||||
Updating() error
|
||||
}
|
||||
|
||||
// UpdatingHookWithCtx is called before updating a model
|
||||
type UpdatingHookWithCtx interface {
|
||||
Updating(context.Context) error
|
||||
}
|
||||
|
||||
// UpdatedHook is called after a model is updated
|
||||
// Deprecated: Please use UpdatedHookWithCtx
|
||||
type UpdatedHook interface {
|
||||
// Deprecated:
|
||||
Updated(result *mongo.UpdateResult) error
|
||||
}
|
||||
|
||||
// UpdatedHookWithCtx is called after a model is updated
|
||||
type UpdatedHookWithCtx interface {
|
||||
Updated(ctx context.Context, result *mongo.UpdateResult) error
|
||||
}
|
||||
|
||||
// SavingHook is called before a model (new or existing) is saved to the database.
|
||||
// Deprecated: Please use SavingHookWithCtx
|
||||
type SavingHook interface {
|
||||
Saving() error
|
||||
}
|
||||
|
||||
// SavingHookWithCtx is called before a model (new or existing) is saved to the database.
|
||||
type SavingHookWithCtx interface {
|
||||
Saving(context.Context) error
|
||||
}
|
||||
|
||||
// SavedHook is called after a model is saved to the database.
|
||||
// Deprecated: Please use SavedHookWithCtx
|
||||
type SavedHook interface {
|
||||
Saved() error
|
||||
}
|
||||
|
||||
// SavedHookWithCtx is called after a model is saved to the database.
|
||||
type SavedHookWithCtx interface {
|
||||
Saved(context.Context) error
|
||||
}
|
||||
|
||||
// DeletingHook is called before a model is deleted
|
||||
// Deprecated: Please use DeletingHookWithCtx
|
||||
type DeletingHook interface {
|
||||
Deleting() error
|
||||
}
|
||||
|
||||
// DeletingHookWithCtx is called before a model is deleted
|
||||
type DeletingHookWithCtx interface {
|
||||
Deleting(context.Context) error
|
||||
}
|
||||
|
||||
// DeletedHook is called after a model is deleted
|
||||
// Deprecated: Please use DeletedHookWithCtx
|
||||
type DeletedHook interface {
|
||||
Deleted(result *mongo.DeleteResult) error
|
||||
}
|
||||
|
||||
// DeletedHookWithCtx is called after a model is deleted
|
||||
type DeletedHookWithCtx interface {
|
||||
Deleted(ctx context.Context, result *mongo.DeleteResult) error
|
||||
}
|
||||
|
||||
func callToBeforeCreateHooks(ctx context.Context, model Model) error {
|
||||
if hook, ok := model.(CreatingHookWithCtx); ok {
|
||||
if err := hook.Creating(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if hook, ok := model.(CreatingHook); ok {
|
||||
if err := hook.Creating(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if hook, ok := model.(SavingHookWithCtx); ok {
|
||||
if err := hook.Saving(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if hook, ok := model.(SavingHook); ok {
|
||||
if err := hook.Saving(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func callToBeforeUpdateHooks(ctx context.Context, model Model) error {
|
||||
if hook, ok := model.(UpdatingHookWithCtx); ok {
|
||||
if err := hook.Updating(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if hook, ok := model.(UpdatingHook); ok {
|
||||
if err := hook.Updating(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if hook, ok := model.(SavingHookWithCtx); ok {
|
||||
if err := hook.Saving(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if hook, ok := model.(SavingHook); ok {
|
||||
if err := hook.Saving(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func callToAfterCreateHooks(ctx context.Context, model Model) error {
|
||||
if hook, ok := model.(CreatedHookWithCtx); ok {
|
||||
if err := hook.Created(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if hook, ok := model.(CreatedHook); ok {
|
||||
if err := hook.Created(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if hook, ok := model.(SavedHookWithCtx); ok {
|
||||
if err := hook.Saved(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if hook, ok := model.(SavedHook); ok {
|
||||
if err := hook.Saved(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func callToAfterUpdateHooks(ctx context.Context, updateResult *mongo.UpdateResult, model Model) error {
|
||||
if hook, ok := model.(UpdatedHookWithCtx); ok {
|
||||
if err := hook.Updated(ctx, updateResult); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if hook, ok := model.(UpdatedHook); ok {
|
||||
if err := hook.Updated(updateResult); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if hook, ok := model.(SavedHookWithCtx); ok {
|
||||
if err := hook.Saved(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if hook, ok := model.(SavedHook); ok {
|
||||
if err := hook.Saved(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func callToBeforeDeleteHooks(ctx context.Context, model Model) error {
|
||||
if hook, ok := model.(DeletingHookWithCtx); ok {
|
||||
if err := hook.Deleting(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if hook, ok := model.(DeletingHook); ok {
|
||||
if err := hook.Deleting(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func callToAfterDeleteHooks(ctx context.Context, deleteResult *mongo.DeleteResult, model Model) error {
|
||||
if hook, ok := model.(DeletedHookWithCtx); ok {
|
||||
if err := hook.Deleted(ctx, deleteResult); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if hook, ok := model.(DeletedHook); ok {
|
||||
if err := hook.Deleted(deleteResult); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package util
|
||||
|
||||
import "reflect"
|
||||
|
||||
// IsNil function determines whether the parameter is nil or not. If the value itself is not nil,
|
||||
// reflection is used to determine whether the underlying value is nil.
|
||||
// See https://play.golang.org/p/Isoo0CcAvr for an example.
|
||||
func IsNil(val interface{}) (result bool) {
|
||||
|
||||
if val == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
switch v := reflect.ValueOf(val); v.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer,
|
||||
reflect.Interface, reflect.Slice:
|
||||
return v.IsNil()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// AnyNil returns true if any of the passed in parameters are nil, and returns false otherwise.
|
||||
func AnyNil(values ...interface{}) bool {
|
||||
for _, val := range values {
|
||||
|
||||
if IsNil(val) {
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
|
||||
var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
|
||||
|
||||
// ToSnakeCase returns snake_case of the provided value.
|
||||
func ToSnakeCase(str string) string {
|
||||
snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}")
|
||||
snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}")
|
||||
return strings.ToLower(snake)
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// AssertErrIsNil function assert that the passed-in error is nil.
|
||||
func AssertErrIsNil(t *testing.T, err error) {
|
||||
// The inserted model's id should not be nil:
|
||||
require.Nil(t, err, "Assertion err: %v", err)
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package util
|
||||
|
||||
// PanicErr panics using the passed-in error if it's not nil.
|
||||
func PanicErr(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package mgm
|
||||
|
||||
// CollectionGetter interface contains a method to return
|
||||
// a model's custom collection.
|
||||
type CollectionGetter interface {
|
||||
// Collection method return collection
|
||||
Collection() *Collection
|
||||
}
|
||||
|
||||
// CollectionNameGetter interface contains a method to return
|
||||
// the collection name of a model.
|
||||
type CollectionNameGetter interface {
|
||||
// CollectionName method return model collection's name.
|
||||
CollectionName() string
|
||||
}
|
||||
|
||||
// Model interface contains base methods that must be implemented by
|
||||
// each model. If you're using the `DefaultModel` struct in your model,
|
||||
// you don't need to implement any of these methods.
|
||||
type Model interface {
|
||||
// PrepareID converts the id value if needed, then
|
||||
// returns it (e.g convert string to objectId).
|
||||
PrepareID(id interface{}) (interface{}, error)
|
||||
|
||||
GetID() interface{}
|
||||
SetID(id interface{})
|
||||
}
|
||||
|
||||
// DefaultModel struct contains a model's default fields.
|
||||
type DefaultModel struct {
|
||||
IDField `bson:",inline"`
|
||||
DateFields `bson:",inline"`
|
||||
}
|
||||
|
||||
// Creating function calls the inner fields' defined hooks
|
||||
// TODO: get context as param in the next version (4).
|
||||
func (model *DefaultModel) Creating() error {
|
||||
return model.DateFields.Creating()
|
||||
}
|
||||
|
||||
// Saving function calls the inner fields' defined hooks
|
||||
// TODO: get context as param the next version(4).
|
||||
func (model *DefaultModel) Saving() error {
|
||||
return model.DateFields.Saving()
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package mgm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/kamva/mgm/v3/field"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func create(ctx context.Context, c *Collection, model Model, opts ...*options.InsertOneOptions) error {
|
||||
// Call to saving hook
|
||||
if err := callToBeforeCreateHooks(ctx, model); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := c.InsertOne(ctx, model, opts...)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set new id
|
||||
model.SetID(res.InsertedID)
|
||||
|
||||
return callToAfterCreateHooks(ctx, model)
|
||||
}
|
||||
|
||||
func first(ctx context.Context, c *Collection, filter interface{}, model Model, opts ...*options.FindOneOptions) error {
|
||||
return c.FindOne(ctx, filter, opts...).Decode(model)
|
||||
}
|
||||
|
||||
func update(ctx context.Context, c *Collection, model Model, opts ...*options.UpdateOptions) error {
|
||||
// Call to saving hook
|
||||
if err := callToBeforeUpdateHooks(ctx, model); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := c.UpdateOne(ctx, bson.M{field.ID: model.GetID()}, bson.M{"$set": model}, opts...)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return callToAfterUpdateHooks(ctx, res, model)
|
||||
}
|
||||
|
||||
func del(ctx context.Context, c *Collection, model Model) error {
|
||||
if err := callToBeforeDeleteHooks(ctx, model); err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := c.DeleteOne(ctx, bson.M{field.ID: model.GetID()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return callToAfterDeleteHooks(ctx, res, model)
|
||||
}
|
@ -0,0 +1,198 @@
|
||||
package operator
|
||||
|
||||
// Arithmetic Expression Operators
|
||||
const (
|
||||
Abs = "$abs"
|
||||
Add = "$add"
|
||||
Ceil = "$ceil"
|
||||
Divide = "$divide"
|
||||
Exp = "$exp"
|
||||
Floor = "$floor"
|
||||
Ln = "$ln"
|
||||
Log = "$log"
|
||||
Log10 = "$log10"
|
||||
// Mod = "$mod" // Declared
|
||||
Multiply = "$multiply"
|
||||
Pow = "$pow"
|
||||
Round = "$round"
|
||||
Sqrt = "$sqrt"
|
||||
Subtract = "$subtract"
|
||||
Trunc = "$trunc"
|
||||
)
|
||||
|
||||
// Array Expression Operators
|
||||
const (
|
||||
ArrayToObject = "$arrayToObject"
|
||||
ConcatArrays = "$concatArrays"
|
||||
Filter = "$filter"
|
||||
// In = "$in" // Declared
|
||||
IndexOfArray = "$indexOfArray"
|
||||
IsArray = "$isArray"
|
||||
Map = "$map"
|
||||
ObjectToArray = "$objectToArray"
|
||||
Range = "$range"
|
||||
Reduce = "$reduce"
|
||||
ReverseArray = "$reverseArray"
|
||||
// Size = "$size" // Declared
|
||||
// Slice = "$slice" // Declared
|
||||
Zip = "$zip"
|
||||
)
|
||||
|
||||
// Boolean Expression Operators
|
||||
const (
|
||||
// And = "$and" // Declared
|
||||
// Not = "$not" // Declared
|
||||
// Or = "$or" // Declared
|
||||
)
|
||||
|
||||
// Comparison Expression Operators
|
||||
const (
|
||||
Cmp = "$cmp"
|
||||
//Eq = "$eq" // Declared
|
||||
//Gt = "$gt" // Declared
|
||||
//Gte = "$gte" // Declared
|
||||
//Lt = "$lt" // Declared
|
||||
//Lte = "$lte" // Declared
|
||||
//Ne = "$ne" // Declared
|
||||
)
|
||||
|
||||
// Conditional Expression Operators
|
||||
const (
|
||||
Cond = "$cond"
|
||||
IfNull = "$ifNull"
|
||||
Switch = "$switch"
|
||||
)
|
||||
|
||||
// Date Expression Operators
|
||||
const (
|
||||
DateFromParts = "$dateFromParts"
|
||||
DateFromString = "$dateFromString"
|
||||
DateToParts = "$dateToParts"
|
||||
DateToString = "$dateToString"
|
||||
DayOfMonth = "$dayOfMonth"
|
||||
DayOfWeek = "$dayOfWeek"
|
||||
DayOfYear = "$dayOfYear"
|
||||
Hour = "$hour"
|
||||
IsoDayOfWeek = "$isoDayOfWeek"
|
||||
IsoWeek = "$isoWeek"
|
||||
IsoWeekYear = "$isoWeekYear"
|
||||
Millisecond = "$millisecond"
|
||||
Minute = "$minute"
|
||||
Month = "$month"
|
||||
Second = "$second"
|
||||
ToDate = "$toDate"
|
||||
Week = "$week"
|
||||
Year = "$year"
|
||||
)
|
||||
|
||||
// Literal Expression Operator
|
||||
const (
|
||||
Literal = "$literal"
|
||||
)
|
||||
|
||||
// Object Expression Operators
|
||||
const (
|
||||
MergeObjects = "$mergeObjects"
|
||||
// ObjectToArray = "$objectToArray" // Declared
|
||||
)
|
||||
|
||||
// Set Expression Operators
|
||||
const (
|
||||
AllElementsTrue = "$allElementsTrue"
|
||||
AnyElementTrue = "$anyElementTrue"
|
||||
SetDifference = "$setDifference"
|
||||
SetEquals = "$setEquals"
|
||||
SetIntersection = "$setIntersection"
|
||||
SetIsSubset = "$setIsSubset"
|
||||
SetUnion = "$setUnion"
|
||||
)
|
||||
|
||||
// String Expression Operators
|
||||
const (
|
||||
Concat = "$concat"
|
||||
// DateFromString = "$dateFromString" // Declared
|
||||
// DateToString = "$dateToString" // Declared
|
||||
IndexOfBytes = "$indexOfBytes"
|
||||
IndexOfCP = "$indexOfCP"
|
||||
Ltrim = "$ltrim"
|
||||
RegexFind = "$regexFind"
|
||||
RegexFindAll = "$regexFindAll"
|
||||
RegexMatch = "$regexMatch"
|
||||
Rtrim = "$rtrim"
|
||||
Split = "$split"
|
||||
StrLenBytes = "$strLenBytes"
|
||||
StrLenCP = "$strLenCP"
|
||||
Strcasecmp = "$strcasecmp"
|
||||
Substr = "$substr"
|
||||
SubstrBytes = "$substrBytes"
|
||||
SubstrCP = "$substrCP"
|
||||
ToLower = "$toLower"
|
||||
ToString = "$toString"
|
||||
Trim = "$trim"
|
||||
ToUpper = "$toUpper"
|
||||
)
|
||||
|
||||
// Text Expression Operator
|
||||
const (
|
||||
// Meta = "$meta" // Declared
|
||||
)
|
||||
|
||||
// Trigonometry Expression Operators
|
||||
const (
|
||||
Sin = "$sin"
|
||||
Cos = "$cos"
|
||||
Tan = "$tan"
|
||||
Asin = "$asin"
|
||||
Acos = "$acos"
|
||||
Atan = "$atan"
|
||||
Atan2 = "$atan2"
|
||||
Asinh = "$asinh"
|
||||
Acosh = "$acosh"
|
||||
Atanh = "$atanh"
|
||||
DegreesToRadians = "$degreesToRadians"
|
||||
RadiansToDegrees = "$radiansToDegrees"
|
||||
)
|
||||
|
||||
// Type Expression Operators
|
||||
const (
|
||||
Convert = "$convert"
|
||||
ToBool = "$toBool"
|
||||
//ToDate = "$toDate" // Declared
|
||||
ToDecimal = "$toDecimal"
|
||||
ToDouble = "$toDouble"
|
||||
ToInt = "$toInt"
|
||||
ToLong = "$toLong"
|
||||
ToObjectID = "$toObjectId"
|
||||
//ToString = "$toString" // Declared
|
||||
//Type = "$type" // Declared
|
||||
)
|
||||
|
||||
// Accumulators ($group)
|
||||
const (
|
||||
// AddToSet = "$addToSet" // Declared
|
||||
Avg = "$avg"
|
||||
First = "$first"
|
||||
Last = "$last"
|
||||
// Max = "$max" // Declared
|
||||
// MergeObjects = "$mergeObjects" // Declared
|
||||
// Min = "$min" // Declared
|
||||
// Push = "$push" // Declared
|
||||
StdDevPop = "$stdDevPop"
|
||||
StdDevSamp = "$stdDevSamp"
|
||||
Sum = "$sum"
|
||||
)
|
||||
|
||||
// Accumulators (in Other Stages)
|
||||
const (
|
||||
// Avg = "$avg" // Declared
|
||||
// Max = "$max" // Declared
|
||||
// Min = "$min" // Declared
|
||||
// StdDevPop = "$stdDevPop" // Declared
|
||||
// StdDevSamp = "$stdDevSamp" // Declared
|
||||
// Sum = "$sum" // Declared
|
||||
)
|
||||
|
||||
// Variable Expression Operators
|
||||
const (
|
||||
Let = "$let"
|
||||
)
|
@ -0,0 +1,39 @@
|
||||
package operator
|
||||
|
||||
// Collection aggregation Stages
|
||||
const (
|
||||
AddFields = "$addFields"
|
||||
Bucket = "$bucket"
|
||||
BucketAuto = "$bucketAuto"
|
||||
CollStats = "$collStats"
|
||||
Count = "$count"
|
||||
Facet = "$facet"
|
||||
GeoNear = "$geoNear"
|
||||
GraphLookup = "$graphLookup"
|
||||
Group = "$group"
|
||||
IndexStats = "$indexStats"
|
||||
Limit = "$limit"
|
||||
ListSessions = "$listSessions"
|
||||
Lookup = "$lookup"
|
||||
Match = "$match"
|
||||
Merge = "$merge"
|
||||
Out = "$out"
|
||||
PlanCacheStats = "$planCacheStats"
|
||||
Project = "$project"
|
||||
Redact = "$redact"
|
||||
ReplaceRoot = "$replaceRoot"
|
||||
ReplaceWith = "$replaceWith"
|
||||
Sample = "$sample"
|
||||
// Set = "$set" // Declared
|
||||
Skip = "$skip"
|
||||
// Sort = "$sort" // Declared
|
||||
SortByCount = "$sortByCount"
|
||||
// Unset = "$unset" // Declared
|
||||
Unwind = "$unwind"
|
||||
)
|
||||
|
||||
// DB Aggregate stages
|
||||
const (
|
||||
CurrentOp = "$currentOp"
|
||||
ListLocalSessions = "$listLocalSessions"
|
||||
)
|
@ -0,0 +1,73 @@
|
||||
package operator
|
||||
|
||||
// Comparison
|
||||
const (
|
||||
Eq = "$eq"
|
||||
Gt = "$gt"
|
||||
Gte = "$gte"
|
||||
In = "$in"
|
||||
Lt = "$lt"
|
||||
Lte = "$lte"
|
||||
Ne = "$ne"
|
||||
Nin = "$nin"
|
||||
)
|
||||
|
||||
// Logical
|
||||
const (
|
||||
And = "$and"
|
||||
Not = "$not"
|
||||
Nor = "$nor"
|
||||
Or = "$or"
|
||||
)
|
||||
|
||||
// Element
|
||||
const (
|
||||
Exists = "$exists"
|
||||
Type = "$type"
|
||||
)
|
||||
|
||||
// Evaluation
|
||||
const (
|
||||
Expr = "$expr"
|
||||
JSONSchema = "$jsonSchema"
|
||||
Mod = "$mod"
|
||||
Regex = "$regex"
|
||||
Text = "$text"
|
||||
Where = "$where"
|
||||
)
|
||||
|
||||
// Geo spatial
|
||||
const (
|
||||
GeoIntersects = "$geoIntersects"
|
||||
GeoWithin = "$geoWithin"
|
||||
Near = "$near"
|
||||
NearSphere = "$nearSphere"
|
||||
)
|
||||
|
||||
// Array
|
||||
const (
|
||||
All = "$all"
|
||||
ElemMatch = "$elemMatch"
|
||||
Size = "$size"
|
||||
)
|
||||
|
||||
// Bitwise
|
||||
const (
|
||||
BitsAllClear = "$bitsAllClear"
|
||||
BitsAllSet = "$bitsAllSet"
|
||||
BitsAnyClear = "$bitsAnyClear"
|
||||
BitsAnySet = "$bitsAnySet"
|
||||
)
|
||||
|
||||
// Comments
|
||||
const (
|
||||
Comment = "$comment"
|
||||
)
|
||||
|
||||
// Projection operators
|
||||
const (
|
||||
Dollar = "$"
|
||||
// ElemMatch = "$elemMatch" // Declared
|
||||
Meta = "$meta"
|
||||
Slice = "$slice"
|
||||
)
|
@ -0,0 +1,20 @@
|
||||
package operator
|
||||
|
||||
// Modifiers
|
||||
const (
|
||||
// Comment = "$comment" // Declared
|
||||
Explain = "$explain"
|
||||
Hint = "$hint"
|
||||
// Max = "$max" // Declared
|
||||
MaxTimeMS = "$maxTimeMS"
|
||||
// Min = "$min" // Declared
|
||||
OrderBy = "$orderby"
|
||||
Query = "$query"
|
||||
ReturnKey = "$returnKey"
|
||||
ShowDiskLoc = "$showDiskLoc"
|
||||
)
|
||||
|
||||
// Sort Order
|
||||
const (
|
||||
Natural = "$natural"
|
||||
)
|
@ -0,0 +1,40 @@
|
||||
package operator
|
||||
|
||||
// Fields
|
||||
const (
|
||||
CurrentDate = "$currentDate"
|
||||
Inc = "$inc"
|
||||
Min = "$min"
|
||||
Max = "$max"
|
||||
Mul = "$mul"
|
||||
Rename = "$rename"
|
||||
Set = "$set"
|
||||
SetOnInsert = "$setOnInsert"
|
||||
Unset = "$unset"
|
||||
)
|
||||
|
||||
// Array Operators
|
||||
const (
|
||||
// $: Act as a modifier
|
||||
// $[]: Act as a modifier
|
||||
// $[<identifier>]: Act as a modifier
|
||||
|
||||
AddToSet = "$addToSet"
|
||||
Pop = "$pop"
|
||||
Pull = "$pull"
|
||||
Push = "$push"
|
||||
PullAll = "$pullAll"
|
||||
)
|
||||
|
||||
// Array modifiers
|
||||
const (
|
||||
Each = "$each"
|
||||
Position = "$position"
|
||||
// Slice = "$slice" // Declared
|
||||
Sort = "$sort"
|
||||
)
|
||||
|
||||
// Array bitwise
|
||||
const (
|
||||
Bit = "$bit"
|
||||
)
|
@ -0,0 +1,39 @@
|
||||
package mgm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
// TransactionFunc is a handler to manage a transaction.
|
||||
type TransactionFunc func(session mongo.Session, sc mongo.SessionContext) error
|
||||
|
||||
// Transaction creates a transaction with the default client.
|
||||
func Transaction(f TransactionFunc) error {
|
||||
return TransactionWithClient(ctx(), client, f)
|
||||
}
|
||||
|
||||
// TransactionWithCtx creates a transaction with the given context and the default client.
|
||||
func TransactionWithCtx(ctx context.Context, f TransactionFunc) error {
|
||||
return TransactionWithClient(ctx, client, f)
|
||||
}
|
||||
|
||||
// TransactionWithClient creates a transaction with the given client.
|
||||
func TransactionWithClient(ctx context.Context, client *mongo.Client, f TransactionFunc) error {
|
||||
session, err := client.StartSession() //start session need to get options.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer session.EndSession(ctx)
|
||||
|
||||
if err = session.StartTransaction(); err != nil { // startTransaction need to get options.
|
||||
return err
|
||||
}
|
||||
|
||||
wrapperFn := func(sc mongo.SessionContext) error {
|
||||
return f(session, sc)
|
||||
}
|
||||
|
||||
return mongo.WithSession(ctx, session, wrapperFn)
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package mgm
|
||||
|
||||
import (
|
||||
"github.com/kamva/mgm/v3/internal/util"
|
||||
"github.com/jinzhu/inflection"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Coll returns the collection associated with a model.
|
||||
func Coll(m Model, opts ...*options.CollectionOptions) *Collection {
|
||||
|
||||
if collGetter, ok := m.(CollectionGetter); ok {
|
||||
return collGetter.Collection()
|
||||
}
|
||||
|
||||
return CollectionByName(CollName(m), opts...)
|
||||
}
|
||||
|
||||
// CollName returns a model's collection name. The `CollectionNameGetter` will be used
|
||||
// if the model implements this interface. Otherwise, the collection name is inferred
|
||||
// based on the model's type using reflection.
|
||||
func CollName(m Model) string {
|
||||
|
||||
if collNameGetter, ok := m.(CollectionNameGetter); ok {
|
||||
return collNameGetter.CollectionName()
|
||||
}
|
||||
|
||||
name := reflect.TypeOf(m).Elem().Name()
|
||||
|
||||
return inflection.Plural(util.ToSnakeCase(name))
|
||||
}
|
||||
|
||||
// UpsertTrueOption returns new instance of UpdateOptions with the upsert property set to true.
|
||||
func UpsertTrueOption() *options.UpdateOptions {
|
||||
upsert := true
|
||||
return &options.UpdateOptions{Upsert: &upsert}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package mgm
|
||||
|
||||
// Version of the package
|
||||
const Version = "3.3.0"
|
@ -1,13 +0,0 @@
|
||||
// Copyright (C) 2019 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build sqlite_json sqlite_json1 json1
|
||||
|
||||
package sqlite3
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -DSQLITE_ENABLE_JSON1
|
||||
*/
|
||||
import "C"
|
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2013, Patrick Mezard
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
The names of its contributors may not be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -0,0 +1,772 @@
|
||||
// Package difflib is a partial port of Python difflib module.
|
||||
//
|
||||
// It provides tools to compare sequences of strings and generate textual diffs.
|
||||
//
|
||||
// The following class and functions have been ported:
|
||||
//
|
||||
// - SequenceMatcher
|
||||
//
|
||||
// - unified_diff
|
||||
//
|
||||
// - context_diff
|
||||
//
|
||||
// Getting unified diffs was the main goal of the port. Keep in mind this code
|
||||
// is mostly suitable to output text differences in a human friendly way, there
|
||||
// are no guarantees generated diffs are consumable by patch(1).
|
||||
package difflib
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func calculateRatio(matches, length int) float64 {
|
||||
if length > 0 {
|
||||
return 2.0 * float64(matches) / float64(length)
|
||||
}
|
||||
return 1.0
|
||||
}
|
||||
|
||||
type Match struct {
|
||||
A int
|
||||
B int
|
||||
Size int
|
||||
}
|
||||
|
||||
type OpCode struct {
|
||||
Tag byte
|
||||
I1 int
|
||||
I2 int
|
||||
J1 int
|
||||
J2 int
|
||||
}
|
||||
|
||||
// SequenceMatcher compares sequence of strings. The basic
|
||||
// algorithm predates, and is a little fancier than, an algorithm
|
||||
// published in the late 1980's by Ratcliff and Obershelp under the
|
||||
// hyperbolic name "gestalt pattern matching". The basic idea is to find
|
||||
// the longest contiguous matching subsequence that contains no "junk"
|
||||
// elements (R-O doesn't address junk). The same idea is then applied
|
||||
// recursively to the pieces of the sequences to the left and to the right
|
||||
// of the matching subsequence. This does not yield minimal edit
|
||||
// sequences, but does tend to yield matches that "look right" to people.
|
||||
//
|
||||
// SequenceMatcher tries to compute a "human-friendly diff" between two
|
||||
// sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the
|
||||
// longest *contiguous* & junk-free matching subsequence. That's what
|
||||
// catches peoples' eyes. The Windows(tm) windiff has another interesting
|
||||
// notion, pairing up elements that appear uniquely in each sequence.
|
||||
// That, and the method here, appear to yield more intuitive difference
|
||||
// reports than does diff. This method appears to be the least vulnerable
|
||||
// to synching up on blocks of "junk lines", though (like blank lines in
|
||||
// ordinary text files, or maybe "<P>" lines in HTML files). That may be
|
||||
// because this is the only method of the 3 that has a *concept* of
|
||||
// "junk" <wink>.
|
||||
//
|
||||
// Timing: Basic R-O is cubic time worst case and quadratic time expected
|
||||
// case. SequenceMatcher is quadratic time for the worst case and has
|
||||
// expected-case behavior dependent in a complicated way on how many
|
||||
// elements the sequences have in common; best case time is linear.
|
||||
type SequenceMatcher struct {
|
||||
a []string
|
||||
b []string
|
||||
b2j map[string][]int
|
||||
IsJunk func(string) bool
|
||||
autoJunk bool
|
||||
bJunk map[string]struct{}
|
||||
matchingBlocks []Match
|
||||
fullBCount map[string]int
|
||||
bPopular map[string]struct{}
|
||||
opCodes []OpCode
|
||||
}
|
||||
|
||||
func NewMatcher(a, b []string) *SequenceMatcher {
|
||||
m := SequenceMatcher{autoJunk: true}
|
||||
m.SetSeqs(a, b)
|
||||
return &m
|
||||
}
|
||||
|
||||
func NewMatcherWithJunk(a, b []string, autoJunk bool,
|
||||
isJunk func(string) bool) *SequenceMatcher {
|
||||
|
||||
m := SequenceMatcher{IsJunk: isJunk, autoJunk: autoJunk}
|
||||
m.SetSeqs(a, b)
|
||||
return &m
|
||||
}
|
||||
|
||||
// Set two sequences to be compared.
|
||||
func (m *SequenceMatcher) SetSeqs(a, b []string) {
|
||||
m.SetSeq1(a)
|
||||
m.SetSeq2(b)
|
||||
}
|
||||
|
||||
// Set the first sequence to be compared. The second sequence to be compared is
|
||||
// not changed.
|
||||
//
|
||||
// SequenceMatcher computes and caches detailed information about the second
|
||||
// sequence, so if you want to compare one sequence S against many sequences,
|
||||
// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other
|
||||
// sequences.
|
||||
//
|
||||
// See also SetSeqs() and SetSeq2().
|
||||
func (m *SequenceMatcher) SetSeq1(a []string) {
|
||||
if &a == &m.a {
|
||||
return
|
||||
}
|
||||
m.a = a
|
||||
m.matchingBlocks = nil
|
||||
m.opCodes = nil
|
||||
}
|
||||
|
||||
// Set the second sequence to be compared. The first sequence to be compared is
|
||||
// not changed.
|
||||
func (m *SequenceMatcher) SetSeq2(b []string) {
|
||||
if &b == &m.b {
|
||||
return
|
||||
}
|
||||
m.b = b
|
||||
m.matchingBlocks = nil
|
||||
m.opCodes = nil
|
||||
m.fullBCount = nil
|
||||
m.chainB()
|
||||
}
|
||||
|
||||
func (m *SequenceMatcher) chainB() {
|
||||
// Populate line -> index mapping
|
||||
b2j := map[string][]int{}
|
||||
for i, s := range m.b {
|
||||
indices := b2j[s]
|
||||
indices = append(indices, i)
|
||||
b2j[s] = indices
|
||||
}
|
||||
|
||||
// Purge junk elements
|
||||
m.bJunk = map[string]struct{}{}
|
||||
if m.IsJunk != nil {
|
||||
junk := m.bJunk
|
||||
for s, _ := range b2j {
|
||||
if m.IsJunk(s) {
|
||||
junk[s] = struct{}{}
|
||||
}
|
||||
}
|
||||
for s, _ := range junk {
|
||||
delete(b2j, s)
|
||||
}
|
||||
}
|
||||
|
||||
// Purge remaining popular elements
|
||||
popular := map[string]struct{}{}
|
||||
n := len(m.b)
|
||||
if m.autoJunk && n >= 200 {
|
||||
ntest := n/100 + 1
|
||||
for s, indices := range b2j {
|
||||
if len(indices) > ntest {
|
||||
popular[s] = struct{}{}
|
||||
}
|
||||
}
|
||||
for s, _ := range popular {
|
||||
delete(b2j, s)
|
||||
}
|
||||
}
|
||||
m.bPopular = popular
|
||||
m.b2j = b2j
|
||||
}
|
||||
|
||||
func (m *SequenceMatcher) isBJunk(s string) bool {
|
||||
_, ok := m.bJunk[s]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Find longest matching block in a[alo:ahi] and b[blo:bhi].
|
||||
//
|
||||
// If IsJunk is not defined:
|
||||
//
|
||||
// Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
|
||||
// alo <= i <= i+k <= ahi
|
||||
// blo <= j <= j+k <= bhi
|
||||
// and for all (i',j',k') meeting those conditions,
|
||||
// k >= k'
|
||||
// i <= i'
|
||||
// and if i == i', j <= j'
|
||||
//
|
||||
// In other words, of all maximal matching blocks, return one that
|
||||
// starts earliest in a, and of all those maximal matching blocks that
|
||||
// start earliest in a, return the one that starts earliest in b.
|
||||
//
|
||||
// If IsJunk is defined, first the longest matching block is
|
||||
// determined as above, but with the additional restriction that no
|
||||
// junk element appears in the block. Then that block is extended as
|
||||
// far as possible by matching (only) junk elements on both sides. So
|
||||
// the resulting block never matches on junk except as identical junk
|
||||
// happens to be adjacent to an "interesting" match.
|
||||
//
|
||||
// If no blocks match, return (alo, blo, 0).
|
||||
func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match {
|
||||
// CAUTION: stripping common prefix or suffix would be incorrect.
|
||||
// E.g.,
|
||||
// ab
|
||||
// acab
|
||||
// Longest matching block is "ab", but if common prefix is
|
||||
// stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
|
||||
// strip, so ends up claiming that ab is changed to acab by
|
||||
// inserting "ca" in the middle. That's minimal but unintuitive:
|
||||
// "it's obvious" that someone inserted "ac" at the front.
|
||||
// Windiff ends up at the same place as diff, but by pairing up
|
||||
// the unique 'b's and then matching the first two 'a's.
|
||||
besti, bestj, bestsize := alo, blo, 0
|
||||
|
||||
// find longest junk-free match
|
||||
// during an iteration of the loop, j2len[j] = length of longest
|
||||
// junk-free match ending with a[i-1] and b[j]
|
||||
j2len := map[int]int{}
|
||||
for i := alo; i != ahi; i++ {
|
||||
// look at all instances of a[i] in b; note that because
|
||||
// b2j has no junk keys, the loop is skipped if a[i] is junk
|
||||
newj2len := map[int]int{}
|
||||
for _, j := range m.b2j[m.a[i]] {
|
||||
// a[i] matches b[j]
|
||||
if j < blo {
|
||||
continue
|
||||
}
|
||||
if j >= bhi {
|
||||
break
|
||||
}
|
||||
k := j2len[j-1] + 1
|
||||
newj2len[j] = k
|
||||
if k > bestsize {
|
||||
besti, bestj, bestsize = i-k+1, j-k+1, k
|
||||
}
|
||||
}
|
||||
j2len = newj2len
|
||||
}
|
||||
|
||||
// Extend the best by non-junk elements on each end. In particular,
|
||||
// "popular" non-junk elements aren't in b2j, which greatly speeds
|
||||
// the inner loop above, but also means "the best" match so far
|
||||
// doesn't contain any junk *or* popular non-junk elements.
|
||||
for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) &&
|
||||
m.a[besti-1] == m.b[bestj-1] {
|
||||
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
||||
}
|
||||
for besti+bestsize < ahi && bestj+bestsize < bhi &&
|
||||
!m.isBJunk(m.b[bestj+bestsize]) &&
|
||||
m.a[besti+bestsize] == m.b[bestj+bestsize] {
|
||||
bestsize += 1
|
||||
}
|
||||
|
||||
// Now that we have a wholly interesting match (albeit possibly
|
||||
// empty!), we may as well suck up the matching junk on each
|
||||
// side of it too. Can't think of a good reason not to, and it
|
||||
// saves post-processing the (possibly considerable) expense of
|
||||
// figuring out what to do with it. In the case of an empty
|
||||
// interesting match, this is clearly the right thing to do,
|
||||
// because no other kind of match is possible in the regions.
|
||||
for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) &&
|
||||
m.a[besti-1] == m.b[bestj-1] {
|
||||
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
||||
}
|
||||
for besti+bestsize < ahi && bestj+bestsize < bhi &&
|
||||
m.isBJunk(m.b[bestj+bestsize]) &&
|
||||
m.a[besti+bestsize] == m.b[bestj+bestsize] {
|
||||
bestsize += 1
|
||||
}
|
||||
|
||||
return Match{A: besti, B: bestj, Size: bestsize}
|
||||
}
|
||||
|
||||
// Return list of triples describing matching subsequences.
|
||||
//
|
||||
// Each triple is of the form (i, j, n), and means that
|
||||
// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
|
||||
// i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are
|
||||
// adjacent triples in the list, and the second is not the last triple in the
|
||||
// list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe
|
||||
// adjacent equal blocks.
|
||||
//
|
||||
// The last triple is a dummy, (len(a), len(b), 0), and is the only
|
||||
// triple with n==0.
|
||||
func (m *SequenceMatcher) GetMatchingBlocks() []Match {
|
||||
if m.matchingBlocks != nil {
|
||||
return m.matchingBlocks
|
||||
}
|
||||
|
||||
var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match
|
||||
matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match {
|
||||
match := m.findLongestMatch(alo, ahi, blo, bhi)
|
||||
i, j, k := match.A, match.B, match.Size
|
||||
if match.Size > 0 {
|
||||
if alo < i && blo < j {
|
||||
matched = matchBlocks(alo, i, blo, j, matched)
|
||||
}
|
||||
matched = append(matched, match)
|
||||
if i+k < ahi && j+k < bhi {
|
||||
matched = matchBlocks(i+k, ahi, j+k, bhi, matched)
|
||||
}
|
||||
}
|
||||
return matched
|
||||
}
|
||||
matched := matchBlocks(0, len(m.a), 0, len(m.b), nil)
|
||||
|
||||
// It's possible that we have adjacent equal blocks in the
|
||||
// matching_blocks list now.
|
||||
nonAdjacent := []Match{}
|
||||
i1, j1, k1 := 0, 0, 0
|
||||
for _, b := range matched {
|
||||
// Is this block adjacent to i1, j1, k1?
|
||||
i2, j2, k2 := b.A, b.B, b.Size
|
||||
if i1+k1 == i2 && j1+k1 == j2 {
|
||||
// Yes, so collapse them -- this just increases the length of
|
||||
// the first block by the length of the second, and the first
|
||||
// block so lengthened remains the block to compare against.
|
||||
k1 += k2
|
||||
} else {
|
||||
// Not adjacent. Remember the first block (k1==0 means it's
|
||||
// the dummy we started with), and make the second block the
|
||||
// new block to compare against.
|
||||
if k1 > 0 {
|
||||
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
|
||||
}
|
||||
i1, j1, k1 = i2, j2, k2
|
||||
}
|
||||
}
|
||||
if k1 > 0 {
|
||||
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
|
||||
}
|
||||
|
||||
nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0})
|
||||
m.matchingBlocks = nonAdjacent
|
||||
return m.matchingBlocks
|
||||
}
|
||||
|
||||
// Return list of 5-tuples describing how to turn a into b.
|
||||
//
|
||||
// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
|
||||
// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
|
||||
// tuple preceding it, and likewise for j1 == the previous j2.
|
||||
//
|
||||
// The tags are characters, with these meanings:
|
||||
//
|
||||
// 'r' (replace): a[i1:i2] should be replaced by b[j1:j2]
|
||||
//
|
||||
// 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case.
|
||||
//
|
||||
// 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case.
|
||||
//
|
||||
// 'e' (equal): a[i1:i2] == b[j1:j2]
|
||||
func (m *SequenceMatcher) GetOpCodes() []OpCode {
|
||||
if m.opCodes != nil {
|
||||
return m.opCodes
|
||||
}
|
||||
i, j := 0, 0
|
||||
matching := m.GetMatchingBlocks()
|
||||
opCodes := make([]OpCode, 0, len(matching))
|
||||
for _, m := range matching {
|
||||
// invariant: we've pumped out correct diffs to change
|
||||
// a[:i] into b[:j], and the next matching block is
|
||||
// a[ai:ai+size] == b[bj:bj+size]. So we need to pump
|
||||
// out a diff to change a[i:ai] into b[j:bj], pump out
|
||||
// the matching block, and move (i,j) beyond the match
|
||||
ai, bj, size := m.A, m.B, m.Size
|
||||
tag := byte(0)
|
||||
if i < ai && j < bj {
|
||||
tag = 'r'
|
||||
} else if i < ai {
|
||||
tag = 'd'
|
||||
} else if j < bj {
|
||||
tag = 'i'
|
||||
}
|
||||
if tag > 0 {
|
||||
opCodes = append(opCodes, OpCode{tag, i, ai, j, bj})
|
||||
}
|
||||
i, j = ai+size, bj+size
|
||||
// the list of matching blocks is terminated by a
|
||||
// sentinel with size 0
|
||||
if size > 0 {
|
||||
opCodes = append(opCodes, OpCode{'e', ai, i, bj, j})
|
||||
}
|
||||
}
|
||||
m.opCodes = opCodes
|
||||
return m.opCodes
|
||||
}
|
||||
|
||||
// Isolate change clusters by eliminating ranges with no changes.
|
||||
//
|
||||
// Return a generator of groups with up to n lines of context.
|
||||
// Each group is in the same format as returned by GetOpCodes().
|
||||
func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
|
||||
if n < 0 {
|
||||
n = 3
|
||||
}
|
||||
codes := m.GetOpCodes()
|
||||
if len(codes) == 0 {
|
||||
codes = []OpCode{OpCode{'e', 0, 1, 0, 1}}
|
||||
}
|
||||
// Fixup leading and trailing groups if they show no changes.
|
||||
if codes[0].Tag == 'e' {
|
||||
c := codes[0]
|
||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||
codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2}
|
||||
}
|
||||
if codes[len(codes)-1].Tag == 'e' {
|
||||
c := codes[len(codes)-1]
|
||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||
codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)}
|
||||
}
|
||||
nn := n + n
|
||||
groups := [][]OpCode{}
|
||||
group := []OpCode{}
|
||||
for _, c := range codes {
|
||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||
// End the current group and start a new one whenever
|
||||
// there is a large range with no changes.
|
||||
if c.Tag == 'e' && i2-i1 > nn {
|
||||
group = append(group, OpCode{c.Tag, i1, min(i2, i1+n),
|
||||
j1, min(j2, j1+n)})
|
||||
groups = append(groups, group)
|
||||
group = []OpCode{}
|
||||
i1, j1 = max(i1, i2-n), max(j1, j2-n)
|
||||
}
|
||||
group = append(group, OpCode{c.Tag, i1, i2, j1, j2})
|
||||
}
|
||||
if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') {
|
||||
groups = append(groups, group)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
// Return a measure of the sequences' similarity (float in [0,1]).
|
||||
//
|
||||
// Where T is the total number of elements in both sequences, and
|
||||
// M is the number of matches, this is 2.0*M / T.
|
||||
// Note that this is 1 if the sequences are identical, and 0 if
|
||||
// they have nothing in common.
|
||||
//
|
||||
// .Ratio() is expensive to compute if you haven't already computed
|
||||
// .GetMatchingBlocks() or .GetOpCodes(), in which case you may
|
||||
// want to try .QuickRatio() or .RealQuickRation() first to get an
|
||||
// upper bound.
|
||||
func (m *SequenceMatcher) Ratio() float64 {
|
||||
matches := 0
|
||||
for _, m := range m.GetMatchingBlocks() {
|
||||
matches += m.Size
|
||||
}
|
||||
return calculateRatio(matches, len(m.a)+len(m.b))
|
||||
}
|
||||
|
||||
// Return an upper bound on ratio() relatively quickly.
|
||||
//
|
||||
// This isn't defined beyond that it is an upper bound on .Ratio(), and
|
||||
// is faster to compute.
|
||||
func (m *SequenceMatcher) QuickRatio() float64 {
|
||||
// viewing a and b as multisets, set matches to the cardinality
|
||||
// of their intersection; this counts the number of matches
|
||||
// without regard to order, so is clearly an upper bound
|
||||
if m.fullBCount == nil {
|
||||
m.fullBCount = map[string]int{}
|
||||
for _, s := range m.b {
|
||||
m.fullBCount[s] = m.fullBCount[s] + 1
|
||||
}
|
||||
}
|
||||
|
||||
// avail[x] is the number of times x appears in 'b' less the
|
||||
// number of times we've seen it in 'a' so far ... kinda
|
||||
avail := map[string]int{}
|
||||
matches := 0
|
||||
for _, s := range m.a {
|
||||
n, ok := avail[s]
|
||||
if !ok {
|
||||
n = m.fullBCount[s]
|
||||
}
|
||||
avail[s] = n - 1
|
||||
if n > 0 {
|
||||
matches += 1
|
||||
}
|
||||
}
|
||||
return calculateRatio(matches, len(m.a)+len(m.b))
|
||||
}
|
||||
|
||||
// Return an upper bound on ratio() very quickly.
|
||||
//
|
||||
// This isn't defined beyond that it is an upper bound on .Ratio(), and
|
||||
// is faster to compute than either .Ratio() or .QuickRatio().
|
||||
func (m *SequenceMatcher) RealQuickRatio() float64 {
|
||||
la, lb := len(m.a), len(m.b)
|
||||
return calculateRatio(min(la, lb), la+lb)
|
||||
}
|
||||
|
||||
// Convert range to the "ed" format
|
||||
func formatRangeUnified(start, stop int) string {
|
||||
// Per the diff spec at http://www.unix.org/single_unix_specification/
|
||||
beginning := start + 1 // lines start numbering with one
|
||||
length := stop - start
|
||||
if length == 1 {
|
||||
return fmt.Sprintf("%d", beginning)
|
||||
}
|
||||
if length == 0 {
|
||||
beginning -= 1 // empty ranges begin at line just before the range
|
||||
}
|
||||
return fmt.Sprintf("%d,%d", beginning, length)
|
||||
}
|
||||
|
||||
// Unified diff parameters
|
||||
type UnifiedDiff struct {
|
||||
A []string // First sequence lines
|
||||
FromFile string // First file name
|
||||
FromDate string // First file time
|
||||
B []string // Second sequence lines
|
||||
ToFile string // Second file name
|
||||
ToDate string // Second file time
|
||||
Eol string // Headers end of line, defaults to LF
|
||||
Context int // Number of context lines
|
||||
}
|
||||
|
||||
// Compare two sequences of lines; generate the delta as a unified diff.
|
||||
//
|
||||
// Unified diffs are a compact way of showing line changes and a few
|
||||
// lines of context. The number of context lines is set by 'n' which
|
||||
// defaults to three.
|
||||
//
|
||||
// By default, the diff control lines (those with ---, +++, or @@) are
|
||||
// created with a trailing newline. This is helpful so that inputs
|
||||
// created from file.readlines() result in diffs that are suitable for
|
||||
// file.writelines() since both the inputs and outputs have trailing
|
||||
// newlines.
|
||||
//
|
||||
// For inputs that do not have trailing newlines, set the lineterm
|
||||
// argument to "" so that the output will be uniformly newline free.
|
||||
//
|
||||
// The unidiff format normally has a header for filenames and modification
|
||||
// times. Any or all of these may be specified using strings for
|
||||
// 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
|
||||
// The modification times are normally expressed in the ISO 8601 format.
|
||||
func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
|
||||
buf := bufio.NewWriter(writer)
|
||||
defer buf.Flush()
|
||||
wf := func(format string, args ...interface{}) error {
|
||||
_, err := buf.WriteString(fmt.Sprintf(format, args...))
|
||||
return err
|
||||
}
|
||||
ws := func(s string) error {
|
||||
_, err := buf.WriteString(s)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(diff.Eol) == 0 {
|
||||
diff.Eol = "\n"
|
||||
}
|
||||
|
||||
started := false
|
||||
m := NewMatcher(diff.A, diff.B)
|
||||
for _, g := range m.GetGroupedOpCodes(diff.Context) {
|
||||
if !started {
|
||||
started = true
|
||||
fromDate := ""
|
||||
if len(diff.FromDate) > 0 {
|
||||
fromDate = "\t" + diff.FromDate
|
||||
}
|
||||
toDate := ""
|
||||
if len(diff.ToDate) > 0 {
|
||||
toDate = "\t" + diff.ToDate
|
||||
}
|
||||
if diff.FromFile != "" || diff.ToFile != "" {
|
||||
err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
first, last := g[0], g[len(g)-1]
|
||||
range1 := formatRangeUnified(first.I1, last.I2)
|
||||
range2 := formatRangeUnified(first.J1, last.J2)
|
||||
if err := wf("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, c := range g {
|
||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||
if c.Tag == 'e' {
|
||||
for _, line := range diff.A[i1:i2] {
|
||||
if err := ws(" " + line); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if c.Tag == 'r' || c.Tag == 'd' {
|
||||
for _, line := range diff.A[i1:i2] {
|
||||
if err := ws("-" + line); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.Tag == 'r' || c.Tag == 'i' {
|
||||
for _, line := range diff.B[j1:j2] {
|
||||
if err := ws("+" + line); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Like WriteUnifiedDiff but returns the diff a string.
|
||||
func GetUnifiedDiffString(diff UnifiedDiff) (string, error) {
|
||||
w := &bytes.Buffer{}
|
||||
err := WriteUnifiedDiff(w, diff)
|
||||
return string(w.Bytes()), err
|
||||
}
|
||||
|
||||
// Convert range to the "ed" format.
|
||||
func formatRangeContext(start, stop int) string {
|
||||
// Per the diff spec at http://www.unix.org/single_unix_specification/
|
||||
beginning := start + 1 // lines start numbering with one
|
||||
length := stop - start
|
||||
if length == 0 {
|
||||
beginning -= 1 // empty ranges begin at line just before the range
|
||||
}
|
||||
if length <= 1 {
|
||||
return fmt.Sprintf("%d", beginning)
|
||||
}
|
||||
return fmt.Sprintf("%d,%d", beginning, beginning+length-1)
|
||||
}
|
||||
|
||||
type ContextDiff UnifiedDiff
|
||||
|
||||
// Compare two sequences of lines; generate the delta as a context diff.
|
||||
//
|
||||
// Context diffs are a compact way of showing line changes and a few
|
||||
// lines of context. The number of context lines is set by diff.Context
|
||||
// which defaults to three.
|
||||
//
|
||||
// By default, the diff control lines (those with *** or ---) are
|
||||
// created with a trailing newline.
|
||||
//
|
||||
// For inputs that do not have trailing newlines, set the diff.Eol
|
||||
// argument to "" so that the output will be uniformly newline free.
|
||||
//
|
||||
// The context diff format normally has a header for filenames and
|
||||
// modification times. Any or all of these may be specified using
|
||||
// strings for diff.FromFile, diff.ToFile, diff.FromDate, diff.ToDate.
|
||||
// The modification times are normally expressed in the ISO 8601 format.
|
||||
// If not specified, the strings default to blanks.
|
||||
func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
|
||||
buf := bufio.NewWriter(writer)
|
||||
defer buf.Flush()
|
||||
var diffErr error
|
||||
wf := func(format string, args ...interface{}) {
|
||||
_, err := buf.WriteString(fmt.Sprintf(format, args...))
|
||||
if diffErr == nil && err != nil {
|
||||
diffErr = err
|
||||
}
|
||||
}
|
||||
ws := func(s string) {
|
||||
_, err := buf.WriteString(s)
|
||||
if diffErr == nil && err != nil {
|
||||
diffErr = err
|
||||
}
|
||||
}
|
||||
|
||||
if len(diff.Eol) == 0 {
|
||||
diff.Eol = "\n"
|
||||
}
|
||||
|
||||
prefix := map[byte]string{
|
||||
'i': "+ ",
|
||||
'd': "- ",
|
||||
'r': "! ",
|
||||
'e': " ",
|
||||
}
|
||||
|
||||
started := false
|
||||
m := NewMatcher(diff.A, diff.B)
|
||||
for _, g := range m.GetGroupedOpCodes(diff.Context) {
|
||||
if !started {
|
||||
started = true
|
||||
fromDate := ""
|
||||
if len(diff.FromDate) > 0 {
|
||||
fromDate = "\t" + diff.FromDate
|
||||
}
|
||||
toDate := ""
|
||||
if len(diff.ToDate) > 0 {
|
||||
toDate = "\t" + diff.ToDate
|
||||
}
|
||||
if diff.FromFile != "" || diff.ToFile != "" {
|
||||
wf("*** %s%s%s", diff.FromFile, fromDate, diff.Eol)
|
||||
wf("--- %s%s%s", diff.ToFile, toDate, diff.Eol)
|
||||
}
|
||||
}
|
||||
|
||||
first, last := g[0], g[len(g)-1]
|
||||
ws("***************" + diff.Eol)
|
||||
|
||||
range1 := formatRangeContext(first.I1, last.I2)
|
||||
wf("*** %s ****%s", range1, diff.Eol)
|
||||
for _, c := range g {
|
||||
if c.Tag == 'r' || c.Tag == 'd' {
|
||||
for _, cc := range g {
|
||||
if cc.Tag == 'i' {
|
||||
continue
|
||||
}
|
||||
for _, line := range diff.A[cc.I1:cc.I2] {
|
||||
ws(prefix[cc.Tag] + line)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
range2 := formatRangeContext(first.J1, last.J2)
|
||||
wf("--- %s ----%s", range2, diff.Eol)
|
||||
for _, c := range g {
|
||||
if c.Tag == 'r' || c.Tag == 'i' {
|
||||
for _, cc := range g {
|
||||
if cc.Tag == 'd' {
|
||||
continue
|
||||
}
|
||||
for _, line := range diff.B[cc.J1:cc.J2] {
|
||||
ws(prefix[cc.Tag] + line)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return diffErr
|
||||
}
|
||||
|
||||
// Like WriteContextDiff but returns the diff a string.
|
||||
func GetContextDiffString(diff ContextDiff) (string, error) {
|
||||
w := &bytes.Buffer{}
|
||||
err := WriteContextDiff(w, diff)
|
||||
return string(w.Bytes()), err
|
||||
}
|
||||
|
||||
// Split a string on "\n" while preserving them. The output can be used
|
||||
// as input for UnifiedDiff and ContextDiff structures.
|
||||
func SplitLines(s string) []string {
|
||||
lines := strings.SplitAfter(s, "\n")
|
||||
lines[len(lines)-1] += "\n"
|
||||
return lines
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors.
|
||||
|
||||
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.
|
@ -0,0 +1,436 @@
|
||||
package assert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CompareType int
|
||||
|
||||
const (
|
||||
compareLess CompareType = iota - 1
|
||||
compareEqual
|
||||
compareGreater
|
||||
)
|
||||
|
||||
var (
|
||||
intType = reflect.TypeOf(int(1))
|
||||
int8Type = reflect.TypeOf(int8(1))
|
||||
int16Type = reflect.TypeOf(int16(1))
|
||||
int32Type = reflect.TypeOf(int32(1))
|
||||
int64Type = reflect.TypeOf(int64(1))
|
||||
|
||||
uintType = reflect.TypeOf(uint(1))
|
||||
uint8Type = reflect.TypeOf(uint8(1))
|
||||
uint16Type = reflect.TypeOf(uint16(1))
|
||||
uint32Type = reflect.TypeOf(uint32(1))
|
||||
uint64Type = reflect.TypeOf(uint64(1))
|
||||
|
||||
float32Type = reflect.TypeOf(float32(1))
|
||||
float64Type = reflect.TypeOf(float64(1))
|
||||
|
||||
stringType = reflect.TypeOf("")
|
||||
|
||||
timeType = reflect.TypeOf(time.Time{})
|
||||
)
|
||||
|
||||
func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
|
||||
obj1Value := reflect.ValueOf(obj1)
|
||||
obj2Value := reflect.ValueOf(obj2)
|
||||
|
||||
// throughout this switch we try and avoid calling .Convert() if possible,
|
||||
// as this has a pretty big performance impact
|
||||
switch kind {
|
||||
case reflect.Int:
|
||||
{
|
||||
intobj1, ok := obj1.(int)
|
||||
if !ok {
|
||||
intobj1 = obj1Value.Convert(intType).Interface().(int)
|
||||
}
|
||||
intobj2, ok := obj2.(int)
|
||||
if !ok {
|
||||
intobj2 = obj2Value.Convert(intType).Interface().(int)
|
||||
}
|
||||
if intobj1 > intobj2 {
|
||||
return compareGreater, true
|
||||
}
|
||||
if intobj1 == intobj2 {
|
||||
return compareEqual, true
|
||||
}
|
||||
if intobj1 < intobj2 {
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Int8:
|
||||
{
|
||||
int8obj1, ok := obj1.(int8)
|
||||
if !ok {
|
||||
int8obj1 = obj1Value.Convert(int8Type).Interface().(int8)
|
||||
}
|
||||
int8obj2, ok := obj2.(int8)
|
||||
if !ok {
|
||||
int8obj2 = obj2Value.Convert(int8Type).Interface().(int8)
|
||||
}
|
||||
if int8obj1 > int8obj2 {
|
||||
return compareGreater, true
|
||||
}
|
||||
if int8obj1 == int8obj2 {
|
||||
return compareEqual, true
|
||||
}
|
||||
if int8obj1 < int8obj2 {
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Int16:
|
||||
{
|
||||
int16obj1, ok := obj1.(int16)
|
||||
if !ok {
|
||||
int16obj1 = obj1Value.Convert(int16Type).Interface().(int16)
|
||||
}
|
||||
int16obj2, ok := obj2.(int16)
|
||||
if !ok {
|
||||
int16obj2 = obj2Value.Convert(int16Type).Interface().(int16)
|
||||
}
|
||||
if int16obj1 > int16obj2 {
|
||||
return compareGreater, true
|
||||
}
|
||||
if int16obj1 == int16obj2 {
|
||||
return compareEqual, true
|
||||
}
|
||||
if int16obj1 < int16obj2 {
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Int32:
|
||||
{
|
||||
int32obj1, ok := obj1.(int32)
|
||||
if !ok {
|
||||
int32obj1 = obj1Value.Convert(int32Type).Interface().(int32)
|
||||
}
|
||||
int32obj2, ok := obj2.(int32)
|
||||
if !ok {
|
||||
int32obj2 = obj2Value.Convert(int32Type).Interface().(int32)
|
||||
}
|
||||
if int32obj1 > int32obj2 {
|
||||
return compareGreater, true
|
||||
}
|
||||
if int32obj1 == int32obj2 {
|
||||
return compareEqual, true
|
||||
}
|
||||
if int32obj1 < int32obj2 {
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Int64:
|
||||
{
|
||||
int64obj1, ok := obj1.(int64)
|
||||
if !ok {
|
||||
int64obj1 = obj1Value.Convert(int64Type).Interface().(int64)
|
||||
}
|
||||
int64obj2, ok := obj2.(int64)
|
||||
if !ok {
|
||||
int64obj2 = obj2Value.Convert(int64Type).Interface().(int64)
|
||||
}
|
||||
if int64obj1 > int64obj2 {
|
||||
return compareGreater, true
|
||||
}
|
||||
if int64obj1 == int64obj2 {
|
||||
return compareEqual, true
|
||||
}
|
||||
if int64obj1 < int64obj2 {
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Uint:
|
||||
{
|
||||
uintobj1, ok := obj1.(uint)
|
||||
if !ok {
|
||||
uintobj1 = obj1Value.Convert(uintType).Interface().(uint)
|
||||
}
|
||||
uintobj2, ok := obj2.(uint)
|
||||
if !ok {
|
||||
uintobj2 = obj2Value.Convert(uintType).Interface().(uint)
|
||||
}
|
||||
if uintobj1 > uintobj2 {
|
||||
return compareGreater, true
|
||||
}
|
||||
if uintobj1 == uintobj2 {
|
||||
return compareEqual, true
|
||||
}
|
||||
if uintobj1 < uintobj2 {
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Uint8:
|
||||
{
|
||||
uint8obj1, ok := obj1.(uint8)
|
||||
if !ok {
|
||||
uint8obj1 = obj1Value.Convert(uint8Type).Interface().(uint8)
|
||||
}
|
||||
uint8obj2, ok := obj2.(uint8)
|
||||
if !ok {
|
||||
uint8obj2 = obj2Value.Convert(uint8Type).Interface().(uint8)
|
||||
}
|
||||
if uint8obj1 > uint8obj2 {
|
||||
return compareGreater, true
|
||||
}
|
||||
if uint8obj1 == uint8obj2 {
|
||||
return compareEqual, true
|
||||
}
|
||||
if uint8obj1 < uint8obj2 {
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Uint16:
|
||||
{
|
||||
uint16obj1, ok := obj1.(uint16)
|
||||
if !ok {
|
||||
uint16obj1 = obj1Value.Convert(uint16Type).Interface().(uint16)
|
||||
}
|
||||
uint16obj2, ok := obj2.(uint16)
|
||||
if !ok {
|
||||
uint16obj2 = obj2Value.Convert(uint16Type).Interface().(uint16)
|
||||
}
|
||||
if uint16obj1 > uint16obj2 {
|
||||
return compareGreater, true
|
||||
}
|
||||
if uint16obj1 == uint16obj2 {
|
||||
return compareEqual, true
|
||||
}
|
||||
if uint16obj1 < uint16obj2 {
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Uint32:
|
||||
{
|
||||
uint32obj1, ok := obj1.(uint32)
|
||||
if !ok {
|
||||
uint32obj1 = obj1Value.Convert(uint32Type).Interface().(uint32)
|
||||
}
|
||||
uint32obj2, ok := obj2.(uint32)
|
||||
if !ok {
|
||||
uint32obj2 = obj2Value.Convert(uint32Type).Interface().(uint32)
|
||||
}
|
||||
if uint32obj1 > uint32obj2 {
|
||||
return compareGreater, true
|
||||
}
|
||||
if uint32obj1 == uint32obj2 {
|
||||
return compareEqual, true
|
||||
}
|
||||
if uint32obj1 < uint32obj2 {
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Uint64:
|
||||
{
|
||||
uint64obj1, ok := obj1.(uint64)
|
||||
if !ok {
|
||||
uint64obj1 = obj1Value.Convert(uint64Type).Interface().(uint64)
|
||||
}
|
||||
uint64obj2, ok := obj2.(uint64)
|
||||
if !ok {
|
||||
uint64obj2 = obj2Value.Convert(uint64Type).Interface().(uint64)
|
||||
}
|
||||
if uint64obj1 > uint64obj2 {
|
||||
return compareGreater, true
|
||||
}
|
||||
if uint64obj1 == uint64obj2 {
|
||||
return compareEqual, true
|
||||
}
|
||||
if uint64obj1 < uint64obj2 {
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Float32:
|
||||
{
|
||||
float32obj1, ok := obj1.(float32)
|
||||
if !ok {
|
||||
float32obj1 = obj1Value.Convert(float32Type).Interface().(float32)
|
||||
}
|
||||
float32obj2, ok := obj2.(float32)
|
||||
if !ok {
|
||||
float32obj2 = obj2Value.Convert(float32Type).Interface().(float32)
|
||||
}
|
||||
if float32obj1 > float32obj2 {
|
||||
return compareGreater, true
|
||||
}
|
||||
if float32obj1 == float32obj2 {
|
||||
return compareEqual, true
|
||||
}
|
||||
if float32obj1 < float32obj2 {
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.Float64:
|
||||
{
|
||||
float64obj1, ok := obj1.(float64)
|
||||
if !ok {
|
||||
float64obj1 = obj1Value.Convert(float64Type).Interface().(float64)
|
||||
}
|
||||
float64obj2, ok := obj2.(float64)
|
||||
if !ok {
|
||||
float64obj2 = obj2Value.Convert(float64Type).Interface().(float64)
|
||||
}
|
||||
if float64obj1 > float64obj2 {
|
||||
return compareGreater, true
|
||||
}
|
||||
if float64obj1 == float64obj2 {
|
||||
return compareEqual, true
|
||||
}
|
||||
if float64obj1 < float64obj2 {
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
case reflect.String:
|
||||
{
|
||||
stringobj1, ok := obj1.(string)
|
||||
if !ok {
|
||||
stringobj1 = obj1Value.Convert(stringType).Interface().(string)
|
||||
}
|
||||
stringobj2, ok := obj2.(string)
|
||||
if !ok {
|
||||
stringobj2 = obj2Value.Convert(stringType).Interface().(string)
|
||||
}
|
||||
if stringobj1 > stringobj2 {
|
||||
return compareGreater, true
|
||||
}
|
||||
if stringobj1 == stringobj2 {
|
||||
return compareEqual, true
|
||||
}
|
||||
if stringobj1 < stringobj2 {
|
||||
return compareLess, true
|
||||
}
|
||||
}
|
||||
// Check for known struct types we can check for compare results.
|
||||
case reflect.Struct:
|
||||
{
|
||||
// All structs enter here. We're not interested in most types.
|
||||
if !canConvert(obj1Value, timeType) {
|
||||
break
|
||||
}
|
||||
|
||||
// time.Time can compared!
|
||||
timeObj1, ok := obj1.(time.Time)
|
||||
if !ok {
|
||||
timeObj1 = obj1Value.Convert(timeType).Interface().(time.Time)
|
||||
}
|
||||
|
||||
timeObj2, ok := obj2.(time.Time)
|
||||
if !ok {
|
||||
timeObj2 = obj2Value.Convert(timeType).Interface().(time.Time)
|
||||
}
|
||||
|
||||
return compare(timeObj1.UnixNano(), timeObj2.UnixNano(), reflect.Int64)
|
||||
}
|
||||
}
|
||||
|
||||
return compareEqual, false
|
||||
}
|
||||
|
||||
// Greater asserts that the first element is greater than the second
|
||||
//
|
||||
// assert.Greater(t, 2, 1)
|
||||
// assert.Greater(t, float64(2), float64(1))
|
||||
// assert.Greater(t, "b", "a")
|
||||
func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// GreaterOrEqual asserts that the first element is greater than or equal to the second
|
||||
//
|
||||
// assert.GreaterOrEqual(t, 2, 1)
|
||||
// assert.GreaterOrEqual(t, 2, 2)
|
||||
// assert.GreaterOrEqual(t, "b", "a")
|
||||
// assert.GreaterOrEqual(t, "b", "b")
|
||||
func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// Less asserts that the first element is less than the second
|
||||
//
|
||||
// assert.Less(t, 1, 2)
|
||||
// assert.Less(t, float64(1), float64(2))
|
||||
// assert.Less(t, "a", "b")
|
||||
func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// LessOrEqual asserts that the first element is less than or equal to the second
|
||||
//
|
||||
// assert.LessOrEqual(t, 1, 2)
|
||||
// assert.LessOrEqual(t, 2, 2)
|
||||
// assert.LessOrEqual(t, "a", "b")
|
||||
// assert.LessOrEqual(t, "b", "b")
|
||||
func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// Positive asserts that the specified element is positive
|
||||
//
|
||||
// assert.Positive(t, 1)
|
||||
// assert.Positive(t, 1.23)
|
||||
func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
zero := reflect.Zero(reflect.TypeOf(e))
|
||||
return compareTwoValues(t, e, zero.Interface(), []CompareType{compareGreater}, "\"%v\" is not positive", msgAndArgs...)
|
||||
}
|
||||
|
||||
// Negative asserts that the specified element is negative
|
||||
//
|
||||
// assert.Negative(t, -1)
|
||||
// assert.Negative(t, -1.23)
|
||||
func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
zero := reflect.Zero(reflect.TypeOf(e))
|
||||
return compareTwoValues(t, e, zero.Interface(), []CompareType{compareLess}, "\"%v\" is not negative", msgAndArgs...)
|
||||
}
|
||||
|
||||
func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
e1Kind := reflect.ValueOf(e1).Kind()
|
||||
e2Kind := reflect.ValueOf(e2).Kind()
|
||||
if e1Kind != e2Kind {
|
||||
return Fail(t, "Elements should be the same type", msgAndArgs...)
|
||||
}
|
||||
|
||||
compareResult, isComparable := compare(e1, e2, e1Kind)
|
||||
if !isComparable {
|
||||
return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
|
||||
}
|
||||
|
||||
if !containsValue(allowedComparesResults, compareResult) {
|
||||
return Fail(t, fmt.Sprintf(failMessage, e1, e2), msgAndArgs...)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func containsValue(values []CompareType, value CompareType) bool {
|
||||
for _, v := range values {
|
||||
if v == value {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
//go:build go1.17
|
||||
// +build go1.17
|
||||
|
||||
// TODO: once support for Go 1.16 is dropped, this file can be
|
||||
// merged/removed with assertion_compare_go1.17_test.go and
|
||||
// assertion_compare_legacy.go
|
||||
|
||||
package assert
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Wrapper around reflect.Value.CanConvert, for compatibility
|
||||
// reasons.
|
||||
func canConvert(value reflect.Value, to reflect.Type) bool {
|
||||
return value.CanConvert(to)
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
//go:build !go1.17
|
||||
// +build !go1.17
|
||||
|
||||
// TODO: once support for Go 1.16 is dropped, this file can be
|
||||
// merged/removed with assertion_compare_go1.17_test.go and
|
||||
// assertion_compare_can_convert.go
|
||||
|
||||
package assert
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Older versions of Go does not have the reflect.Value.CanConvert
|
||||
// method.
|
||||
func canConvert(value reflect.Value, to reflect.Type) bool {
|
||||
return false
|
||||
}
|
@ -0,0 +1,753 @@
|
||||
/*
|
||||
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
|
||||
* THIS FILE MUST NOT BE EDITED BY HAND
|
||||
*/
|
||||
|
||||
package assert
|
||||
|
||||
import (
|
||||
http "net/http"
|
||||
url "net/url"
|
||||
time "time"
|
||||
)
|
||||
|
||||
// Conditionf uses a Comparison to assert a complex condition.
|
||||
func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Condition(t, comp, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Containsf asserts that the specified string, list(array, slice...) or map contains the
|
||||
// specified substring or element.
|
||||
//
|
||||
// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted")
|
||||
// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
|
||||
// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")
|
||||
func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Contains(t, s, contains, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// DirExistsf checks whether a directory exists in the given path. It also fails
|
||||
// if the path is a file rather a directory or there is an error checking whether it exists.
|
||||
func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return DirExists(t, path, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified
|
||||
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
|
||||
// the number of appearances of each of them in both lists should match.
|
||||
//
|
||||
// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")
|
||||
func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// assert.Emptyf(t, obj, "error message %s", "formatted")
|
||||
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Empty(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Equalf asserts that two objects are equal.
|
||||
//
|
||||
// assert.Equalf(t, 123, 123, "error message %s", "formatted")
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses). Function equality
|
||||
// cannot be determined and will always fail.
|
||||
func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Equal(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that it is equal to the provided error.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted")
|
||||
func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// EqualValuesf asserts that two objects are equal or convertable to the same types
|
||||
// and equal.
|
||||
//
|
||||
// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted")
|
||||
func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return EqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Errorf asserts that a function returned an error (i.e. not `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if assert.Errorf(t, err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedErrorf, err)
|
||||
// }
|
||||
func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Error(t, err, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
|
||||
// This is a wrapper for errors.As.
|
||||
func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return ErrorAs(t, err, target, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// ErrorContainsf asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that the error contains the specified substring.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// assert.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted")
|
||||
func ErrorContainsf(t TestingT, theError error, contains string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return ErrorContains(t, theError, contains, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// ErrorIsf asserts that at least one of the errors in err's chain matches target.
|
||||
// This is a wrapper for errors.Is.
|
||||
func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return ErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Eventuallyf asserts that given condition will be met in waitFor time,
|
||||
// periodically checking target function each tick.
|
||||
//
|
||||
// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
|
||||
func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Eventually(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Exactlyf asserts that two objects are equal in value and type.
|
||||
//
|
||||
// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted")
|
||||
func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Exactly(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Failf reports a failure through
|
||||
func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Fail(t, failureMessage, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// FailNowf fails test
|
||||
func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return FailNow(t, failureMessage, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Falsef asserts that the specified value is false.
|
||||
//
|
||||
// assert.Falsef(t, myBool, "error message %s", "formatted")
|
||||
func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return False(t, value, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// FileExistsf checks whether a file exists in the given path. It also fails if
|
||||
// the path points to a directory or there is an error when trying to check the file.
|
||||
func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return FileExists(t, path, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Greaterf asserts that the first element is greater than the second
|
||||
//
|
||||
// assert.Greaterf(t, 2, 1, "error message %s", "formatted")
|
||||
// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted")
|
||||
// assert.Greaterf(t, "b", "a", "error message %s", "formatted")
|
||||
func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Greater(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// GreaterOrEqualf asserts that the first element is greater than or equal to the second
|
||||
//
|
||||
// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted")
|
||||
// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted")
|
||||
// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted")
|
||||
// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted")
|
||||
func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return GreaterOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPBodyContainsf asserts that a specified handler returns a
|
||||
// body that contains a string.
|
||||
//
|
||||
// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPBodyContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPBodyNotContainsf asserts that a specified handler returns a
|
||||
// body that does not contain a string.
|
||||
//
|
||||
// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPBodyNotContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPErrorf asserts that a specified handler returns an error status code.
|
||||
//
|
||||
// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPError(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPRedirectf asserts that a specified handler returns a redirect status code.
|
||||
//
|
||||
// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPStatusCodef asserts that a specified handler returns a specified status code.
|
||||
//
|
||||
// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPStatusCode(t, handler, method, url, values, statuscode, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPSuccessf asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPSuccess(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Implementsf asserts that an object is implemented by the specified interface.
|
||||
//
|
||||
// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
|
||||
func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Implements(t, interfaceObject, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InDeltaf asserts that the two numerals are within delta of each other.
|
||||
//
|
||||
// assert.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted")
|
||||
func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InDelta(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
|
||||
func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InDeltaMapValues(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InDeltaSlicef is the same as InDelta, except it compares two slices.
|
||||
func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InDeltaSlice(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InEpsilonf asserts that expected and actual have a relative error less than epsilon
|
||||
func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InEpsilon(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
|
||||
func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// IsDecreasingf asserts that the collection is decreasing
|
||||
//
|
||||
// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted")
|
||||
// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted")
|
||||
// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
|
||||
func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return IsDecreasing(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// IsIncreasingf asserts that the collection is increasing
|
||||
//
|
||||
// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted")
|
||||
// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted")
|
||||
// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
|
||||
func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return IsIncreasing(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// IsNonDecreasingf asserts that the collection is not decreasing
|
||||
//
|
||||
// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted")
|
||||
// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted")
|
||||
// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
|
||||
func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return IsNonDecreasing(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// IsNonIncreasingf asserts that the collection is not increasing
|
||||
//
|
||||
// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted")
|
||||
// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted")
|
||||
// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
|
||||
func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return IsNonIncreasing(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// IsTypef asserts that the specified objects are of the same type.
|
||||
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return IsType(t, expectedType, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// JSONEqf asserts that two JSON strings are equivalent.
|
||||
//
|
||||
// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
|
||||
func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Lenf asserts that the specified object has specific length.
|
||||
// Lenf also fails if the object has a type that len() not accept.
|
||||
//
|
||||
// assert.Lenf(t, mySlice, 3, "error message %s", "formatted")
|
||||
func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Len(t, object, length, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Lessf asserts that the first element is less than the second
|
||||
//
|
||||
// assert.Lessf(t, 1, 2, "error message %s", "formatted")
|
||||
// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted")
|
||||
// assert.Lessf(t, "a", "b", "error message %s", "formatted")
|
||||
func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Less(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// LessOrEqualf asserts that the first element is less than or equal to the second
|
||||
//
|
||||
// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted")
|
||||
// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted")
|
||||
// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted")
|
||||
// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted")
|
||||
func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return LessOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Negativef asserts that the specified element is negative
|
||||
//
|
||||
// assert.Negativef(t, -1, "error message %s", "formatted")
|
||||
// assert.Negativef(t, -1.23, "error message %s", "formatted")
|
||||
func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Negative(t, e, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Neverf asserts that the given condition doesn't satisfy in waitFor time,
|
||||
// periodically checking the target function each tick.
|
||||
//
|
||||
// assert.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted")
|
||||
func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Never(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Nilf asserts that the specified object is nil.
|
||||
//
|
||||
// assert.Nilf(t, err, "error message %s", "formatted")
|
||||
func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Nil(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NoDirExistsf checks whether a directory does not exist in the given path.
|
||||
// It fails if the path points to an existing _directory_ only.
|
||||
func NoDirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NoDirExists(t, path, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NoErrorf asserts that a function returned no error (i.e. `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if assert.NoErrorf(t, err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedObj, actualObj)
|
||||
// }
|
||||
func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NoError(t, err, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NoFileExistsf checks whether a file does not exist in a given path. It fails
|
||||
// if the path points to an existing _file_ only.
|
||||
func NoFileExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NoFileExists(t, path, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||
// specified substring or element.
|
||||
//
|
||||
// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted")
|
||||
// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted")
|
||||
// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted")
|
||||
func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotContains(t, s, contains, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// if assert.NotEmptyf(t, obj, "error message %s", "formatted") {
|
||||
// assert.Equal(t, "two", obj[1])
|
||||
// }
|
||||
func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotEmpty(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotEqualf asserts that the specified values are NOT equal.
|
||||
//
|
||||
// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted")
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses).
|
||||
func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotEqualValuesf asserts that two objects are not equal even when converted to the same type
|
||||
//
|
||||
// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted")
|
||||
func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotEqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotErrorIsf asserts that at none of the errors in err's chain matches target.
|
||||
// This is a wrapper for errors.Is.
|
||||
func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotNilf asserts that the specified object is not nil.
|
||||
//
|
||||
// assert.NotNilf(t, err, "error message %s", "formatted")
|
||||
func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotNil(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
|
||||
//
|
||||
// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted")
|
||||
func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotPanics(t, f, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotRegexpf asserts that a specified regexp does not match a string.
|
||||
//
|
||||
// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted")
|
||||
// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
|
||||
func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotRegexp(t, rx, str, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotSamef asserts that two pointers do not reference the same object.
|
||||
//
|
||||
// assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted")
|
||||
//
|
||||
// Both arguments must be pointer variables. Pointer variable sameness is
|
||||
// determined based on the equality of both type and value.
|
||||
func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotSame(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotSubsetf asserts that the specified list(array, slice...) contains not all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
|
||||
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotSubset(t, list, subset, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotZerof asserts that i is not the zero value for its type.
|
||||
func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotZero(t, i, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Panicsf asserts that the code inside the specified PanicTestFunc panics.
|
||||
//
|
||||
// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Panics(t, f, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc
|
||||
// panics, and that the recovered panic value is an error that satisfies the
|
||||
// EqualError comparison.
|
||||
//
|
||||
// assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func PanicsWithErrorf(t TestingT, errString string, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return PanicsWithError(t, errString, f, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
|
||||
// the recovered panic value equals the expected panic value.
|
||||
//
|
||||
// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Positivef asserts that the specified element is positive
|
||||
//
|
||||
// assert.Positivef(t, 1, "error message %s", "formatted")
|
||||
// assert.Positivef(t, 1.23, "error message %s", "formatted")
|
||||
func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Positive(t, e, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Regexpf asserts that a specified regexp matches a string.
|
||||
//
|
||||
// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted")
|
||||
// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
|
||||
func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Regexp(t, rx, str, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Samef asserts that two pointers reference the same object.
|
||||
//
|
||||
// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted")
|
||||
//
|
||||
// Both arguments must be pointer variables. Pointer variable sameness is
|
||||
// determined based on the equality of both type and value.
|
||||
func Samef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Same(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Subsetf asserts that the specified list(array, slice...) contains all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
|
||||
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Subset(t, list, subset, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Truef asserts that the specified value is true.
|
||||
//
|
||||
// assert.Truef(t, myBool, "error message %s", "formatted")
|
||||
func Truef(t TestingT, value bool, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return True(t, value, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// WithinDurationf asserts that the two times are within duration delta of each other.
|
||||
//
|
||||
// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
|
||||
func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// YAMLEqf asserts that two YAML strings are equivalent.
|
||||
func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return YAMLEq(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Zerof asserts that i is the zero value for its type.
|
||||
func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Zero(t, i, append([]interface{}{msg}, args...)...)
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
{{.CommentFormat}}
|
||||
func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool {
|
||||
if h, ok := t.(tHelper); ok { h.Helper() }
|
||||
return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}})
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,5 @@
|
||||
{{.CommentWithoutT "a"}}
|
||||
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool {
|
||||
if h, ok := a.t.(tHelper); ok { h.Helper() }
|
||||
return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package assert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// isOrdered checks that collection contains orderable elements.
|
||||
func isOrdered(t TestingT, object interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool {
|
||||
objKind := reflect.TypeOf(object).Kind()
|
||||
if objKind != reflect.Slice && objKind != reflect.Array {
|
||||
return false
|
||||
}
|
||||
|
||||
objValue := reflect.ValueOf(object)
|
||||
objLen := objValue.Len()
|
||||
|
||||
if objLen <= 1 {
|
||||
return true
|
||||
}
|
||||
|
||||
value := objValue.Index(0)
|
||||
valueInterface := value.Interface()
|
||||
firstValueKind := value.Kind()
|
||||
|
||||
for i := 1; i < objLen; i++ {
|
||||
prevValue := value
|
||||
prevValueInterface := valueInterface
|
||||
|
||||
value = objValue.Index(i)
|
||||
valueInterface = value.Interface()
|
||||
|
||||
compareResult, isComparable := compare(prevValueInterface, valueInterface, firstValueKind)
|
||||
|
||||
if !isComparable {
|
||||
return Fail(t, fmt.Sprintf("Can not compare type \"%s\" and \"%s\"", reflect.TypeOf(value), reflect.TypeOf(prevValue)), msgAndArgs...)
|
||||
}
|
||||
|
||||
if !containsValue(allowedComparesResults, compareResult) {
|
||||
return Fail(t, fmt.Sprintf(failMessage, prevValue, value), msgAndArgs...)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsIncreasing asserts that the collection is increasing
|
||||
//
|
||||
// assert.IsIncreasing(t, []int{1, 2, 3})
|
||||
// assert.IsIncreasing(t, []float{1, 2})
|
||||
// assert.IsIncreasing(t, []string{"a", "b"})
|
||||
func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
return isOrdered(t, object, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// IsNonIncreasing asserts that the collection is not increasing
|
||||
//
|
||||
// assert.IsNonIncreasing(t, []int{2, 1, 1})
|
||||
// assert.IsNonIncreasing(t, []float{2, 1})
|
||||
// assert.IsNonIncreasing(t, []string{"b", "a"})
|
||||
func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
return isOrdered(t, object, []CompareType{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// IsDecreasing asserts that the collection is decreasing
|
||||
//
|
||||
// assert.IsDecreasing(t, []int{2, 1, 0})
|
||||
// assert.IsDecreasing(t, []float{2, 1})
|
||||
// assert.IsDecreasing(t, []string{"b", "a"})
|
||||
func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
return isOrdered(t, object, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs...)
|
||||
}
|
||||
|
||||
// IsNonDecreasing asserts that the collection is not decreasing
|
||||
//
|
||||
// assert.IsNonDecreasing(t, []int{1, 1, 2})
|
||||
// assert.IsNonDecreasing(t, []float{1, 2})
|
||||
// assert.IsNonDecreasing(t, []string{"a", "b"})
|
||||
func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
return isOrdered(t, object, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs...)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,45 @@
|
||||
// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system.
|
||||
//
|
||||
// Example Usage
|
||||
//
|
||||
// The following is a complete example using assert in a standard test function:
|
||||
// import (
|
||||
// "testing"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
//
|
||||
// func TestSomething(t *testing.T) {
|
||||
//
|
||||
// var a string = "Hello"
|
||||
// var b string = "Hello"
|
||||
//
|
||||
// assert.Equal(t, a, b, "The two words should be the same.")
|
||||
//
|
||||
// }
|
||||
//
|
||||
// if you assert many times, use the format below:
|
||||
//
|
||||
// import (
|
||||
// "testing"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
//
|
||||
// func TestSomething(t *testing.T) {
|
||||
// assert := assert.New(t)
|
||||
//
|
||||
// var a string = "Hello"
|
||||
// var b string = "Hello"
|
||||
//
|
||||
// assert.Equal(a, b, "The two words should be the same.")
|
||||
// }
|
||||
//
|
||||
// Assertions
|
||||
//
|
||||
// Assertions allow you to easily write test code, and are global funcs in the `assert` package.
|
||||
// All assertion functions take, as the first argument, the `*testing.T` object provided by the
|
||||
// testing framework. This allows the assertion funcs to write the failings and other details to
|
||||
// the correct place.
|
||||
//
|
||||
// Every assertion function also takes an optional string message as the final argument,
|
||||
// allowing custom error messages to be appended to the message the assertion method outputs.
|
||||
package assert
|
@ -0,0 +1,10 @@
|
||||
package assert
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// AnError is an error instance useful for testing. If the code does not care
|
||||
// about error specifics, and only needs to return the error for example, this
|
||||
// error should be used to make the test code more readable.
|
||||
var AnError = errors.New("assert.AnError general error for testing")
|
@ -0,0 +1,16 @@
|
||||
package assert
|
||||
|
||||
// Assertions provides assertion methods around the
|
||||
// TestingT interface.
|
||||
type Assertions struct {
|
||||
t TestingT
|
||||
}
|
||||
|
||||
// New makes a new Assertions object for the specified TestingT.
|
||||
func New(t TestingT) *Assertions {
|
||||
return &Assertions{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_forward.go.tmpl -include-format-funcs"
|
@ -0,0 +1,162 @@
|
||||
package assert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// httpCode is a helper that returns HTTP code of the response. It returns -1 and
|
||||
// an error if building a new request fails.
|
||||
func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) {
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
req.URL.RawQuery = values.Encode()
|
||||
handler(w, req)
|
||||
return w.Code, nil
|
||||
}
|
||||
|
||||
// HTTPSuccess asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil)
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
}
|
||||
|
||||
isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent
|
||||
if !isSuccessCode {
|
||||
Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code))
|
||||
}
|
||||
|
||||
return isSuccessCode
|
||||
}
|
||||
|
||||
// HTTPRedirect asserts that a specified handler returns a redirect status code.
|
||||
//
|
||||
// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
}
|
||||
|
||||
isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect
|
||||
if !isRedirectCode {
|
||||
Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code))
|
||||
}
|
||||
|
||||
return isRedirectCode
|
||||
}
|
||||
|
||||
// HTTPError asserts that a specified handler returns an error status code.
|
||||
//
|
||||
// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
}
|
||||
|
||||
isErrorCode := code >= http.StatusBadRequest
|
||||
if !isErrorCode {
|
||||
Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code))
|
||||
}
|
||||
|
||||
return isErrorCode
|
||||
}
|
||||
|
||||
// HTTPStatusCode asserts that a specified handler returns a specified status code.
|
||||
//
|
||||
// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501)
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
}
|
||||
|
||||
successful := code == statuscode
|
||||
if !successful {
|
||||
Fail(t, fmt.Sprintf("Expected HTTP status code %d for %q but received %d", statuscode, url+"?"+values.Encode(), code))
|
||||
}
|
||||
|
||||
return successful
|
||||
}
|
||||
|
||||
// HTTPBody is a helper that returns HTTP body of the response. It returns
|
||||
// empty string if building a new request fails.
|
||||
func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string {
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(method, url+"?"+values.Encode(), nil)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
handler(w, req)
|
||||
return w.Body.String()
|
||||
}
|
||||
|
||||
// HTTPBodyContains asserts that a specified handler returns a
|
||||
// body that contains a string.
|
||||
//
|
||||
// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
body := HTTPBody(handler, method, url, values)
|
||||
|
||||
contains := strings.Contains(body, fmt.Sprint(str))
|
||||
if !contains {
|
||||
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
|
||||
}
|
||||
|
||||
return contains
|
||||
}
|
||||
|
||||
// HTTPBodyNotContains asserts that a specified handler returns a
|
||||
// body that does not contain a string.
|
||||
//
|
||||
// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
body := HTTPBody(handler, method, url, values)
|
||||
|
||||
contains := strings.Contains(body, fmt.Sprint(str))
|
||||
if contains {
|
||||
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
|
||||
}
|
||||
|
||||
return !contains
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
// Package require implements the same assertions as the `assert` package but
|
||||
// stops test execution when a test fails.
|
||||
//
|
||||
// Example Usage
|
||||
//
|
||||
// The following is a complete example using require in a standard test function:
|
||||
// import (
|
||||
// "testing"
|
||||
// "github.com/stretchr/testify/require"
|
||||
// )
|
||||
//
|
||||
// func TestSomething(t *testing.T) {
|
||||
//
|
||||
// var a string = "Hello"
|
||||
// var b string = "Hello"
|
||||
//
|
||||
// require.Equal(t, a, b, "The two words should be the same.")
|
||||
//
|
||||
// }
|
||||
//
|
||||
// Assertions
|
||||
//
|
||||
// The `require` package have same global functions as in the `assert` package,
|
||||
// but instead of returning a boolean result they call `t.FailNow()`.
|
||||
//
|
||||
// Every assertion function also takes an optional string message as the final argument,
|
||||
// allowing custom error messages to be appended to the message the assertion method outputs.
|
||||
package require
|
@ -0,0 +1,16 @@
|
||||
package require
|
||||
|
||||
// Assertions provides assertion methods around the
|
||||
// TestingT interface.
|
||||
type Assertions struct {
|
||||
t TestingT
|
||||
}
|
||||
|
||||
// New makes a new Assertions object for the specified TestingT.
|
||||
func New(t TestingT) *Assertions {
|
||||
return &Assertions{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require_forward.go.tmpl -include-format-funcs"
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,6 @@
|
||||
{{.Comment}}
|
||||
func {{.DocInfo.Name}}(t TestingT, {{.Params}}) {
|
||||
if h, ok := t.(tHelper); ok { h.Helper() }
|
||||
if assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { return }
|
||||
t.FailNow()
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,5 @@
|
||||
{{.CommentWithoutT "a"}}
|
||||
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) {
|
||||
if h, ok := a.t.(tHelper); ok { h.Helper() }
|
||||
{{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package require
|
||||
|
||||
// TestingT is an interface wrapper around *testing.T
|
||||
type TestingT interface {
|
||||
Errorf(format string, args ...interface{})
|
||||
FailNow()
|
||||
}
|
||||
|
||||
type tHelper interface {
|
||||
Helper()
|
||||
}
|
||||
|
||||
// ComparisonAssertionFunc is a common function prototype when comparing two values. Can be useful
|
||||
// for table driven tests.
|
||||
type ComparisonAssertionFunc func(TestingT, interface{}, interface{}, ...interface{})
|
||||
|
||||
// ValueAssertionFunc is a common function prototype when validating a single value. Can be useful
|
||||
// for table driven tests.
|
||||
type ValueAssertionFunc func(TestingT, interface{}, ...interface{})
|
||||
|
||||
// BoolAssertionFunc is a common function prototype when validating a bool value. Can be useful
|
||||
// for table driven tests.
|
||||
type BoolAssertionFunc func(TestingT, bool, ...interface{})
|
||||
|
||||
// ErrorAssertionFunc is a common function prototype when validating an error value. Can be useful
|
||||
// for table driven tests.
|
||||
type ErrorAssertionFunc func(TestingT, error, ...interface{})
|
||||
|
||||
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require.go.tmpl -include-format-funcs"
|
@ -0,0 +1,50 @@
|
||||
|
||||
This project is covered by two different licenses: MIT and Apache.
|
||||
|
||||
#### MIT License ####
|
||||
|
||||
The following files were ported to Go from C files of libyaml, and thus
|
||||
are still covered by their original MIT license, with the additional
|
||||
copyright staring in 2011 when the project was ported over:
|
||||
|
||||
apic.go emitterc.go parserc.go readerc.go scannerc.go
|
||||
writerc.go yamlh.go yamlprivateh.go
|
||||
|
||||
Copyright (c) 2006-2010 Kirill Simonov
|
||||
Copyright (c) 2006-2011 Kirill Simonov
|
||||
|
||||
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.
|
||||
|
||||
### Apache License ###
|
||||
|
||||
All the remaining project files are covered by the Apache license:
|
||||
|
||||
Copyright (c) 2011-2019 Canonical Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -0,0 +1,13 @@
|
||||
Copyright 2011-2016 Canonical Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -0,0 +1,150 @@
|
||||
# YAML support for the Go language
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
The yaml package enables Go programs to comfortably encode and decode YAML
|
||||
values. It was developed within [Canonical](https://www.canonical.com) as
|
||||
part of the [juju](https://juju.ubuntu.com) project, and is based on a
|
||||
pure Go port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML)
|
||||
C library to parse and generate YAML data quickly and reliably.
|
||||
|
||||
Compatibility
|
||||
-------------
|
||||
|
||||
The yaml package supports most of YAML 1.2, but preserves some behavior
|
||||
from 1.1 for backwards compatibility.
|
||||
|
||||
Specifically, as of v3 of the yaml package:
|
||||
|
||||
- YAML 1.1 bools (_yes/no, on/off_) are supported as long as they are being
|
||||
decoded into a typed bool value. Otherwise they behave as a string. Booleans
|
||||
in YAML 1.2 are _true/false_ only.
|
||||
- Octals encode and decode as _0777_ per YAML 1.1, rather than _0o777_
|
||||
as specified in YAML 1.2, because most parsers still use the old format.
|
||||
Octals in the _0o777_ format are supported though, so new files work.
|
||||
- Does not support base-60 floats. These are gone from YAML 1.2, and were
|
||||
actually never supported by this package as it's clearly a poor choice.
|
||||
|
||||
and offers backwards
|
||||
compatibility with YAML 1.1 in some cases.
|
||||
1.2, including support for
|
||||
anchors, tags, map merging, etc. Multi-document unmarshalling is not yet
|
||||
implemented, and base-60 floats from YAML 1.1 are purposefully not
|
||||
supported since they're a poor design and are gone in YAML 1.2.
|
||||
|
||||
Installation and usage
|
||||
----------------------
|
||||
|
||||
The import path for the package is *gopkg.in/yaml.v3*.
|
||||
|
||||
To install it, run:
|
||||
|
||||
go get gopkg.in/yaml.v3
|
||||
|
||||
API documentation
|
||||
-----------------
|
||||
|
||||
If opened in a browser, the import path itself leads to the API documentation:
|
||||
|
||||
- [https://gopkg.in/yaml.v3](https://gopkg.in/yaml.v3)
|
||||
|
||||
API stability
|
||||
-------------
|
||||
|
||||
The package API for yaml v3 will remain stable as described in [gopkg.in](https://gopkg.in).
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
The yaml package is licensed under the MIT and Apache License 2.0 licenses.
|
||||
Please see the LICENSE file for details.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var data = `
|
||||
a: Easy!
|
||||
b:
|
||||
c: 2
|
||||
d: [3, 4]
|
||||
`
|
||||
|
||||
// Note: struct fields must be public in order for unmarshal to
|
||||
// correctly populate the data.
|
||||
type T struct {
|
||||
A string
|
||||
B struct {
|
||||
RenamedC int `yaml:"c"`
|
||||
D []int `yaml:",flow"`
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
t := T{}
|
||||
|
||||
err := yaml.Unmarshal([]byte(data), &t)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- t:\n%v\n\n", t)
|
||||
|
||||
d, err := yaml.Marshal(&t)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- t dump:\n%s\n\n", string(d))
|
||||
|
||||
m := make(map[interface{}]interface{})
|
||||
|
||||
err = yaml.Unmarshal([]byte(data), &m)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- m:\n%v\n\n", m)
|
||||
|
||||
d, err = yaml.Marshal(&m)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- m dump:\n%s\n\n", string(d))
|
||||
}
|
||||
```
|
||||
|
||||
This example will generate the following output:
|
||||
|
||||
```
|
||||
--- t:
|
||||
{Easy! {2 [3 4]}}
|
||||
|
||||
--- t dump:
|
||||
a: Easy!
|
||||
b:
|
||||
c: 2
|
||||
d: [3, 4]
|
||||
|
||||
|
||||
--- m:
|
||||
map[a:Easy! b:map[c:2 d:[3 4]]]
|
||||
|
||||
--- m dump:
|
||||
a: Easy!
|
||||
b:
|
||||
c: 2
|
||||
d:
|
||||
- 3
|
||||
- 4
|
||||
```
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue