diff --git a/README.md b/README.md index 755be79..0b64622 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,158 @@ -# Lighthouse +# **🚢 Lighthouse GraphQL Framework** + +[English](https://github.com/light-speak/lighthouse/blob/main/README.md) | [中文](https://github.com/light-speak/lighthouse/blob/main/README_zh.md) [![CI](https://github.com/light-speak/lighthouse/actions/workflows/main.yml/badge.svg)](https://github.com/light-speak/lighthouse/actions/workflows/main.yml) [![codecov](https://codecov.io/gh/light-speak/lighthouse/branch/main/graph/badge.svg)](https://codecov.io/gh/light-speak/lighthouse) [![Go Report Card](https://goreportcard.com/badge/github.com/light-speak/lighthouse)](https://goreportcard.com/report/github.com/light-speak/lighthouse) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +`Lighthouse` is a feature-rich self-developed GraphQL framework designed to simplify GraphQL service development based on microservice architecture. The framework integrates a logging system (using `zeroLog`), supports Elasticsearch, file logging mode and Redis caching, while featuring a flexible directive system and powerful custom configuration capabilities. The framework currently has built-in support for `gorm` and will expand to more ORM options in the future. + +## Features + +- **Microservice Architecture Support**: Adopts independent microservice mode, doesn't support GraphQL Federation but manages microservices through a custom service registry. +- **Custom Directives**: Supports rich custom directives for dynamic queries, filtering, relationships, and more. +- **Extensibility**: Supports various GraphQL file structures through configuration files to flexibly meet different project needs. +- **ORM Integration**: Currently supports `gorm`, with plans to add support for more ORM libraries. +- **Logging and Cache Integration**: Integrates `zeroLog` logging system, supports Elasticsearch, file logging, and Redis caching. + +## Quick Start + +### Installation + +1. **Install using `go install`** + + ```bash + go install github.com/light-speak/lighthouse@latest + ``` + +2. **Create a new project** + + ```bash + lighthouse generate:init + ``` + +### Configuration File (lighthouse.yml) + +`lighthouse.yml` is the core configuration file for `lighthouse`, used to specify GraphQL Schema paths, file extensions, ORM settings, etc. Here's an example configuration: + +```yaml +# lighthouse.yml + +schema: + ext: + - graphql # Supported file extensions + - graphqls + path: + - schema # Path to GraphQL Schema files + model: + orm: gorm # ORM configuration, currently supports gorm +``` + +- `schema.ext`: Specifies Schema file extensions, can be `.graphql` or `.graphqls`. +- `schema.path`: Defines the path to Schema files, framework will automatically load all files in this path. +- `model.orm`: Currently supports `gorm` as the ORM library. + +### Directory Structure + +The `example` project structure is as follows: + +```plaintext +. +├── cmd # CLI related code +│ ├── cmd.go # Main command entry +│ ├── migrate +│ │ └── migrate.go # Database migration logic +│ └── start +│ └── start.go # Service start entry +├── models # Data model definitions +│ ├── enum.go # Enum type definitions +│ ├── input.go # Input type definitions +│ ├── interface.go # Interface definitions +│ ├── model.go # Model structures +│ └── response.go # Response data structures +├── repo # Database operation encapsulation +│ └── repo.go +├── resolver # GraphQL resolvers +│ ├── mutation.go # Mutation resolver +│ ├── query.go # Query resolver +│ └── resolver.go # Resolver main entry +├── schema # GraphQL Schema files +│ └── user.graphql # Example Schema file +└── service # Service logic + └── service.go +``` + +### Next Steps + +Add your custom schema files in the `schema` directory, then run the following command to generate corresponding code: + +```bash +lighthouse generate:schema +``` + +### Using Directives + +In `lighthouse`, you can use the following directives in your GraphQL Schema: + +- **@skip / @include**: Conditional query directives for dynamically controlling field inclusion in responses. +- **@enum**: For defining enum type fields, currently only supports `int8` type. +- **@paginate / @find / @first**: For pagination, finding, and getting first result queries. +- **@in / @eq / @neq / @gt / @gte / @lt / @lte / @like / @notIn**: Parameter filtering directives supporting various comparison operators. +- **@belongsTo / @hasMany / @hasOne / @morphTo / @morphToMany**: Relationship mapping directives for defining model relationships. +- **@index / @unique**: Create index or add unique constraint for fields. +- **@defaultString / @defaultInt**: Set default values for fields. +- **@tag**: For marking additional field attributes. +- **@model**: Mark type as database model. +- **@softDeleteModel**: Mark type as database model with soft delete support. +- **@order**: For sorting query results. +- **@cache**: For caching query results to improve response speed. + +### Example Code + +Here's an example query using the `@paginate` directive for user data pagination: + +```graphql +type Query { + users: [User] @paginate(scopes: ["active"]) +} + +type User @model(name: "UserModel") { + id: ID! + name: String! + age: Int + posts: [Post] @hasMany(relation: "Post", foreignKey: "user_id") +} +``` -## Status: Under Development 🚧 +## Extension and Customization -Lighthouse is currently under active development. Stay tuned for exciting updates! +`lighthouse` provides flexible extension interfaces, you can: -Coming soon... +- **Add Custom Directives**: Write your own directives to extend framework functionality. +- **Support Other ORMs**: Add support for other ORM libraries by referencing the `gorm` integration approach. +## Development Plan -## License +| 🚀 Feature Category | ✨ Feature Description | 📅 Status | +|-------------------|---------------------|-----------| +| 🛠️ Custom Directives | Add support for custom directives | ✅ Completed | +| 📊 Query Directives | Add @find and @first annotations for query support | ✅ Completed | +| 🔍 Query & Filtering | Add date range filtering directives | ✅ Completed | +| | Add string matching directives | ✅ Completed | +| | Add dynamic sorting directives | ✅ Completed | +| 📊 Pagination | Add @paginate annotation for pagination support | ✅ Completed | +| 📜 Conditional Query | Add @skip and @include conditional query directives | 🚧 In Progress | +| 📚 Relationship Mapping | Add @morphTo, @morphToMany, @hasOne, @manyToMany | 🚧 In Progress | +| 🔧 Microservice Management | Add microservice registry | ⏳ Planned | +| 💾 Cache Integration | Integrate Redis as cache support | ✅ Completed | +| 📝 Logging System | Integrate zeroLog system, support Elasticsearch and file logging | ✅ Completed | +| 🔄 Cache Directive | Add @cache directive to support query result caching | 🚧 In Progress | +| 🔀 Sorting Directive | Add @order directive to support query result sorting | 🚧 In Progress | +| 🗄️ ORM Support | Extend support for other ORMs like `ent`, `sqlc` | ⏳ Planned | +| 📑 Doc Generation | Auto-generate GraphQL Schema documentation | ⏳ Planned | +| 📦 Plugin Support | Provide plugin system for community contributions | ⏳ Planned | +| 🌐 Frontend Tools | Develop Apollo Studio-like frontend for query testing | ⏳ Planned | +| 📊 Performance Tracking | Support performance tracking for fields and services | ⏳ Planned | -This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details. \ No newline at end of file diff --git a/README_zh.md b/README_zh.md new file mode 100644 index 0000000..3c10780 --- /dev/null +++ b/README_zh.md @@ -0,0 +1,156 @@ +# **🚢 Lighthouse GraphQL Framework** + +[English](https://github.com/light-speak/lighthouse/blob/main/README.md) | [中文](https://github.com/light-speak/lighthouse/blob/main/README_zh.md) + +[![CI](https://github.com/light-speak/lighthouse/actions/workflows/main.yml/badge.svg)](https://github.com/light-speak/lighthouse/actions/workflows/main.yml) +[![codecov](https://codecov.io/gh/light-speak/lighthouse/branch/main/graph/badge.svg)](https://codecov.io/gh/light-speak/lighthouse) +[![Go Report Card](https://goreportcard.com/badge/github.com/light-speak/lighthouse)](https://goreportcard.com/report/github.com/light-speak/lighthouse) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +`Lighthouse` 是一个功能丰富的自研 GraphQL 框架,旨在简化基于微服务架构的 GraphQL 服务开发。框架集成了日志系统(使用 `zeroLog`),支持 Elasticsearch、文件日志模式和 Redis 缓存,同时具有灵活的指令系统和强大的自定义配置能力。框架目前内置对 `gorm` 的支持,未来还将扩展更多的 ORM 选项。 + +## 特性 + +- **微服务架构支持**:采用独立的微服务模式,不支持 GraphQL Federation,而是通过自定义的服务注册中心来管理微服务。 +- **自定义指令**:支持丰富的自定义指令,可以实现动态查询、过滤、关联等操作。 +- **可扩展性**:通过配置文件支持多种 GraphQL 文件结构,灵活满足不同项目需求。 +- **ORM 集成**:目前支持 `gorm`,未来会增加对更多 ORM 库的支持。 +- **日志与缓存集成**:集成了 `zeroLog` 日志系统,支持 Elasticsearch、文件日志和 Redis 缓存。 + +## 快速开始 + +### 安装 + +1. **使用 `go install` 安装** + + ```bash + go install github.com/light-speak/lighthouse@latest + ``` + +2. **创建新项目** + + ```bash + lighthouse generate:init + ``` + +### 配置文件 (lighthouse.yml) + +`lighthouse.yml` 是 `lighthouse` 的核心配置文件,用于指定 GraphQL Schema 路径、文件扩展名、ORM 设置等。以下是示例配置: + +```yaml +# lighthouse.yml + +schema: + ext: + - graphql # 支持的文件扩展名 + - graphqls + path: + - schema # GraphQL Schema 文件所在路径 + model: + orm: gorm # ORM 配置,当前支持 gorm +``` + +- `schema.ext`:指定 Schema 文件的扩展名,可以是 `.graphql` 或 `.graphqls`。 +- `schema.path`:定义 Schema 文件的路径,框架将自动加载该路径下的所有文件。 +- `model.orm`:当前支持 `gorm` 作为 ORM 库。 + +### 目录结构 + +`example` 项目结构如下: + +```plaintext +. +├── cmd # CLI 相关代码 +│ ├── cmd.go # 主命令入口 +│ ├── migrate +│ │ └── migrate.go # 数据库迁移逻辑 +│ └── start +│ └── start.go # 启动服务入口 +├── models # 数据模型相关定义 +│ ├── enum.go # 枚举类型定义 +│ ├── input.go # 输入类型定义 +│ ├── interface.go # 接口定义 +│ ├── model.go # 模型结构 +│ └── response.go # 响应数据结构 +├── repo # 数据库操作封装 +│ └── repo.go +├── resolver # GraphQL 解析器 +│ ├── mutation.go # Mutation 解析 +│ ├── query.go # Query 解析 +│ └── resolver.go # Resolver 主入口 +├── schema # GraphQL Schema 文件 +│ └── user.graphql # 示例 Schema 文件 +└── service # 服务逻辑 + └── service.go +``` + +### 接下来步骤 + +在 `schema` 目录下填入自定义的 `schema` 文件,然后执行以下命令生成对应的代码: + +lighthouse generate:schema + +### 使用指令 + +在 `lighthouse` 中,你可以在 GraphQL Schema 中使用以下指令: + +- **@skip / @include**:条件查询指令,用于动态控制字段是否包含在响应中。 +- **@enum**:用于定义枚举类型字段,目前只支持 `int8` 类型。 +- **@paginate / @find / @first**:用于分页、查找和获取第一个结果的查询。 +- **@in / @eq / @neq / @gt / @gte / @lt / @lte / @like / @notIn**:用于参数过滤的指令,支持各种比较运算符。 +- **@belongsTo / @hasMany / @hasOne / @morphTo / @morphToMany**:关系映射指令,用于定义模型之间的关系。 +- **@index / @unique**:为字段创建索引或添加唯一约束。 +- **@defaultString / @defaultInt**:为字段设置默认值。 +- **@tag**:用于标记字段的附加属性。 +- **@model**:标记类型为数据库模型。 +- **@softDeleteModel**:标记类型为数据库模型,并支持软删除功能。 +- **@order**:用于对查询结果进行排序。 +- **@cache**:用于缓存查询结果,提高响应速度。 + +### 示例代码 + +以下是一个示例查询,在获取用户数据时使用了 `@paginate` 指令进行分页: + +```graphql +type Query { + users: [User] @paginate(scopes: ["active"]) +} + +type User @model(name: "UserModel") { + id: ID! + name: String! + age: Int + posts: [Post] @hasMany(relation: "Post", foreignKey: "user_id") +} +``` + +## 扩展与自定义 + +`lighthouse` 提供了灵活的扩展接口,你可以: + +- **添加自定义指令**:编写自己的指令来扩展框架的功能。 +- **支持其他 ORM**:参考 `gorm` 集成方式,添加对其他 ORM 库的支持。 + +## 开发计划 + +| 🚀 功能分类 | ✨ 功能描述 | 📅 状态 | +| -------------- | -------------------------------------------------- | -------- | +| 🛠️ 自定义指令 | 添加自定义指令的支持 | ✅ 已完成 | +| 📊 查询指令 | 添加 @find 和 @first 注解以支持查询功能 | ✅ 已完成 | +| 🔍 查询与过滤 | 添加日期范围过滤指令 | ✅ 已完成 | +| | 添加字符串匹配指令 | ✅ 已完成 | +| | 添加动态排序指令 | ✅ 已完成 | +| 📊 分页指令 | 添加 @paginate 注解以支持分页功能 | ✅ 已完成 | +| 📜 条件查询指令 | 添加 @skip 和 @include 条件查询指令 | 🚧 进行中 | +| 📚 关系映射指令 | 添加 @morphTo, @morphToMany, @hasOne, @manyToMany | 🚧 进行中 | +| 🔧 微服务管理 | 添加微服务注册中心 | ⏳ 计划中 | +| 💾 缓存集成 | 集成 Redis 作为缓存支持 | ✅ 已完成 | +| 📝 日志系统 | 集成 zeroLog 日志系统,支持 Elasticsearch 和文件日志 | ✅ 已完成 | +| 🔄 缓存指令 | 添加 @cache 指令来支持缓存查询结果 | 🚧 进行中 | +| 🔀 排序指令 | 添加 @order 指令来支持对查询结果进行排序 | 🚧 进行中 | +| 🗄️ ORM 支持 | 扩展对其他 ORM 的支持,如 `ent`、`sqlc` 等 | ⏳ 计划中 | +| 📑 文档生成工具 | 自动化生成 GraphQL Schema 文档 | ⏳ 计划中 | +| 📦 插件支持 | 提供插件系统,支持社区贡献和功能扩展 | ⏳ 计划中 | +| 🌐 前端工具 | 开发类似 Apollo Studio 的前端,用于查询与测试 | ⏳ 计划中 | +| 📊 性能追踪 | 支持每个字段和每个服务的性能追踪,以优化 GraphQL 查询性能 | ⏳ 计划中 | + diff --git a/command/cli/generate/initialize/one/tpl/ide-helper.tpl b/command/cli/generate/initialize/one/tpl/ide-helper.tpl index c7e06a4..cfc1d35 100644 --- a/command/cli/generate/initialize/one/tpl/ide-helper.tpl +++ b/command/cli/generate/initialize/one/tpl/ide-helper.tpl @@ -2,8 +2,44 @@ type Query type Mutation type Subscription - -directive @paginate(scopes: [String!]) on FIELD_DEFINITION directive @skip(if: Boolean!) on FIELD_DEFINITION directive @include(if: Boolean!) on FIELD_DEFINITION -directive @enum(value: Int!) on FIELD_DEFINITION \ No newline at end of file +directive @deprecated(reason: String) on FIELD_DEFINITION + +# enum 枚举 +directive @enum(value: Int!) on FIELD_DEFINITION + + +# model +directive @index(name: String) on FIELD_DEFINITION +directive @tag(name: String!, value: String!) on FIELD_DEFINITION +directive @defaultString(value: String!) on FIELD_DEFINITION +directive @defaultInt(value: Int!) on FIELD_DEFINITION +directive @unique on FIELD_DEFINITION +directive @model(name: String) on OBJECT +directive @softDeleteModel(name: String) on OBJECT + + +# query returnType +directive @paginate(scopes: [String!]) on FIELD_DEFINITION +directive @find(scopes: [String!]) on FIELD_DEFINITION +directive @first(scopes: [String!]) on FIELD_DEFINITION + + +# argument filter +directive @in(field: String) on ARGUMENT_DEFINITION +directive @eq(field: String) on ARGUMENT_DEFINITION +directive @neq(field: String) on ARGUMENT_DEFINITION +directive @gt(field: String) on ARGUMENT_DEFINITION +directive @gte(field: String) on ARGUMENT_DEFINITION +directive @lt(field: String) on ARGUMENT_DEFINITION +directive @lte(field: String) on ARGUMENT_DEFINITION +directive @like(field: String) on ARGUMENT_DEFINITION +directive @notIn(field: String) on ARGUMENT_DEFINITION + +# relation +directive @belongsTo(relation: String, foreignKey: String, reference: String) on FIELD_DEFINITION +directive @hasMany(relation: String, foreignKey: String, reference: String) on FIELD_DEFINITION +directive @hasOne(relation: String, foreignKey: String, reference: String) on FIELD_DEFINITION +directive @morphTo(morphType: String, morphKey: String, reference: String) on FIELD_DEFINITION +directive @morphToMany(relation: String!, morphType: String, morphKey: String, reference: String) on FIELD_DEFINITION diff --git a/example/user/ide-helper.graphql b/example/user/ide-helper.graphql index 3d66e7f..147daf1 100644 --- a/example/user/ide-helper.graphql +++ b/example/user/ide-helper.graphql @@ -5,17 +5,43 @@ type Subscription directive @skip(if: Boolean!) on FIELD_DEFINITION directive @include(if: Boolean!) on FIELD_DEFINITION +directive @deprecated(reason: String) on FIELD_DEFINITION -# ORM directive -directive @paginate(scopes: [String!]) on FIELD_DEFINITION +# enum 枚举 directive @enum(value: Int!) on FIELD_DEFINITION + + +# model directive @index(name: String) on FIELD_DEFINITION -directive @tag(name: String!, value: String!) repeatable on FIELD_DEFINITION +directive @tag(name: String!, value: String!) on FIELD_DEFINITION directive @defaultString(value: String!) on FIELD_DEFINITION directive @defaultInt(value: Int!) on FIELD_DEFINITION directive @unique on FIELD_DEFINITION directive @model(name: String) on OBJECT directive @softDeleteModel(name: String) on OBJECT -directive @deprecated(reason: String) on FIELD_DEFINITION -directive @in(fields: [String!]!) on ARGUMENT_DEFINITION -directive @find(scopes: [String!]) on FIELD_DEFINITION \ No newline at end of file + + +# query returnType +directive @paginate(scopes: [String!]) on FIELD_DEFINITION +directive @find(scopes: [String!]) on FIELD_DEFINITION +directive @first(scopes: [String!]) on FIELD_DEFINITION + + +# argument filter +directive @in(field: String) on ARGUMENT_DEFINITION +directive @eq(field: String) on ARGUMENT_DEFINITION +directive @neq(field: String) on ARGUMENT_DEFINITION +directive @gt(field: String) on ARGUMENT_DEFINITION +directive @gte(field: String) on ARGUMENT_DEFINITION +directive @lt(field: String) on ARGUMENT_DEFINITION +directive @lte(field: String) on ARGUMENT_DEFINITION +directive @like(field: String) on ARGUMENT_DEFINITION +directive @notIn(field: String) on ARGUMENT_DEFINITION + +# relation +directive @belongsTo(relation: String, foreignKey: String, reference: String) on FIELD_DEFINITION +directive @hasMany(relation: String, foreignKey: String, reference: String) on FIELD_DEFINITION +directive @hasOne(relation: String, foreignKey: String, reference: String) on FIELD_DEFINITION +directive @morphTo(morphType: String, morphKey: String, reference: String) on FIELD_DEFINITION +directive @morphToMany(relation: String!, morphType: String, morphKey: String, reference: String) on FIELD_DEFINITION +directive @manyToMany(relation: String!, pivot: String, foreignKey: String, reference: String) on FIELD_DEFINITION diff --git a/example/user/models/interface.go b/example/user/models/interface.go index 69dcbaf..cd48e55 100644 --- a/example/user/models/interface.go +++ b/example/user/models/interface.go @@ -4,17 +4,17 @@ package models -type HasName interface { - IsHasName() - GetName() string -} - type Userable interface { IsUserable() GetUser() User GetUserId() int64 } +type HasName interface { + IsHasName() + GetName() string +} + type Commentable interface { IsCommentable() } diff --git a/example/user/models/model.go b/example/user/models/model.go index 371959c..1178706 100644 --- a/example/user/models/model.go +++ b/example/user/models/model.go @@ -4,62 +4,90 @@ package models import "github.com/light-speak/lighthouse/graphql/model" -type User struct { +type Article struct { model.Model - Name string `json:"name" gorm:"index;type:varchar(255)" ` - MyPosts *[]Post `json:"my_posts" gorm:"comment:五二零" ` + Name string `json:"name" gorm:"type:varchar(255)" ` + Content string `json:"content" gorm:"type:varchar(255)" ` } -func (*User) IsModel() bool { return true } -func (*User) IsHasName() bool { return true } -func (this *User) GetName() string { return this.Name } -func (*User) TableName() string { return "users" } -func (*User) TypeName() string { return "user" } +func (*Article) IsModel() bool { return true } +func (*Article) TableName() string { return "articles" } +func (*Article) TypeName() string { return "article" } +var ArticleEnumFields = map[string]func(interface{}) interface{} { +} type Post struct { model.ModelSoftDelete Title string `json:"title" gorm:"index;type:varchar(255)" ` Content string `json:"content" gorm:"type:varchar(255)" ` - TagId int64 `json:"tag_id" ` BackId int64 `json:"back_id" ` - Enum TestEnum `json:"enum" ` - UserId int64 `json:"user_id" gorm:"index" ` IsBool bool `json:"is_bool" gorm:"default:false" ` User *User `json:"user" ` + UserId int64 `json:"user_id" gorm:"index" ` + TagId int64 `json:"tag_id" ` + Enum TestEnum `json:"enum" ` } func (*Post) IsModel() bool { return true } func (*Post) TableName() string { return "posts" } func (*Post) TypeName() string { return "post" } +var PostEnumFields = map[string]func(interface{}) interface{} { + "enum": func(value interface{}) interface{} { + switch v := value.(type) { + case int64: + return TestEnum(v) + case int8: + return TestEnum(v) + default: + return v + } + }, +} type Comment struct { model.Model Content string `json:"content" gorm:"type:varchar(255)" ` CommentableId int64 `json:"commentable_id" gorm:"index:commentable" ` CommentableType CommentableType `gorm:"index:commentable" json:"commentable_type" ` - Commentable interface{} `gorm:"-" json:"commentable" ` + Commentable interface{} `json:"commentable" gorm:"-" ` } func (*Comment) IsModel() bool { return true } func (*Comment) TableName() string { return "comments" } func (*Comment) TypeName() string { return "comment" } +var CommentEnumFields = map[string]func(interface{}) interface{} { + "commentableType": func(value interface{}) interface{} { + switch v := value.(type) { + case int64: + return CommentableType(v) + case int8: + return CommentableType(v) + default: + return v + } + }, +} -type Article struct { +type User struct { model.Model - Name string `json:"name" gorm:"type:varchar(255)" ` - Content string `json:"content" gorm:"type:varchar(255)" ` + Name string `json:"name" gorm:"index;type:varchar(255)" ` + MyPosts *[]Post `json:"my_posts" gorm:"comment:五二零" ` } -func (*Article) IsModel() bool { return true } -func (*Article) TableName() string { return "articles" } -func (*Article) TypeName() string { return "article" } +func (*User) IsModel() bool { return true } +func (*User) IsHasName() bool { return true } +func (this *User) GetName() string { return this.Name } +func (*User) TableName() string { return "users" } +func (*User) TypeName() string { return "user" } +var UserEnumFields = map[string]func(interface{}) interface{} { +} func Migrate() error { return model.GetDB().AutoMigrate( - &User{}, + &Article{}, &Post{}, &Comment{}, - &Article{}, + &User{}, ) } \ No newline at end of file diff --git a/example/user/models/response.go b/example/user/models/response.go index ffa2547..3791b58 100644 --- a/example/user/models/response.go +++ b/example/user/models/response.go @@ -4,22 +4,22 @@ package models import "github.com/light-speak/lighthouse/graphql/model" -type UserPaginateResponse struct { - Data *[]*User `json:"data" ` +type PostPaginateResponse struct { + Data *[]*Post `json:"data" ` PaginateInfo *model.PaginateInfo `json:"paginate_info" ` } type LoginResponse struct { + User *User `json:"user" ` Token string `json:"token" gorm:"type:varchar(255)" ` Authorization string `json:"authorization" gorm:"type:varchar(255)" ` - User *User `json:"user" ` } -type Test struct { - Test string `json:"test" gorm:"type:varchar(255)" ` +type UserPaginateResponse struct { + Data *[]*User `json:"data" ` + PaginateInfo *model.PaginateInfo `json:"paginate_info" ` } -type PostPaginateResponse struct { - Data *[]*Post `json:"data" ` - PaginateInfo *model.PaginateInfo `json:"paginate_info" ` +type Test struct { + Test string `json:"test" gorm:"type:varchar(255)" ` } diff --git a/example/user/repo/repo.go b/example/user/repo/repo.go index 493604f..4aa3a34 100644 --- a/example/user/repo/repo.go +++ b/example/user/repo/repo.go @@ -2,51 +2,53 @@ package repo import ( - "github.com/light-speak/lighthouse/graphql/ast" "github.com/light-speak/lighthouse/context" - "gorm.io/gorm" - "user/models" "github.com/light-speak/lighthouse/graphql/model" + "user/models" + "gorm.io/gorm" ) -func Provide__User() map[string]*ast.Relation { return map[string]*ast.Relation{"created_at": {},"id": {},"myPosts": {Name: "post", RelationType: ast.RelationTypeHasMany, ForeignKey: "user_id", Reference: "id"},"name": {},"updated_at": {},}} -func Load__User(ctx *context.Context, key int64, field string) (map[string]interface{}, error) { - return model.GetLoader[int64](model.GetDB(), "users", field).Load(key) +func Load__Article(ctx *context.Context, key int64, field string) (map[string]interface{}, error) { + return model.GetLoader[int64](model.GetDB(), "articles", field).Load(key) } -func LoadList__User(ctx *context.Context, key int64, field string) ([]map[string]interface{}, error) { - return model.GetLoader[int64](model.GetDB(), "users", field).LoadList(key) +func LoadList__Article(ctx *context.Context, key int64, field string) ([]map[string]interface{}, error) { + return model.GetLoader[int64](model.GetDB(), "articles", field).LoadList(key) } -func Query__User(scopes ...func(db *gorm.DB) *gorm.DB) *gorm.DB { - return model.GetDB().Model(&models.User{}).Scopes(scopes...) +func Query__Article(scopes ...func(db *gorm.DB) *gorm.DB) *gorm.DB { + return model.GetDB().Model(&models.Article{}).Scopes(scopes...) } -func First__User(ctx *context.Context, data map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) (map[string]interface{}, error) { +func First__Article(ctx *context.Context, data map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) (map[string]interface{}, error) { var err error if data == nil { data = make(map[string]interface{}) - err = Query__User().Scopes(scopes...).First(data).Error + err = Query__Article().Scopes(scopes...).First(data).Error if err != nil { return nil, err } } + for key, value := range data { + if fn, ok := models.ArticleEnumFields[key]; ok { + data[key] = fn(value) + } + } return data, nil } -func List__User(ctx *context.Context, datas []map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) ([]map[string]interface{}, error) { +func List__Article(ctx *context.Context, datas []map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) ([]map[string]interface{}, error) { var err error if datas == nil { datas = make([]map[string]interface{}, 0) - err = Query__User().Scopes(scopes...).Find(&datas).Error + err = Query__Article().Scopes(scopes...).Find(&datas).Error if err != nil { return nil, err } } return datas, nil } -func Count__User(scopes ...func(db *gorm.DB) *gorm.DB) (int64, error) { +func Count__Article(scopes ...func(db *gorm.DB) *gorm.DB) (int64, error) { var count int64 - err := Query__User().Scopes(scopes...).Count(&count).Error + err := Query__Article().Scopes(scopes...).Count(&count).Error return count, err } -func Provide__Post() map[string]*ast.Relation { return map[string]*ast.Relation{"BackId": {},"IsBool": {},"content": {},"created_at": {},"deleted_at": {},"enum": {},"id": {},"tagId": {},"title": {},"updated_at": {},"user": {Name: "user", RelationType: ast.RelationTypeBelongsTo, ForeignKey: "user_id", Reference: "id"},"userId": {},}} func Load__Post(ctx *context.Context, key int64, field string) (map[string]interface{}, error) { return model.GetLoader[int64](model.GetDB(), "posts", field).Load(key) } @@ -65,6 +67,11 @@ func First__Post(ctx *context.Context, data map[string]interface{}, scopes ...fu return nil, err } } + for key, value := range data { + if fn, ok := models.PostEnumFields[key]; ok { + data[key] = fn(value) + } + } return data, nil } func List__Post(ctx *context.Context, datas []map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) ([]map[string]interface{}, error) { @@ -83,7 +90,6 @@ func Count__Post(scopes ...func(db *gorm.DB) *gorm.DB) (int64, error) { err := Query__Post().Scopes(scopes...).Count(&count).Error return count, err } -func Provide__Comment() map[string]*ast.Relation { return map[string]*ast.Relation{"commentable": {Name: "", RelationType: ast.RelationTypeMorphTo, ForeignKey: "", Reference: "id"},"commentableId": {},"commentableType": {},"content": {},"created_at": {},"id": {},"updated_at": {},}} func Load__Comment(ctx *context.Context, key int64, field string) (map[string]interface{}, error) { return model.GetLoader[int64](model.GetDB(), "comments", field).Load(key) } @@ -102,6 +108,11 @@ func First__Comment(ctx *context.Context, data map[string]interface{}, scopes .. return nil, err } } + for key, value := range data { + if fn, ok := models.CommentEnumFields[key]; ok { + data[key] = fn(value) + } + } return data, nil } func List__Comment(ctx *context.Context, datas []map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) ([]map[string]interface{}, error) { @@ -120,51 +131,55 @@ func Count__Comment(scopes ...func(db *gorm.DB) *gorm.DB) (int64, error) { err := Query__Comment().Scopes(scopes...).Count(&count).Error return count, err } -func Provide__Article() map[string]*ast.Relation { return map[string]*ast.Relation{"content": {},"created_at": {},"id": {},"name": {},"updated_at": {},}} -func Load__Article(ctx *context.Context, key int64, field string) (map[string]interface{}, error) { - return model.GetLoader[int64](model.GetDB(), "articles", field).Load(key) +func Load__User(ctx *context.Context, key int64, field string) (map[string]interface{}, error) { + return model.GetLoader[int64](model.GetDB(), "users", field).Load(key) } -func LoadList__Article(ctx *context.Context, key int64, field string) ([]map[string]interface{}, error) { - return model.GetLoader[int64](model.GetDB(), "articles", field).LoadList(key) +func LoadList__User(ctx *context.Context, key int64, field string) ([]map[string]interface{}, error) { + return model.GetLoader[int64](model.GetDB(), "users", field).LoadList(key) } -func Query__Article(scopes ...func(db *gorm.DB) *gorm.DB) *gorm.DB { - return model.GetDB().Model(&models.Article{}).Scopes(scopes...) +func Query__User(scopes ...func(db *gorm.DB) *gorm.DB) *gorm.DB { + return model.GetDB().Model(&models.User{}).Scopes(scopes...) } -func First__Article(ctx *context.Context, data map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) (map[string]interface{}, error) { +func First__User(ctx *context.Context, data map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) (map[string]interface{}, error) { var err error if data == nil { data = make(map[string]interface{}) - err = Query__Article().Scopes(scopes...).First(data).Error + err = Query__User().Scopes(scopes...).First(data).Error if err != nil { return nil, err } } + for key, value := range data { + if fn, ok := models.UserEnumFields[key]; ok { + data[key] = fn(value) + } + } return data, nil } -func List__Article(ctx *context.Context, datas []map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) ([]map[string]interface{}, error) { +func List__User(ctx *context.Context, datas []map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) ([]map[string]interface{}, error) { var err error if datas == nil { datas = make([]map[string]interface{}, 0) - err = Query__Article().Scopes(scopes...).Find(&datas).Error + err = Query__User().Scopes(scopes...).Find(&datas).Error if err != nil { return nil, err } } return datas, nil } -func Count__Article(scopes ...func(db *gorm.DB) *gorm.DB) (int64, error) { +func Count__User(scopes ...func(db *gorm.DB) *gorm.DB) (int64, error) { var count int64 - err := Query__Article().Scopes(scopes...).Count(&count).Error + err := Query__User().Scopes(scopes...).Count(&count).Error return count, err } func init() { - model.AddQuickFirst("User", First__User) - model.AddQuickList("User", List__User) - model.AddQuickLoad("User", Load__User) - model.AddQuickLoadList("User", LoadList__User) - model.AddQuickCount("User", Count__User) + model.AddQuickFirst("Article", First__Article) + model.AddQuickList("Article", List__Article) + model.AddQuickLoad("Article", Load__Article) + model.AddQuickLoadList("Article", LoadList__Article) + model.AddQuickCount("Article", Count__Article) model.AddQuickFirst("Post", First__Post) model.AddQuickList("Post", List__Post) model.AddQuickLoad("Post", Load__Post) @@ -175,9 +190,9 @@ func init() { model.AddQuickLoad("Comment", Load__Comment) model.AddQuickLoadList("Comment", LoadList__Comment) model.AddQuickCount("Comment", Count__Comment) - model.AddQuickFirst("Article", First__Article) - model.AddQuickList("Article", List__Article) - model.AddQuickLoad("Article", Load__Article) - model.AddQuickLoadList("Article", LoadList__Article) - model.AddQuickCount("Article", Count__Article) + model.AddQuickFirst("User", First__User) + model.AddQuickList("User", List__User) + model.AddQuickLoad("User", Load__User) + model.AddQuickLoadList("User", LoadList__User) + model.AddQuickCount("User", Count__User) } diff --git a/example/user/resolver/mutation.go b/example/user/resolver/mutation.go index 8aebd28..6214099 100644 --- a/example/user/resolver/mutation.go +++ b/example/user/resolver/mutation.go @@ -2,16 +2,16 @@ package resolver import ( - "fmt" - "user/models" - - "github.com/light-speak/lighthouse/auth" - "github.com/light-speak/lighthouse/context" - "github.com/light-speak/lighthouse/graphql/model" - "github.com/light-speak/lighthouse/log" + "github.com/light-speak/lighthouse/graphql/model" + "fmt" + "user/models" + "github.com/light-speak/lighthouse/auth" + "github.com/light-speak/lighthouse/log" + "github.com/light-speak/lighthouse/context" ) -func (r *Resolver) LoginResolver(ctx *context.Context, name string) (*models.LoginResponse, error) { + +func (r *Resolver) LoginResolver(ctx *context.Context,name string) (*models.LoginResponse, error) { // Func:Login user code start. Do not remove this comment. user := &models.User{} db := model.GetDB() @@ -28,10 +28,10 @@ func (r *Resolver) LoginResolver(ctx *context.Context, name string) (*models.Log Token: token, Authorization: fmt.Sprintf("Bearer %s", token), }, nil - // Func:Login user code end. Do not remove this comment. + // Func:Login user code end. Do not remove this comment. } -func (r *Resolver) CreatePostResolver(ctx *context.Context, input *models.TestInput) (*models.Post, error) { +func (r *Resolver) CreatePostResolver(ctx *context.Context,input *models.TestInput) (*models.Post, error) { // Func:CreatePost user code start. Do not remove this comment. panic("not implement") - // Func:CreatePost user code end. Do not remove this comment. -} + // Func:CreatePost user code end. Do not remove this comment. +} \ No newline at end of file diff --git a/example/user/resolver/operation_gen.go b/example/user/resolver/operation_gen.go index bcee27d..a185d27 100644 --- a/example/user/resolver/operation_gen.go +++ b/example/user/resolver/operation_gen.go @@ -2,13 +2,13 @@ package resolver import ( + "github.com/light-speak/lighthouse/graphql" "github.com/light-speak/lighthouse/context" - "user/models" "github.com/light-speak/lighthouse/graphql/model" + "github.com/light-speak/lighthouse/graphql/excute" "fmt" - "github.com/light-speak/lighthouse/graphql" "github.com/light-speak/lighthouse/resolve" - "github.com/light-speak/lighthouse/graphql/excute" + "user/models" ) func init() { diff --git a/example/user/resolver/query.go b/example/user/resolver/query.go index 553b5e0..8b592e7 100644 --- a/example/user/resolver/query.go +++ b/example/user/resolver/query.go @@ -2,19 +2,31 @@ package resolver import ( - "fmt" - "github.com/light-speak/lighthouse/context" "user/models" "github.com/light-speak/lighthouse/log" + "github.com/light-speak/lighthouse/context" "github.com/light-speak/lighthouse/graphql/model" + "fmt" ) -func (r *Resolver) TestPostIdResolver(ctx *context.Context,id int64) (*models.Post, error) { - // Func:TestPostId user code start. Do not remove this comment. - log.Debug().Msgf("id: %d", id) - return nil, nil - // Func:TestPostId user code end. Do not remove this comment. +func (r *Resolver) TestPostInputResolver(ctx *context.Context,input *models.TestInput) (string, error) { + // Func:TestPostInput user code start. Do not remove this comment. + res := fmt.Sprintf("input: %+v", input) + return res, nil + // Func:TestPostInput user code end. Do not remove this comment. +} +func (r *Resolver) TestNullableEnumResolver(ctx *context.Context,enum *models.TestEnum) (string, error) { + // Func:TestNullableEnum user code start. Do not remove this comment. + panic("not implement") + // Func:TestNullableEnum user code end. Do not remove this comment. +} +func (r *Resolver) TestPostEnumResolver(ctx *context.Context,enum *models.TestEnum) (string, error) { + // Func:TestPostEnum user code start. Do not remove this comment. + log.Debug().Msgf("enum: %+v", enum) + res := fmt.Sprintf("啥也不是!:%v", *enum == models.TestEnumA) + return res, nil + // Func:TestPostEnum user code end. Do not remove this comment. } func (r *Resolver) GetPostResolver(ctx *context.Context,fuck string) (*models.Post, error) { // Func:GetPost user code start. Do not remove this comment. @@ -25,34 +37,17 @@ func (r *Resolver) GetPostResolver(ctx *context.Context,fuck string) (*models.Po return post, nil // Func:GetPost user code end. Do not remove this comment. } -func (r *Resolver) TestNullableEnumResolver(ctx *context.Context,enum *models.TestEnum) (string, error) { - // Func:TestNullableEnum user code start. Do not remove this comment. - panic("not implement") - // Func:TestNullableEnum user code end. Do not remove this comment. -} -func (r *Resolver) GetPostIdsResolver(ctx *context.Context) ([]int64, error) { - // Func:GetPostIds user code start. Do not remove this comment. - return []int64{1, 2, 3}, nil - // Func:GetPostIds user code end. Do not remove this comment. +func (r *Resolver) TestPostIdResolver(ctx *context.Context,id int64) (*models.Post, error) { + // Func:TestPostId user code start. Do not remove this comment. + log.Debug().Msgf("id: %d", id) + return nil, nil + // Func:TestPostId user code end. Do not remove this comment. } func (r *Resolver) TestPostIntResolver(ctx *context.Context,id bool) (*models.Post, error) { // Func:TestPostInt user code start. Do not remove this comment. return nil, nil // Func:TestPostInt user code end. Do not remove this comment. } -func (r *Resolver) TestPostEnumResolver(ctx *context.Context,enum *models.TestEnum) (string, error) { - // Func:TestPostEnum user code start. Do not remove this comment. - log.Debug().Msgf("enum: %+v", enum) - res := fmt.Sprintf("啥也不是!:%v", *enum == models.TestEnumA) - return res, nil - // Func:TestPostEnum user code end. Do not remove this comment. -} -func (r *Resolver) TestPostInputResolver(ctx *context.Context,input *models.TestInput) (string, error) { - // Func:TestPostInput user code start. Do not remove this comment. - res := fmt.Sprintf("input: %+v", input) - return res, nil - // Func:TestPostInput user code end. Do not remove this comment. -} func (r *Resolver) GetPostsResolver(ctx *context.Context,fuck string) ([]*models.Post, error) { // Func:GetPosts user code start. Do not remove this comment. posts := []*models.Post{} @@ -60,4 +55,9 @@ func (r *Resolver) GetPostsResolver(ctx *context.Context,fuck string) ([]*models db.Find(&posts) return posts, nil // Func:GetPosts user code end. Do not remove this comment. +} +func (r *Resolver) GetPostIdsResolver(ctx *context.Context) ([]int64, error) { + // Func:GetPostIds user code start. Do not remove this comment. + return []int64{1, 2, 3}, nil + // Func:GetPostIds user code end. Do not remove this comment. } \ No newline at end of file diff --git a/example/user/schema/post.graphqls b/example/user/schema/post.graphqls index a677dda..4d3091e 100644 --- a/example/user/schema/post.graphqls +++ b/example/user/schema/post.graphqls @@ -20,7 +20,7 @@ extend type Query { testPostInt(id: Boolean!): Post testPostEnum(enum: TestEnum!): String! testPostInput(input: TestInput!): String! - testNullableEnum(enum: TestEnum): String! + testNullableEnum(enum: TestEnum): String! } extend type Mutation { diff --git a/graphql/ast/directive.go b/graphql/ast/directive.go index 048d019..ec38516 100644 --- a/graphql/ast/directive.go +++ b/graphql/ast/directive.go @@ -4,6 +4,7 @@ import "github.com/light-speak/lighthouse/errors" var fieldDirectiveMap = make(map[string]func(f *Field, d *Directive, store *NodeStore, parent Node) errors.GraphqlErrorInterface) var objectDirectiveMap = make(map[string]func(o *ObjectNode, d *Directive, store *NodeStore) errors.GraphqlErrorInterface) +var fieldRuntimeDirectiveMap = make(map[string]func(f *Field, d *Directive, store *NodeStore, parent Node) errors.GraphqlErrorInterface) func AddFieldDirective(name string, fn func(f *Field, d *Directive, store *NodeStore, parent Node) errors.GraphqlErrorInterface) { fieldDirectiveMap[name] = fn @@ -13,6 +14,10 @@ func AddObjectDirective(name string, fn func(o *ObjectNode, d *Directive, store objectDirectiveMap[name] = fn } +func AddFieldRuntimeDirective(name string, fn func(f *Field, d *Directive, store *NodeStore, parent Node) errors.GraphqlErrorInterface) { + fieldRuntimeDirectiveMap[name] = fn +} + func (f *Field) ParseFieldDirectives(store *NodeStore, parent Node) errors.GraphqlErrorInterface { for _, directive := range f.Directives { if fn, ok := fieldDirectiveMap[directive.Name]; ok { diff --git a/graphql/ast/directive/belongsto.go b/graphql/ast/directive/belongsto.go index 464fc68..568bbab 100644 --- a/graphql/ast/directive/belongsto.go +++ b/graphql/ast/directive/belongsto.go @@ -11,12 +11,12 @@ func handlerBelongsTo(f *ast.Field, d *ast.Directive, store *ast.NodeStore, pare RelationType: ast.RelationTypeBelongsTo, } if relationName := d.GetArg("relation"); relationName != nil { - relation.Name = relationName.Value.(string) + relation.Name = utils.SnakeCase(relationName.Value.(string)) } else { relation.Name = utils.LcFirst(f.Name) } if reference := d.GetArg("reference"); reference != nil { - relation.Reference = reference.Value.(string) + relation.Reference = utils.SnakeCase(reference.Value.(string)) } else { relation.Reference = "id" } diff --git a/graphql/ast/directive/hasmany.go b/graphql/ast/directive/hasmany.go index 1d042d0..7575081 100644 --- a/graphql/ast/directive/hasmany.go +++ b/graphql/ast/directive/hasmany.go @@ -12,20 +12,14 @@ func handlerHasMany(f *ast.Field, d *ast.Directive, store *ast.NodeStore, parent RelationType: ast.RelationTypeHasMany, } if relationName := d.GetArg("relation"); relationName != nil { - relation.Name = relationName.Value.(string) + relation.Name = utils.SnakeCase(relationName.Value.(string)) } else { - return &errors.GraphQLError{ - Message: "relation name is required for hasMany directive", - Locations: []*errors.GraphqlLocation{d.GetLocation()}, - } + relation.Name = utils.SnakeCase(f.Type.GetRealType().Name) } if foreignKey := d.GetArg("foreignKey"); foreignKey != nil { relation.ForeignKey = utils.SnakeCase(foreignKey.Value.(string)) } else { - return &errors.GraphQLError{ - Message: "foreign key is required for hasMany directive", - Locations: []*errors.GraphqlLocation{d.GetLocation()}, - } + relation.ForeignKey = utils.SnakeCase(relation.Name) + "_id" } if reference := d.GetArg("reference"); reference != nil { relation.Reference = utils.SnakeCase(reference.Value.(string)) diff --git a/graphql/ast/directive/morphto.go b/graphql/ast/directive/morphto.go index 0e18b37..a4b9bab 100644 --- a/graphql/ast/directive/morphto.go +++ b/graphql/ast/directive/morphto.go @@ -13,17 +13,17 @@ func handlerMorphTo(f *ast.Field, d *ast.Directive, store *ast.NodeStore, parent RelationType: ast.RelationTypeMorphTo, } if morphType := d.GetArg("morphType"); morphType != nil { - relation.MorphType = morphType.Value.(string) + relation.MorphType = utils.SnakeCase(morphType.Value.(string)) } else { relation.MorphType = fmt.Sprintf("%s_type", utils.LcFirst(f.Name)) } if morphKey := d.GetArg("morphKey"); morphKey != nil { - relation.MorphKey = morphKey.Value.(string) + relation.MorphKey = utils.SnakeCase(morphKey.Value.(string)) } else { - relation.MorphKey = fmt.Sprintf("%s_id", utils.LcFirst(f.Name)) + relation.MorphKey = fmt.Sprintf("%s_id", utils.SnakeCase(f.Name)) } if reference := d.GetArg("reference"); reference != nil { - relation.Reference = reference.Value.(string) + relation.Reference = utils.SnakeCase(reference.Value.(string)) } else { relation.Reference = "id" } diff --git a/graphql/ast/node.go b/graphql/ast/node.go index 92a4578..8850a6a 100644 --- a/graphql/ast/node.go +++ b/graphql/ast/node.go @@ -532,6 +532,10 @@ func (t *TypeRef) IsObject() bool { return t.Kind == KindObject } +func (t *TypeRef) IsEnum() bool { + return t.GetRealType().Kind == KindEnum +} + func (t *TypeRef) GetRealType() *TypeRef { if t.Kind == KindNonNull { return t.OfType.GetRealType() diff --git a/graphql/excute/excute.go b/graphql/excute/excute.go index 3769fe5..ea37e26 100644 --- a/graphql/excute/excute.go +++ b/graphql/excute/excute.go @@ -42,11 +42,11 @@ func ExecuteQuery(ctx *context.Context, query string, variables map[string]any) var funMap map[string]func(qp *parser.QueryParser, field *ast.Field) (interface{}, error) switch qp.OperationType { - case "Mutation": + case "Mutation", "mutation": funMap = mutationMap - case "Subscription": + case "Subscription", "subscription": funMap = subscriptionMap - case "Query": + case "Query", "query": funMap = queryMap default: e := &errors.ParserError{ diff --git a/graphql/excute/validate.go b/graphql/excute/validate.go index fa8ae63..b3a71b6 100644 --- a/graphql/excute/validate.go +++ b/graphql/excute/validate.go @@ -1,6 +1,8 @@ package excute import ( + "fmt" + "github.com/light-speak/lighthouse/errors" "github.com/light-speak/lighthouse/graphql/ast" "github.com/light-speak/lighthouse/graphql/model" @@ -13,15 +15,30 @@ func ValidateValue(field *ast.Field, value interface{}, isVariable bool) (interf switch realType.Kind { case ast.KindScalar: - v, err = realType.TypeNode.(*ast.ScalarNode).ScalarType.Serialize(value, field.GetLocation()) - if err != nil { + if scalarNode, ok := realType.TypeNode.(*ast.ScalarNode); ok { + v, err = scalarNode.ScalarType.Serialize(value, field.GetLocation()) + if err != nil { + return nil, &errors.GraphQLError{ + Message: err.Error(), + Locations: []*errors.GraphqlLocation{field.GetLocation()}, + } + } + } else { return nil, &errors.GraphQLError{ - Message: err.Error(), + Message: fmt.Sprintf("scalar type is not a scalar node, field: %s", field.Name), Locations: []*errors.GraphqlLocation{field.GetLocation()}, } } case ast.KindEnum: - v = value.(model.EnumInterface).ToString() + + if e, ok := value.(model.EnumInterface); ok { + v = e.ToString() + } else { + return nil, &errors.GraphQLError{ + Message: fmt.Sprintf("enum value type not supported, field: %s, got %v , type: %T", field.Name, value, value), + Locations: []*errors.GraphqlLocation{field.GetLocation()}, + } + } } return v, nil } diff --git a/graphql/model/generate/tpl/model.tpl b/graphql/model/generate/tpl/model.tpl index e9b20f7..b93a8cc 100644 --- a/graphql/model/generate/tpl/model.tpl +++ b/graphql/model/generate/tpl/model.tpl @@ -9,6 +9,22 @@ func (this *{{ $name | ucFirst }}) Get{{ .Name | ucFirst }}() {{ false | .Type.G {{- end }} func (*{{ $name | ucFirst }}) TableName() string { return "{{ if ne .Table "" }}{{ .Table }}{{ else }}{{ .Name | pluralize | lcFirst }}{{ end }}" } func (*{{ $name | ucFirst }}) TypeName() string { return "{{ .Name | lcFirst }}" } +var {{ $name | ucFirst }}EnumFields = map[string]func(interface{}) interface{} { + {{- range .Fields }} + {{- if .Type.IsEnum }} + "{{ .Name | lcFirst }}": func(value interface{}) interface{} { + switch v := value.(type) { + case int64: + return {{ .Type.GetRealType.Name }}(v) + case int8: + return {{ .Type.GetRealType.Name }}(v) + default: + return v + } + }, + {{- end }} + {{- end }} +} {{ end }} func Migrate() error { diff --git a/graphql/model/generate/tpl/repo.tpl b/graphql/model/generate/tpl/repo.tpl index 3170fec..2606956 100644 --- a/graphql/model/generate/tpl/repo.tpl +++ b/graphql/model/generate/tpl/repo.tpl @@ -1,10 +1,5 @@ {{ range .Nodes }} {{- $name := .Name -}} -func Provide__{{ $name | ucFirst }}() map[string]*ast.Relation { return map[string]*ast.Relation{ - {{- range .Fields }} - {{- if ne .Name "__typename" }}"{{ .Name }}": {{ if .Relation }}{{ buildRelation . }}{{ else }}{}{{ end }},{{ end -}} - {{- end -}} -}} func Load__{{ $name | ucFirst }}(ctx *context.Context, key int64, field string) (map[string]interface{}, error) { return model.GetLoader[int64](model.GetDB(), "{{ if ne .Table "" }}{{ .Table }}{{ else }}{{ $name | pluralize | lcFirst }}{{ end }}", field).Load(key) } @@ -23,6 +18,11 @@ func First__{{ $name | ucFirst }}(ctx *context.Context, data map[string]interfac return nil, err } } + for key, value := range data { + if fn, ok := models.{{ $name | ucFirst }}EnumFields[key]; ok { + data[key] = fn(value) + } + } return data, nil } func List__{{ $name | ucFirst }}(ctx *context.Context, datas []map[string]interface{}, scopes ...func(db *gorm.DB) *gorm.DB) ([]map[string]interface{}, error) { diff --git a/graphql/parser/directives.go b/graphql/parser/directives.go index 5ac99db..19d7422 100644 --- a/graphql/parser/directives.go +++ b/graphql/parser/directives.go @@ -303,11 +303,11 @@ func (p *Parser) addRelationDirective() { Args: map[string]*ast.Argument{ "relation": { Name: "relation", - Type: &ast.TypeRef{Kind: ast.KindNonNull, OfType: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"}}, + Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"}, }, "foreignKey": { Name: "foreignKey", - Type: &ast.TypeRef{Kind: ast.KindNonNull, OfType: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"}}, + Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"}, }, "reference": { Name: "reference", @@ -322,7 +322,7 @@ func (p *Parser) addRelationDirective() { Args: map[string]*ast.Argument{ "relation": { Name: "relation", - Type: &ast.TypeRef{Kind: ast.KindNonNull, OfType: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"}}, + Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"}, }, "foreignKey": { Name: "foreignKey", @@ -353,6 +353,52 @@ func (p *Parser) addRelationDirective() { }, }, }) + // morphToMany + p.AddDirectiveDefinition(&ast.DirectiveDefinition{ + Name: "morphToMany", Description: utils.StrPtr("The field is a relationship with another model."), + Locations: []ast.Location{ast.LocationFieldDefinition}, + Args: map[string]*ast.Argument{ + "relation": { + Name: "relation", + Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"}, + }, + "morphType": { + Name: "morphType", + Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"}, + }, + "morphKey": { + Name: "morphKey", + Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"}, + }, + "reference": { + Name: "reference", + Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"}, + }, + }, + }) + // manyToMany + p.AddDirectiveDefinition(&ast.DirectiveDefinition{ + Name: "manyToMany", Description: utils.StrPtr("The field is a relationship with another model."), + Locations: []ast.Location{ast.LocationFieldDefinition}, + Args: map[string]*ast.Argument{ + "relation": { + Name: "relation", + Type: &ast.TypeRef{Kind: ast.KindNonNull, OfType: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"}}, + }, + "pivot": { + Name: "pivot", + Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"}, + }, + "foreignKey": { + Name: "foreignKey", + Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"}, + }, + "reference": { + Name: "reference", + Type: &ast.TypeRef{Kind: ast.KindScalar, Name: "String"}, + }, + }, + }) } func (p *Parser) addObjectDirective() { diff --git a/graphql/parser/parsing.go b/graphql/parser/parsing.go index 52ee857..1992a5a 100644 --- a/graphql/parser/parsing.go +++ b/graphql/parser/parsing.go @@ -255,10 +255,13 @@ func (p *Parser) parseArguments() map[string]*ast.Argument { } p.expect(lexer.LeftParent) for p.currToken.Type != lexer.RightParent { + if p.currToken.Type == lexer.Comma { + p.expect(lexer.Comma) + } arg := p.parseArgument() args[arg.Name] = arg - if p.currToken.Type != lexer.RightParent { - p.expect(lexer.Comma) + if p.currToken.Type == lexer.RightParent { + break } } diff --git a/version/version.go b/version/version.go index 566d4f7..149642f 100644 --- a/version/version.go +++ b/version/version.go @@ -1,3 +1,3 @@ package version -var Version = "v0.0.3" +var Version = "v0.0.4"