`DefineRec.InputObject` with `fixFields`?
还没有人认领这个 Issue。
评估
- 难度
- 3/5
- 预计耗时
- 1-2 天
- 新手友好度
- 45/100
- Issue 类型
- 功能
- 描述清晰度
- 基本清楚
- 活跃度
- 活跃
- 技术栈
- fsharp
调研方向
从 issue 中所示的 Define.InputObject 和拟议的 DefineRec.InputObject 入口点开始,然后检查当前如何表示递归 input 定义。运行完整的 ProductFilter 演示,并确定公共 helper 是否应在无警告的情况下支持递归字段;完成的标准是达成一致的 API,并验证一个递归 schema 示例。
由索引模型根据 Issue 内容生成。
描述
For recursive input types it's nice not to have warnings.
I came up with this helper function:
type DefineRec with
static member InputObject(
name: string,
fixFields: InputObjectDefinition<'a> -> InputFieldDef list,
?description: string
) : InputObjectDefinition<'a> =
let mutable self = Unchecked.defaultof<InputObjectDefinition<'a>>
let definition =
{
Name = name
Fields = lazy (fixFields self |> List.toArray)
Description = description
Validator = Validation.GQLValidator.empty
ExecuteInput = Unchecked.defaultof<_>
}
self <- definition
definition
Minimal usage:
type Comment =
{
Message : string
Reply : Comment
}
DefineRec.InputObject<Comment>(
name = "Comment",
fixFields =
fun self ->
[
Define.Input("message", StringType)
Define.Input("reply", self)
]
)
Is this a good approach?
Full demo usage
#r "nuget: FSharp.Data.GraphQL.Server, 3.1.1"
open System.Text.Json
open FSharp.Data.GraphQL
open FSharp.Data.GraphQL.Types
type DefineRec with
static member InputObject(
name: string,
fixFields: InputObjectDefinition<'a> -> InputFieldDef list,
?description: string
) : InputObjectDefinition<'a> =
let mutable self = Unchecked.defaultof<InputObjectDefinition<'a>>
let definition =
{
Name = name
Fields = lazy (fixFields self |> List.toArray)
Description = description
Validator = Validation.GQLValidator.empty
ExecuteInput = Unchecked.defaultof<_>
}
self <- definition
definition
type StringFilter =
{
Eq : string option
Ne : string option
In : string list option
Nin : string list option
}
type ProductFilter =
{
Title : StringFilter option
Category : StringFilter option
Brand : StringFilter option
And : ProductFilter list option
Or : ProductFilter list option
Not : ProductFilter option
}
type Product =
{
ID : int
Title : string
Category : string
Brand : string
}
let stringFilterInputType : InputObjectDefinition<StringFilter> =
Define.InputObject(
name = "StringFilterInput",
fields =
[
Define.Input("eq", Nullable StringType)
Define.Input("ne", Nullable StringType)
Define.Input("in", Nullable (ListOf StringType))
Define.Input("nin", Nullable (ListOf StringType))
]
)
let productFilterInputType : InputObjectDefinition<ProductFilter> =
DefineRec.InputObject(
name = "ProductFilterInput",
fixFields =
fun self ->
[
Define.Input("title", Nullable stringFilterInputType)
Define.Input("category", Nullable stringFilterInputType)
Define.Input("brand", Nullable stringFilterInputType)
Define.Input("and", Nullable (ListOf self))
Define.Input("or", Nullable (ListOf self))
Define.Input("not", Nullable self)
]
)
let productType =
Define.Object<Product>(
name = "Product",
fields =
[
Define.Field("id", IntType, fun _ p -> p.ID)
Define.Field("title", StringType, fun _ p -> p.Title)
Define.Field("category", StringType, fun _ p -> p.Category)
Define.Field("brand", StringType, fun _ p -> p.Brand)
]
)
let evalStringFilter (targetVal : string) (filter : StringFilter) : bool =
let matchEq = filter.Eq |> Option.forall (fun v -> targetVal = v)
let matchNe = filter.Ne |> Option.forall (fun v -> targetVal <> v)
let matchIn = filter.In |> Option.forall (fun list -> List.contains targetVal list)
let matchNin = filter.Nin |> Option.forall (fun list -> not (List.contains targetVal list))
matchEq && matchNe && matchIn && matchNin
let rec matchesProduct (product : Product) (filter : ProductFilter) : bool =
let titleMatch = filter.Title |> Option.forall (evalStringFilter product.Title)
let categoryMatch = filter.Category |> Option.forall (evalStringFilter product.Category)
let brandMatch = filter.Brand |> Option.forall (evalStringFilter product.Brand)
let fieldsValid = titleMatch && categoryMatch && brandMatch
let andValid = filter.And |> Option.forall (List.forall (matchesProduct product))
let orValid = filter.Or |> Option.forall (List.exists (matchesProduct product))
let notValid = filter.Not |> Option.forall (fun f -> not (matchesProduct product f))
fieldsValid && andValid && orValid && notValid
let products =
[
{ ID = 1; Title = "Laptop"; Category = "Electronics"; Brand = "BrandA" }
{ ID = 2; Title = "Smartphone"; Category = "Electronics"; Brand = "BrandB" }
{ ID = 3; Title = "Headphones"; Category = "Accessories"; Brand = "BrandA" }
{ ID = 4; Title = "Coffee Maker"; Category = "Home Appliances"; Brand = "BrandC" }
{ ID = 5; Title = "Blender"; Category = "Home Appliances"; Brand = "BrandD" }
]
let fetchProducts (filter : ProductFilter option) =
match filter with
| Some f -> List.filter (fun p -> matchesProduct p f) products
| None -> products
let queryType =
Define.Object(
name = "Query",
fields = [
Define.Field(
name = "products",
typedef = ListOf productType,
args =
[
Define.Input("filter", Nullable productFilterInputType)
],
resolve =
fun ctx () ->
let filterArg : ProductFilter voption = ctx.TryArg("filter")
fetchProducts (Option.ofValueOption filterArg)
)
]
)
let schema = Schema(queryType)
let executor = Executor(schema)
let query =
"""
query {
products(
filter: {
or: [
{ category: { eq: "Electronics" } },
{ brand: { eq: "BrandA" } }
]
}
) {
id
title
category
brand
}
}
"""
let response =
executor.AsyncExecute(
query,
(fun () ->
{
new IInputExecutionContext with
member this.GetFile(_ : string) =
Result.Error "Not implemented"
})
)
|> Async.RunSynchronously
let content =
match response.Content with
| GQLResponseContent.Direct (content, []) -> content
| GQLResponseContent.Direct (_, errors) -> failwith $"Unexpected errors: {errors}"
| x -> failwith $"Unexpected response content: %A{x}"
let json = System.Text.Json.JsonSerializer.Serialize(content, JsonSerializerOptions(WriteIndented = true))
printfn "%s" json
{
"products": [
{
"id": 1,
"title": "Laptop",
"category": "Electronics",
"brand": "BrandA"
},
{
"id": 2,
"title": "Smartphone",
"category": "Electronics",
"brand": "BrandB"
},
{
"id": 3,
"title": "Headphones",
"category": "Accessories",
"brand": "BrandA"
}
]
}
- 主要语言
- F#
- 星标
- 406
- 派生
- 74
- 平均合并
- 1 天 8 小时
- 30 天内合并 PR
- 14
贡献指南
这个仓库没有索引到贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
fsprojects/FSharp.Data.GraphQL 的其他 Issue
-
难度 4/5 3-5 天 新手友好度 52/100
-
fsprojects/FSharp.Data.GraphQL#573 · 1 个 reaction · 已指派 2 人 ·
-
fsprojects/FSharp.Data.GraphQL#566 · 1 条评论 · 1 个 reaction · 已指派 2 人 ·
-
难度 5/5 一周以上 新手友好度 30/100
-
难度 5/5 一周以上 新手友好度 25/100
查看 fsprojects/FSharp.Data.GraphQL 的全部 Issue
相似的 Issue
-
bug priority:normal ready-for-dev
难度 2/5 1-3 小时 新手友好度 88/100
OpenHands/extensions#626 · 1 条评论 ·
-
bug
难度 2/5 1-3 小时 新手友好度 76/100
avniproject/avni-client#2135 ·
-
难度 2/5 1-3 小时 新手友好度 88/100
-
needs-triage
难度 2/5 1-3 小时 新手友好度 78/100
-
难度 2/5 1-3 小时 新手友好度 78/100
use-agent-os/agent-os#3276 ·