Hacktoberfest 2026 : les issues que les mainteneurs ont marquées pour octobre, ouvertes et accessibles aux débutants. Parcourir les issues Hacktoberfest

`DefineRec.InputObject` with `fixFields`?

Ouverte
#596 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Les mainteneurs répondent en général sous 1 jour

Personne n'a encore pris cette issue.

Évaluation

Difficulté
3/5
Temps estimé
1-2 jours
Accessibilité débutants
45/100
Type d'issue
Fonctionnalité
Clarté
Plutôt claire
Activité
Active
Stack technique
fsharp

Piste de recherche

Commencez par les points d’entrée Define.InputObject et DefineRec.InputObject proposés et présentés dans l’issue, puis examinez comment les définitions d’input récursives sont actuellement représentées. Exécutez la démo complète de ProductFilter et déterminez si le helper public devrait prendre en charge les champs récursifs sans avertissements ; le travail est considéré comme terminé lorsqu’une API convenue et un exemple validé de schéma récursif sont disponibles.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Description

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"
    }
  ]
}
Langage dominant
F#
Étoiles
406
Forks
74
Merge moyen
15 h 59 min
PR mergées (30 j)
20

Préparer son environnement

Ce projet ne fournit ni conteneur de développement, ni Dockerfile, ni guide de contribution : l'installation est à votre charge. Commencez par son README, et consultez notre guide de la première contribution pour les étapes générales.

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de fsprojects/FSharp.Data.GraphQL

Toutes les issues de fsprojects/FSharp.Data.GraphQL

Issues similaires

Plus d'issues Backend & API Design

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.