Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type HttpContextRequestExecutionContext (httpContext : HttpContext) =
else
let form = httpContext.Request.Form
match (form.Files |> Seq.vtryFind (fun f -> f.Name = key)) with
| ValueSome file -> Ok (file.OpenReadStream())
| ValueSome file ->
Ok { Stream = file.OpenReadStream (); ContentType = file.ContentType }
| ValueNone -> Error $"File with key '{key}' not found"

7 changes: 6 additions & 1 deletion src/FSharp.Data.GraphQL.Shared/InputContext.fs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
namespace FSharp.Data.GraphQL

type FileData = {
Stream : System.IO.Stream
ContentType : string
}

type IInputExecutionContext =
abstract GetFile : string -> Result<System.IO.Stream, string>
abstract GetFile : string -> Result<FileData, string>

type InputExecutionContextProvider = unit -> IInputExecutionContext

20 changes: 10 additions & 10 deletions src/FSharp.Data.GraphQL.Shared/SchemaDefinitions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ module SchemaDefinitions =
| _ -> Some(x.ToString())

/// Tries to convert any value to string.
let coerceFileValue (context : IInputExecutionContext) (value : obj) : Result<System.IO.Stream, string> =
let coerceFileValue (context : IInputExecutionContext) (value : obj) : Result<FileData, string> =
match coerceStringValue value with
| Some fileName -> context.GetFile fileName
| None -> Error "Only string value can be used as file name"
Expand Down Expand Up @@ -482,31 +482,31 @@ module SchemaDefinitions =
CoerceInput = coerceGuidInput
CoerceOutput = coerceGuidValue }

/// Defines an object list filter for use as an argument for filter list of object fields.
let FileType : InputCustomDefinition<System.IO.Stream> = {
/// Defines an object list filter for use as an argument for filter list of object fields.
let FileType : InputCustomDefinition<FileData> = {
Name = "FileType"
Description =
Some
"The `File` type represents a file on one or more fields of an object in an object list. The filter is represented by a JSON object where the fields are the complemented by specific suffixes to represent a query."
CoerceInput =
(fun inputContext input variables ->
let getFileStream fileKey =
let getFileData fileKey =
let inputExecutionContext = inputContext()
let streamResult = inputExecutionContext.GetFile fileKey
match streamResult with
| Ok stream -> Ok stream
let fileData = inputExecutionContext.GetFile fileKey
match fileData with
| Ok data -> Ok data
| Error errorMessage -> IGQLError.createResultErrorList errorMessage

match input with
| InlineConstant c ->
match c with
| StringValue strValue -> getFileStream strValue
| StringValue strValue -> getFileData strValue
| VariableName varName ->
Ok (variables[varName] :?> System.IO.Stream)
Ok (variables[varName] :?> FileData)
| _ -> IGQLError.createResultErrorList "Only a string value or a variable with a string value can be used as a file name."
| Variable json ->
match (json |> InputValue.OfJsonElement) with
| StringValue str -> getFileStream str
| StringValue str -> getFileData str
| _ -> IGQLError.createResultErrorList "Only a variable with a string value can be used as a file name.")
}

Expand Down
32 changes: 17 additions & 15 deletions tests/FSharp.Data.GraphQL.Tests/FileTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ let QueryType =
)

type Input = {
File : Stream
File2 : Stream option
File : FileData
File2 : FileData option
}

let private getFullInfo fileData =
use reader = new StreamReader(fileData.Stream, Encoding.UTF8, true)
let fileContent = reader.ReadToEnd()
fileContent + fileData.ContentType

