Skip to content

Commit

Permalink
Initial support for update/delete
Browse files Browse the repository at this point in the history
Also returning the correct http status code.
  • Loading branch information
hammett committed Apr 19, 2012
1 parent 6729d80 commit 49eddd3
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 104 deletions.
Expand Up @@ -94,11 +94,12 @@
else
// todo: execute result?
false
let invoke_controller_key = invoke_controller

let callbacks = {
accessSingle = Func<ResourceType,obj,bool>(fun rt o -> invoke_controller "Access" false rt o true);
accessMany = Func<ResourceType,IEnumerable,bool>(fun rt o -> invoke_controller "AccessMany" true rt o true);
create = Func<ResourceType,obj,bool>(fun rt o -> invoke_controller "Create" false rt o false);
create = Func<ResourceType,obj,bool * string>(fun rt o -> invoke_controller_key "Create" false rt o false, "");
update = Func<ResourceType,obj,bool>(fun rt o -> invoke_controller "Update" false rt o false);
remove = Func<ResourceType,obj,bool>(fun rt o -> invoke_controller "Remove" false rt o false);
}
Expand All @@ -117,6 +118,8 @@
contentEncoding = response.ContentEncoding;
writer = writer;
httpStatus = 200;
httpStatusDesc = "OK";
location = null;
}

try
Expand All @@ -125,10 +128,15 @@

SegmentProcessor.Process op segments callbacks requestParams responseParams

if responseParams.httpStatus <> 200 then
response.StatusCode <- responseParams.httpStatus
response.StatusDescription <- responseParams.httpStatusDesc
if not <| String.IsNullOrEmpty responseParams.contentType then
response.ContentType <- responseParams.contentType
if responseParams.contentEncoding <> null then
response.ContentEncoding <- responseParams.contentEncoding
if responseParams.location <> null then
response.AddHeader("Location", responseParams.location)

EmptyResult.Instance

Expand Down
Expand Up @@ -26,7 +26,7 @@ type SegmentOp =
type ProcessorCallbacks = {
accessSingle : Func<ResourceType, obj, bool>;
accessMany : Func<ResourceType, IEnumerable, bool>;
create : Func<ResourceType, obj, bool>;
create : Func<ResourceType, obj, bool * string>;
update : Func<ResourceType, obj, bool>;
remove : Func<ResourceType, obj, bool>;
}
Expand All @@ -47,10 +47,17 @@ type ResponseParameters = {
mutable contentEncoding : Encoding;
writer : TextWriter;
mutable httpStatus : int;
mutable httpStatusDesc : string;
mutable location : string;
}

module SegmentProcessor =
begin
type ResponseParameters with
member x.SetStatus(code:int, desc:string) =
x.httpStatus <- code
x.httpStatusDesc <- desc

let internal emptyResponse = { QItems = null; EItems = null; SingleResult = null; ResType = null; FinalResourceUri=null; ResProp = null }

let (|HttpGet|HttpPost|HttpPut|HttpDelete|HttpMerge|HttpHead|) (arg:string) =
Expand Down Expand Up @@ -115,7 +122,8 @@ module SegmentProcessor =


let internal process_collection_property op container (p:PropertyAccessInfo) (previous:UriSegment) hasMoreSegments
(model:ODataModel) (callbacks:ProcessorCallbacks) (request:RequestParameters)
(model:ODataModel) (callbacks:ProcessorCallbacks)
(request:RequestParameters) (response:ResponseParameters)
(shouldContinue:Ref<bool>) =
System.Diagnostics.Debug.Assert ((match previous with | UriSegment.Nothing -> false | _ -> true), "cannot be root")

Expand All @@ -139,22 +147,25 @@ module SegmentProcessor =
| _ -> ()

let input = deserialize_input p.ResourceType request
if callbacks.create.Invoke(p.ResourceType, input) then
()
else shouldContinue := false

