Skip to content

add file upload and download endpoints to the test controller#60

Closed
baronfel wants to merge 8 commits intofsprojects:masterfrom
baronfel:files
Closed

add file upload and download endpoints to the test controller#60
baronfel wants to merge 8 commits intofsprojects:masterfrom
baronfel:files

Conversation

@baronfel
Copy link
Contributor

@baronfel baronfel commented Dec 4, 2016

I had to just make some stubs and then register their correct output schema formats after checking to see how Swashbuckle itself does it for their testing. So it seems there's not a unified useful way to represent a 'file' yet.

but this at least generates the expected swagger formats in the doc, so it can be used as input when testing the provider.

See
swashbuckle file download controller
swashbuckle file upload controller
swashbuckle file upload registrations
swashbuckle file download registrations

I had to add more to our 'file download' registration because the key is to have the response be of type 'file' as well, so we can key Swagger Provider off of that.

@baronfel
Copy link
Contributor Author

baronfel commented Apr 9, 2017

I've started trialing using Http.fs on this branch. I've converted the existing codebase over and been able to run all the tests with the exception of 4 that use the query string to send arrays of items, which Http.fs doesn't support. I've sent a PR lifting this restriction, at which point all the existing tests should work.

However, this only works with some changes I made to the typeprovider starter pack. In ProvidedTypes.fs/emitC, I added a branch that checks if the incoming type is a union and constructs an instance of the value. The tests on CI will fail with messages like 'Unknown constant Post in generated method, because they're reaching in to the Http.fslib to grab the DU caseHttpfs.Client.HttpMethod.Post`, and this extension does the IL necessary to provide this member. My extension would start at line 1101 of ProvidedTypes.fs, and looks like this:

                | t when FSharp.Reflection.FSharpType.IsUnion (v.GetType()) -> 
                    let case, fields = FSharp.Reflection.FSharpValue.GetUnionFields (v, v.GetType())
                    let caseC = FSharp.Reflection.FSharpValue.PreComputeUnionConstructorInfo(case)
                    for field in fields do
                        emitC field
                    ilg.EmitCall(OpCodes.Call, caseC, [||])   

With that branch added the code compiles and the tests mostly pass! Does any of that make sense to you? I was thinking about opening a PR to the TypeProvider Starter Pack repo seeing if anyone else wanted this.

@baronfel
Copy link
Contributor Author

baronfel commented Apr 9, 2017

Latest commits have pointed the type provider files to my fork which has the union code added, so builds should now pass on CI. Tests however will mostly fail for the reasons described above.

@sergey-tihon
Copy link
Member

sergey-tihon commented Apr 9, 2017

@baronfel thank you for all your effort here!

You definitely could open PR to FSharp.TypeProviders.StarterPack - it deserves discussion. I do not personally know, why current TP API does not allow emitting usage of DU cases....

I am not sure about Httpfs+Hopac... It is definitely an option, but I think that goal for Swagger Provider is wide platform support - not only net40 but pcl(xamarin) as well, and Fable one day. Neither Httpfs nor Hopac provide pcl compatible binaries as of today.

The primary reason behind Httpfs is nice F#-friendly API

It isn't intended as a high-performance library, usability from F# has been the goal. It shouldn't be much worse than HttpWebRequest, but you'd have to test it if that was important.

But we are not going Httpfs(and Hopac) to TP users, all these usability improvements will be hidden behind provided method.

The only runtime dependency that we really need is serializer and for not it is JSON.NET, all other libraries are really optional we should carefully decide which ones we really need.

@baronfel In your opinion, how hard will be to implement our own http communication directly based on HttpWebRequest? I guess that we use pretty small subset of Httpfs /Http.fs features

@baronfel
Copy link
Contributor Author

baronfel commented Apr 9, 2017

It's no big deal to not use HttpFs, that was mostly me trying to see if I understood how the project worked well enough to do such a drastic change.

If we don't want to use HttpFs, which is perfectly reasonable as it does bring in hopac too :), then I'll just continue with implementing multipart uploads on FSharp.Data so that more than just us can use it. It shouldn't block us here because I can paket include my fork until it gets merged in.

As a result we won't need the type provider union changes, but I'll open a PR for that as well to see what the community thinks.

Thanks for the checkup and sanity check, Sergey!

@sergey-tihon
Copy link
Member

Sometimes I not super responsive on GitHub, but this topic is definitely interesting for me.
Keep me posted please.

@baronfel
Copy link
Contributor Author

baronfel commented Jun 3, 2017

Finally finished the extension to FSharp.Data.Http that adds support for Multipart form data, that's in fsprojects/FSharp.Data#1056. Once that is merged we can update paket references and continue this work.

@sergey-tihon
Copy link
Member

@baronfel why did you close this PR?

@baronfel
Copy link
Contributor Author

baronfel commented Jun 12, 2017 via email

@baronfel baronfel reopened this Jun 25, 2017
@baronfel
Copy link
Contributor Author

@sergey-tihon any tips on live-debugging type providers on mono?

@sergey-tihon
Copy link
Member

I did not do that on Mono. Only did attach to VS from another instance to VS.

In theory, you could use VS Code to attach to some Ionide process (it should run fsautocomplete somewhere). Or attach from one instance of VS for Mac to another...

@baronfel maybe you already found the solution how to do this? 😃

@baronfel
Copy link
Contributor Author

baronfel commented Sep 6, 2017

GOT IT!

At freaking last.

I cleaned up some of the operation code a bit to make it hopefully easier to follow, but I'll dump some comments inline.

The tests all run and pass!


github fsharp/FSharp.Data src/Net/UriUtils.fs
github fsharp/FSharp.Data src/Net/Http.fs
github baronfel/FSharp.Data:multipart_form_data src/Net/Http.fs
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still waiting on a merge for FSharp.Data at the moment

open System
open System.Reflection

type FileWrapper(fileName: string, data: IO.Stream) =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to make this a tuple of these two pieces of data but that failed for some reason.

open System.Text.RegularExpressions
open System.IO

type BodyType =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modeling the body content like this made it easier to switch between formdata and multipart form data when we get parameters lists that have normal form data and files

| Some ty -> defCompiler.CompileTy methodName "Response" ty true
| None -> typeof<unit>
| None -> typeof<unit>, false
| Some ty when ty = SchemaObject.File ->
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is sort of a hack because I didn't yet want to look at making the definitions compiler learn that the same swagger type (in this case File), could have different representations on the input and output side.

let headers =
let jsonConsumable = op.Consumes |> Seq.exists (fun mt -> mt="application/json")
<@@
<@
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generally got away from the untyped expression API as much as possible in this PR.

| _ -> Some (FormData, <@@ (seq [name, (%var : string)]) @@>)
| Some (BodyForm f) ->
Some (BodyForm <@ Seq.append %f [name, %var()] @>)
| Some (BodyForm f) when param.Type = SchemaObject.File ->
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we get a file param, now we have to switch to multipart. for this we have to convert the previous entries into the contentTypefileNamedata structure that files have

if isReturnFile
then box stream
else
let reader = new StreamReader(stream)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can't use a use here because the finally branch goes all wonky.

let value =
<@@
let stream = (%result).ResponseStream
if isReturnFile
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like how I'm explicitly checking here.

I also don't like how I'm just returning a stream. I think I'd like to see if we can default some kind of file name, perhaps from the Content-Disposition header of the response if it exists?


testCase "file Upload" <| fun _ ->
try
let result = store.UploadFile(6L, "thing", new FileWrapper("thing.jpg", new MemoryStream(Text.Encoding.UTF8.GetBytes("hello!"))))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FileWrapper is a leaky type, I'd like to not have to expose it. Thoughts?

</dependentAssembly>
</assemblyBinding></runtime>
</configuration> No newline at end of file
</assemblyBinding></runtime></configuration> No newline at end of file
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Binding redirect changes came from paket for some reason.

@baronfel
Copy link
Contributor Author

baronfel commented Sep 28, 2017

Ping! I've changed this to point back to official FSharp.Data sources now that the Http Multipart Data work was merged.

@sergey-tihon
Copy link
Member

@baronfel does it ready for review/merge?

@baronfel
Copy link
Contributor Author

Hah! Apparently not. Let me see if I can fix that test.

@panesofglass
Copy link

Is this going to continue forward? @baronfel it sounds like from #92 that you are replacing the HTTP bits with HttpClient, which would be necessary for #28.

@baronfel
Copy link
Contributor Author

That's a good question, and one that gets harder if we support openapi 2 and 3 in this library. 3 drastically changed the file schema for the better, but 2 was very very particular in how items could be sent...

It's something I want to get back to, just have to find the time.

@sergey-tihon
Copy link
Member

As I understand it now, Microsoft.OpenApi.Readers support v2 and v3 schemas and parse both in v3 object model. So when we do #98 we will be able to think in v3 object model.

@baronfel
Copy link
Contributor Author

That's great news!

@baronfel
Copy link
Contributor Author

closing in favor of new PR

@baronfel baronfel closed this Mar 14, 2018
@baronfel baronfel mentioned this pull request Mar 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants