Hacktoberfest 2026: die Issues, die Maintainer für den Oktober markiert haben – offen und einsteigerfreundlich. Hacktoberfest-Issues durchsuchen

`DefineRec.InputObject` with `fixFields`?

Offen
#596 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Maintainer antworten meist innerhalb von 1 Tag

Dieses Issue hat noch niemand übernommen.

Bewertung

Schwierigkeit
3/5
Geschätzter Aufwand
1-2 Tage
Anfängerfreundlichkeit
45/100
Issue-Typ
Feature
Klarheit
Größtenteils klar
Aktivitätsstatus
Aktiv
Tech-Stack
fsharp

Rechercherichtung

Beginnen Sie mit den im Issue gezeigten Einstiegspunkten Define.InputObject und dem vorgeschlagenen DefineRec.InputObject und untersuchen Sie anschließend, wie rekursive Input-Definitionen derzeit dargestellt werden. Führen Sie die vollständige ProductFilter-Demo aus und ermitteln Sie, ob der öffentliche Helper rekursive Felder ohne Warnungen unterstützen sollte; abgeschlossen ist dies, wenn eine abgestimmte API und ein validiertes Beispiel für ein rekursives Schema vorliegen.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

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"
    }
  ]
}
Vorherrschende Sprache
F#
Sterne
406
Forks
74
Ø Merge
15 Std. 59 Min.
Gemergte PRs (30 T.)
20

Entwicklungsumgebung

Dieses Projekt bietet weder Dev-Container noch Dockerfile noch Beitragsleitfaden – die Einrichtung liegt bei Ihnen. Beginnen Sie mit der README; die allgemeinen Schritte stehen in unserem Leitfaden für den ersten Beitrag.

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Mehr aus fsprojects/FSharp.Data.GraphQL

Alle Issues in fsprojects/FSharp.Data.GraphQL

Ähnliche Issues

Weitere Issues zu Backend & API Design

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.