emptyResponse
let succ, key = callbacks.create.Invoke(p.ResourceType, input)
if succ then
response.SetStatus(201, "Created")
response.location <- Uri(request.baseUri, p.Uri.OriginalString + "(" + key + ")").AbsoluteUri

{ ResType = p.ResourceType;
QItems = null; EItems = null; SingleResult = input;
FinalResourceUri = p.Uri; ResProp = null }
else
shouldContinue := false
emptyResponse

| SegmentOp.Update ->
// deserialize
// process
// result
raise(NotImplementedException("Update for property not supported yet"))
| _ -> failwithf "Unsupported operation %O" op


let internal process_item_property op container (p:PropertyAccessInfo) (previous:UriSegment) hasMoreSegments
(model:ODataModel) (callbacks:ProcessorCallbacks) (shouldContinue:Ref<bool>) =
(model:ODataModel) (callbacks:ProcessorCallbacks) (shouldContinue:Ref<bool>)
(requestParams:RequestParameters) (responseParams:ResponseParameters) =
System.Diagnostics.Debug.Assert ((match previous with | UriSegment.Nothing -> false | _ -> true), "cannot be root")

if op = SegmentOp.View || (hasMoreSegments && op = SegmentOp.Update) then
Expand All @@ -170,8 +181,8 @@ module SegmentProcessor =
if callbacks.accessSingle.Invoke(p.ResourceType, finalValue) then
p.SingleResult <- finalValue
{ ResType = p.ResourceType;
QItems = null; EItems = null; SingleResult = finalValue;
FinalResourceUri = p.Uri; ResProp = p.Property }
QItems = null; EItems = null; SingleResult = finalValue;
FinalResourceUri = p.Uri; ResProp = p.Property }
else emptyResponse

else
Expand All @@ -185,7 +196,8 @@ module SegmentProcessor =


let internal process_entityset op (d:EntityAccessInfo) (previous:UriSegment) hasMoreSegments
(model:ODataModel) (callbacks:ProcessorCallbacks) (shouldContinue:Ref<bool>) requestParams =
(model:ODataModel) (callbacks:ProcessorCallbacks) (shouldContinue:Ref<bool>)
(request:RequestParameters) (response:ResponseParameters) =
System.Diagnostics.Debug.Assert ((match previous with | UriSegment.Nothing -> true | _ -> false), "must be root")

// System.Diagnostics.Debug.Assert (not hasMoreSegments)
Expand All @@ -205,38 +217,30 @@ module SegmentProcessor =
| SegmentOp.Create ->
System.Diagnostics.Debug.Assert (not hasMoreSegments)

let item = deserialize_input d.ResourceType requestParams
if callbacks.create.Invoke(d.ResourceType, item) then
()
else shouldContinue := false

emptyResponse
let item = deserialize_input d.ResourceType request

| SegmentOp.Update ->
System.Diagnostics.Debug.Assert (not hasMoreSegments)
// deserialize
// process
// result
emptyResponse
let succ, key = callbacks.create.Invoke(d.ResourceType, item)
if succ then
response.SetStatus(201, "Created")
response.location <- Uri(request.baseUri, d.Uri.OriginalString + "(" + key + ")").AbsoluteUri

{ ResType = d.ResourceType;
QItems = null; EItems = null; SingleResult = item;
FinalResourceUri = d.Uri; ResProp = null }
else
shouldContinue := false
emptyResponse

| SegmentOp.Delete ->
System.Diagnostics.Debug.Assert (not hasMoreSegments)
// process
// result
emptyResponse

| _ -> failwithf "Unsupported operation %O" op


let internal process_entityset_single op (d:EntityAccessInfo) (previous:UriSegment) hasMoreSegments
(model:ODataModel) (callbacks:ProcessorCallbacks) (shouldContinue:Ref<bool>) stream =

