Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

`DefineRec.InputObject` with `fixFields`?

オープン
#596 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
45/100
issue の種類
機能追加
明瞭さ
おおむね明確
活発さ
活発
技術スタック
fsharp

調査の方向性

Issue に示されている Define.InputObject と提案された DefineRec.InputObject のエントリーポイントから始め、現在、再帰的な input 定義がどのように表現されているかを調べてください。ProductFilter の完全なデモを実行し、公開 helper が警告なしで再帰的なフィールドをサポートすべきかを判断してください;合意された API と、検証済みの再帰的スキーマ例が得られれば完了です。

索引モデルが 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時間
マージ済み PR(30日)
14

コントリビューションガイド

このリポジトリのコントリビューションガイドは索引されていません

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

fsprojects/FSharp.Data.GraphQL のほかの issue

fsprojects/FSharp.Data.GraphQL の issue をすべて見る

似ている issue

Backend & API Design の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。