let InputObject = Define.InputObject<Input>(
name = "Input",
fields =
Expand All @@ -37,21 +42,18 @@ let MutationType =
fields =
[ Define.Field ("uploadFile", StringType, "", [ Define.Input ("input", FileType) ],
(fun ctx () ->
let stream = ctx.Arg<Stream> "input"
use reader = new StreamReader(stream, Encoding.UTF8, true)
reader.ReadToEnd()
let fileData = ctx.Arg<FileData> "input"
getFullInfo fileData
));
Define.Field ("uploadFileComplex", StringType, "", [ Define.Input ("input", InputObject) ],
(fun ctx () ->
let input = ctx.Arg<Input> "input"
let reader = new StreamReader(input.File, Encoding.UTF8, true)
let fileContent = reader.ReadToEnd()
let file2Content = match input.File2 with
let fileString = getFullInfo input.File
let file2String = match input.File2 with
| Some file2 ->
let reader2 = new StreamReader(file2, Encoding.UTF8, true)
reader2.ReadToEnd()
getFullInfo file2
| None -> ""
fileContent + file2Content
fileString + file2String
))
]
)
Expand Down Expand Up @@ -80,7 +82,7 @@ let mutationComplexObjectWithTwoFiles = """mutation uploadFile () {

[<Fact>]
let ``File type: Must upload file as input scalar using inline string as a file name`` () =
let expected = NameValueLookup.ofList [ "uploadFile", MockInputContext.mockFileText ]
let expected = NameValueLookup.ofList [ "uploadFile", MockInputContext.mockFileTextAndContentType ]
let result = execute mutationWithConstant
ensureDirect result <| fun data errors ->
empty errors
Expand All @@ -89,7 +91,7 @@ let ``File type: Must upload file as input scalar using inline string as a file

[<Fact>]
let ``File type: Must upload a file as input scalar using a variable`` () =
let expected = NameValueLookup.ofList [ "uploadFile", MockInputContext.mockFileText ]
let expected = NameValueLookup.ofList [ "uploadFile", MockInputContext.mockFileTextAndContentType ]
let jsonVariable = "\"fileKey\"" |> JsonDocument.Parse |> _.RootElement
let variables = ImmutableDictionary<string, JsonElement>.Empty.Add ("file", jsonVariable)
let result = executeWithVariables (mutationWithVariable, variables)
Expand All @@ -100,7 +102,7 @@ let ``File type: Must upload a file as input scalar using a variable`` () =

[<Fact>]
let ``File type: Must upload a file as input object field using inline string a file name`` () =
let expected = NameValueLookup.ofList [ "uploadFileComplex", MockInputContext.mockFileText ]
let expected = NameValueLookup.ofList [ "uploadFileComplex", MockInputContext.mockFileTextAndContentType ]
let result = execute mutationComplexObject
ensureDirect result <| fun data errors ->
empty errors
Expand All @@ -109,7 +111,7 @@ let ``File type: Must upload a file as input object field using inline string a

[<Fact>]
let ``File type: Must upload two files as input object field using inline string a file name`` () =
let expectedContent = MockInputContext.mockFileText + MockInputContext.mockFileText2
let expectedContent = MockInputContext.mockFileTextAndContentType + MockInputContext.mockFileText2AndContentType
let expected = NameValueLookup.ofList [
"uploadFileComplex", expectedContent
]
Expand Down
8 changes: 6 additions & 2 deletions tests/FSharp.Data.GraphQL.Tests/Helpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,15 @@ type ExecutorExtensions =

module MockInputContext =

let mockContentType = System.Net.Mime.MediaTypeNames.Text.Plain
let mockFileKey = "fileKey"
let mockFileKey2 = "fileKey2"
let mockFileText = "fileText"
let mockFileText2 = "fileText2"

let mockFileTextAndContentType = mockFileText + mockContentType
let mockFileText2AndContentType = mockFileText2 + mockContentType

type MockInputExecutionContext () =

member _.FileKey = mockFileKey
Expand All @@ -207,9 +211,9 @@ module MockInputContext =
interface IInputExecutionContext with
member context.GetFile key =
if (key = context.FileKey) then
Ok context.Stream
Ok { Stream = context.Stream; ContentType = mockContentType }
else if (key = context.FileKey2) then
Ok context.Stream2
Ok { Stream = context.Stream2; ContentType = mockContentType }
else
failwith $"only file {context.FileKey} and file {context.FileKey2} exist"

Expand Down