(model:ODataModel) (callbacks:ProcessorCallbacks) (shouldContinue:Ref<bool>)
(request:RequestParameters) (response:ResponseParameters) =
System.Diagnostics.Debug.Assert ((match previous with | UriSegment.Nothing -> true | _ -> false), "must be root")

if op = SegmentOp.View || hasMoreSegments then
if not hasMoreSegments then
System.Diagnostics.Debug.Assert (not (op = SegmentOp.Delete), "should not be delete")

let get_single_result () =
// if there are more segments, consider this a read
let wholeSet = model.GetQueryable (d.ResSet)
let singleResult = select_by_key d.ResourceType wholeSet d.Key
Expand All @@ -249,24 +253,45 @@ module SegmentProcessor =
shouldContinue := false
emptyResponse

else
if op = SegmentOp.View || hasMoreSegments then
if not hasMoreSegments then
System.Diagnostics.Debug.Assert (not (op = SegmentOp.Delete), "should not be delete")

get_single_result ()

else

match op with
| SegmentOp.Update ->
// deserialize
// process
// result

let item = deserialize_input d.ResourceType request

let succ = callbacks.update.Invoke(d.ResourceType, item)
if succ then
response.SetStatus(204, "No Content")
response.location <- d.Uri.AbsoluteUri
else shouldContinue := false

emptyResponse

| SegmentOp.Delete ->
// http://www.odata.org/developers/protocols/operations#DeletingEntries
// Entries are deleted by executing an HTTP DELETE request against a URI that points at the Entry.
// If the operation executed successfully servers should return 200 (OK) with no response body.
let result = get_single_result()

// process
// result
if result <> emptyResponse then
let single = result.SingleResult
if callbacks.remove.Invoke(d.ResourceType, single) then
response.SetStatus(204, "No Content")
response.location <- d.Uri.AbsoluteUri
else
shouldContinue := false

emptyResponse

| _ -> failwithf "Unsupported operation %O at this level" op



let internal serialize_directory op hasMoreSegments (previous:UriSegment) writer baseUri metadataProviderWrapper (response:ResponseParameters) =
Expand Down Expand Up @@ -336,7 +361,6 @@ module SegmentProcessor =
// in case of exception, serialized error is sent

let model = request.model
let stream = request.input
let baseUri = request.baseUri
let writer = response.writer
do response.contentType <- resolveResponseContentType segments request.accept
Expand Down Expand Up @@ -376,16 +400,16 @@ module SegmentProcessor =
emptyResponse

| UriSegment.EntitySet d ->
process_entityset op d previous hasMoreSegments model callbacks shouldContinue request
process_entityset op d previous hasMoreSegments model callbacks shouldContinue request response

| UriSegment.EntityType d ->
process_entityset_single op d previous hasMoreSegments model callbacks shouldContinue stream
process_entityset_single op d previous hasMoreSegments model callbacks shouldContinue request response

| UriSegment.PropertyAccessCollection d ->
process_collection_property op container d previous hasMoreSegments model callbacks request shouldContinue
process_collection_property op container d previous hasMoreSegments model callbacks request response shouldContinue

| UriSegment.ComplexType d | UriSegment.PropertyAccessSingle d ->
process_item_property op container d previous hasMoreSegments model callbacks shouldContinue
process_item_property op container d previous hasMoreSegments model callbacks shouldContinue request response

| _ -> Unchecked.defaultof<ResponseToSend>

Expand Down
Expand Up @@ -59,7 +59,6 @@
<Compile Include="AtomServiceDocSerializerTestCase.cs" />
<Compile Include="SegmentProcessorTestCase.Delete.cs" />
<Compile Include="SegmentProcessorTestCase.Update.cs" />
<Compile Include="SegmentProcessorTestCase.Callbacks.cs" />
<Compile Include="SegmentProcessorTestCase.EntityType.cs" />
<Compile Include="SegmentProcessorTestCase.PropColl.cs" />
<Compile Include="SegmentProcessorTestCase.PropSingle.cs" />
Expand Down

This file was deleted.

Expand Up @@ -11,8 +11,6 @@
using NUnit.Framework;




public partial class SegmentProcessorTestCase
{
// naming convention for testing methods
Expand All @@ -28,10 +26,11 @@ public void PropCollection_Create_Atom_Atom_Success()
IsCurated = true,
Name = "testing", Price = 2.3m
};
prod.ToSyndicationItem();

Process("/catalogs(1)/Products/", SegmentOp.Create, _modelWithMinimalContainer, inputStream: prod.ToSyndicationItem().ToStream() );

Assertion.ResponseIs(201, "application/atom+xml", location: "http://localhost/base/catalogs(1)/Products(0)");

// TODO: need to collect the containers, so controller can get all of them in the action call

Assertion.Callbacks.CreateWasCalled(1);
Expand All @@ -45,5 +44,32 @@ public void PropCollection_Create_Atom_Atom_Success()
deserializedProd.Price.Should().Be(prod.Price);
}

[Test, Description("Id for products needs to refer back to EntityContainer.Products")]
public void EntitySet_Create_Atom_Atom_Success()
{
var prod = new Product1()
{
Created = DateTime.Now,
Modified = DateTime.Now,
IsCurated = true,
Name = "testing",
Price = 2.3m
};

Process("/Products/", SegmentOp.Create, _model, inputStream: prod.ToSyndicationItem().ToStream());

Assertion.ResponseIs(201, "application/atom+xml", location: "http://localhost/base/Products(0)");

Assertion.Callbacks.CreateWasCalled(1);

var deserializedProd = (Product1)_created.ElementAt(0).Item2;

deserializedProd.Name.Should().Be(prod.Name);
deserializedProd.IsCurated.Should().Be(prod.IsCurated);
deserializedProd.Modified.Should().Be(prod.Modified);
deserializedProd.Created.Should().Be(prod.Created);
deserializedProd.Price.Should().Be(prod.Price);
}

}
}
Expand Up @@ -16,32 +16,25 @@ public partial class SegmentProcessorTestCase
// [EntitySet|EntityType|PropSingle|PropCollection|Complex|Primitive]_[Operation]_[InputFormat]_[OutputFormat]__[Success|Failure]

[Test, Description("The EntityContainer only has Catalog, so creation is for nested object")]
public void PropCollection_Delete_Atom_Atom_Success()
public void EntityType_PropertySingle_Delete__Success()
{
var prod = new Product1()
{
Id = 1,
Created = DateTime.Now,
Modified = DateTime.Now,
IsCurated = true,
Name = "testing", Price = 2.3m
};
prod.ToSyndicationItem();

Process("/catalogs(1)/Products(1)/", SegmentOp.Delete, _modelWithMinimalContainer, inputStream: prod.ToSyndicationItem().ToStream() );
Process("/catalogs(1)/Products(1)/", SegmentOp.Delete, _modelWithMinimalContainer );

// TODO: need to collect the containers, so controller can get all of them in the action call

Assertion.Callbacks.SingleWasCalled(2);
Assertion.Callbacks.RemoveWasCalled(1);
Assertion.ResponseIs(204);
}

var deserializedProd = (Product1) _created.ElementAt(0).Item2;
[Test]
public void EntitySetSingle_Delete__Success()
{
Process("/Products(1)/", SegmentOp.Delete, _model);

deserializedProd.Id.Should().Be(prod.Id);
deserializedProd.Name.Should().Be(prod.Name);
deserializedProd.IsCurated.Should().Be(prod.IsCurated);
deserializedProd.Modified.Should().Be(prod.Modified);
deserializedProd.Created.Should().Be(prod.Created);
deserializedProd.Price.Should().Be(prod.Price);
Assertion.Callbacks.SingleWasCalled(1);
Assertion.Callbacks.RemoveWasCalled(1);
Assertion.ResponseIs(204);
}

}
Expand Down

0 comments on commit 49eddd3

Please sign in to comment.