Skip to content

Commit

Permalink
Merge pull request #41 from digirati-co-uk/feature/imageapi_ensure_co…
Browse files Browse the repository at this point in the history
…ntext

Move EnsureContext method to own class + add imageApi to known list
  • Loading branch information
donaldgray committed Aug 10, 2023
2 parents 4250624 + ea0db1a commit b61d2a9
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 55 deletions.
149 changes: 149 additions & 0 deletions src/IIIF/IIIF.Tests/ContextHelperTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;

namespace IIIF.Tests;

public class ContextHelperTests
{
[Fact]
public void EnsureContext_AddsContext_IfNoExisting()
{
// Arrange
var jsonLdBase = new TestJsonLdBase();
const string customContext = "http://my-custom-context";

// Act
jsonLdBase.EnsureContext(customContext);

// Assert
jsonLdBase.Context.Should().BeOfType<string>()
.And.Subject.Should().Be(customContext);
}

[Fact]
public void EnsureContext_AddsContext_IfOneExisting_WithoutReordering()
{
// Arrange
const string context = "http://existing-context";
const string customContext = "http://my-custom-context";
var jsonLdBase = new TestJsonLdBase { Context = context };

var expected = new List<string> { context, customContext };

// Act
jsonLdBase.EnsureContext(customContext);

// Assert
(jsonLdBase.Context as List<string>).Should().ContainInOrder(expected);
}

[Theory]
[InlineData(IIIF.Presentation.Context.Presentation2Context)]
[InlineData(IIIF.Presentation.Context.Presentation3Context)]
[InlineData(IIIF.ImageApi.V2.ImageService2.Image2Context)]
[InlineData(IIIF.ImageApi.V3.ImageService3.Image3Context)]
[InlineData("http://my-custom-context")]
public void EnsureContext_NoOp_IfContextAlreadyExistsAsSingle(string context)
{
// Arrange
var jsonLdBase = new TestJsonLdBase { Context = context };

// Act
jsonLdBase.EnsureContext(context);

// Assert
jsonLdBase.Context.Should().BeOfType<string>()
.And.Subject.Should().Be(context);
}

[Theory]
[InlineData(IIIF.Presentation.Context.Presentation2Context)]
[InlineData(IIIF.Presentation.Context.Presentation3Context)]
[InlineData(IIIF.ImageApi.V2.ImageService2.Image2Context)]
[InlineData(IIIF.ImageApi.V3.ImageService3.Image3Context)]
[InlineData("http://my-custom-context")]
public void EnsureContext_NoOp_IfContextAlreadyExistsInList(string context)
{
// Arrange
const string customContext = "http://existing-context";
var jsonLdBase = new TestJsonLdBase { Context = customContext };
jsonLdBase.EnsureContext(context);

var expected = new List<string> { customContext, context };

// Act
jsonLdBase.EnsureContext(customContext);

// Assert
(jsonLdBase.Context as List<string>).Should().ContainInOrder(expected);
}

[Theory]
[InlineData(IIIF.Presentation.Context.Presentation2Context)]
[InlineData(IIIF.Presentation.Context.Presentation3Context)]
[InlineData(IIIF.ImageApi.V2.ImageService2.Image2Context)]
[InlineData(IIIF.ImageApi.V3.ImageService3.Image3Context)]
public void EnsureContext_AlwaysReordersIIIFContextsToLast_IfOthersAddedAfter(string iiifContext)
{
// Arrange
const string customContext = "http://my-custom-context";
var jsonLdBase = new TestJsonLdBase { Context = iiifContext };

var expected = new List<string> { customContext, iiifContext };

// Act
jsonLdBase.EnsureContext(customContext);

// Assert
(jsonLdBase.Context as List<string>).Should().ContainInOrder(expected);
}

[Theory]
[InlineData(IIIF.Presentation.Context.Presentation2Context)]
[InlineData(IIIF.Presentation.Context.Presentation3Context)]
[InlineData(IIIF.ImageApi.V2.ImageService2.Image2Context)]
[InlineData(IIIF.ImageApi.V3.ImageService3.Image3Context)]
public void EnsureContext_AlwaysReordersIIIFContextsToLast_IfMultipleAdds(string iiifContext)
{
// Arrange
const string context = "http://existing-context";
const string customContext = "http://my-custom-context";
var jsonLdBase = new TestJsonLdBase { Context = context };

var expected = new List<string> { context, customContext, iiifContext };

// Act
jsonLdBase.EnsureContext(iiifContext);
jsonLdBase.EnsureContext(customContext);

// Assert
(jsonLdBase.Context as List<string>).Should().ContainInOrder(expected);
}

[Theory]
[MemberData(nameof(SampleContexts))]
public void EnsureContext_Throws_IfMultipleIIIFContextsAdded(string first, string second)
{
// Arrange
var jsonLdBase = new TestJsonLdBase { Context = first };

// Act
Action action = () => jsonLdBase.EnsureContext(second);

// Assert
action.Should().Throw<InvalidOperationException>();
}

public static IEnumerable<object[]> SampleContexts =>
new List<object[]>
{
new object[] { IIIF.Presentation.Context.Presentation2Context, IIIF.Presentation.Context.Presentation3Context },
new object[] { IIIF.ImageApi.V2.ImageService2.Image2Context, IIIF.ImageApi.V3.ImageService3.Image3Context },
new object[] { IIIF.Presentation.Context.Presentation2Context, IIIF.ImageApi.V3.ImageService3.Image3Context },
new object[] { IIIF.ImageApi.V3.ImageService3.Image3Context , IIIF.Presentation.Context.Presentation3Context },
};

private class TestJsonLdBase : JsonLdBase
{
}
}
2 changes: 2 additions & 0 deletions src/IIIF/IIIF.Tests/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
global using FluentAssertions;
global using Xunit;
70 changes: 70 additions & 0 deletions src/IIIF/IIIF/ContextHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace IIIF;

public static class ContextHelper
{
// This is a list of known IIIF contexts that must be last in Contexts list
// Only 1 can be present in any given Context list
private static List<string> knownContexts = new()
{
Presentation.Context.Presentation2Context,
Presentation.Context.Presentation3Context,
ImageApi.V2.ImageService2.Image2Context,
ImageApi.V3.ImageService3.Image3Context,
};

/// <summary>
/// Adds specified Context to list.
/// The IIIF context must be last in the list, to override any that come before it.
/// </summary>
public static void EnsureContext(this JsonLdBase resource, string contextToEnsure)
{
if (resource.Context == null)
{
resource.Context = contextToEnsure;
return;
}

var workingContexts = GetWorkingContexts(resource);
if (workingContexts.Contains(contextToEnsure))
{
// Context already added - no-op
SetContext(resource, workingContexts);
return;
}
workingContexts.Add(contextToEnsure);

if (workingContexts.Intersect(knownContexts).Count() > 1)
{
throw new InvalidOperationException(
"You cannot have multiple IIIF contexts (Presentation or Image) in the same resource.");
}

var iiifContext = workingContexts.FirstOrDefault(wc => knownContexts.Contains(wc));

// If we have an IIIF context it must be last in list
if (!string.IsNullOrEmpty(iiifContext) && workingContexts.Count > 1)
{
workingContexts.Remove(iiifContext);
workingContexts.Add(iiifContext);
}

// Now JSON-LD rules. The @context is the only Presentation 3 element that has this.
SetContext(resource, workingContexts);
}

private static void SetContext(JsonLdBase resource, IReadOnlyList<string> workingContexts)
=> resource.Context = workingContexts.Count == 1 ? workingContexts[0] : workingContexts;

private static List<string> GetWorkingContexts(JsonLdBase resource)
{
if (resource.Context is List<string> existingContexts) return existingContexts;

if (resource.Context is string singleContext) return new List<string> { singleContext };

return new List<string>(1);
}
}
56 changes: 1 addition & 55 deletions src/IIIF/IIIF/Presentation/Context.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;

namespace IIIF.Presentation;
namespace IIIF.Presentation;

/// <summary>
/// Contains JSON-LD Contexts for IIIF Presentation API.
Expand All @@ -27,55 +24,4 @@ public static void EnsurePresentation2Context(this JsonLdBase resource)
{
resource.EnsureContext(Presentation2Context);
}

// The IIIF context must be last in the list, to override any that come before it.
public static void EnsureContext(this JsonLdBase resource, string contextToEnsure)
{
if (resource.Context == null)
{
resource.Context = contextToEnsure;
return;
}

List<string> workingContexts = new();
if (resource.Context is List<string> existingContexts) workingContexts = existingContexts;

if (resource.Context is string singleContext) workingContexts = new List<string> { singleContext };

List<string> newContexts = new();
var requiresPresentation3Context = contextToEnsure == Presentation3Context;
var requiresPresentation2Context = contextToEnsure == Presentation2Context;
foreach (var workingContext in workingContexts)
switch (workingContext)
{
case Presentation3Context:
requiresPresentation3Context = true;
break;
case Presentation2Context:
requiresPresentation2Context = true;
break;
default:
newContexts.Add(workingContext);
break;
}

// Add the new context to the list but not if it supposed to come last
if (!newContexts.Contains(contextToEnsure)
&& contextToEnsure != Presentation3Context
&& contextToEnsure != Presentation2Context)
newContexts.Add(contextToEnsure);

if (requiresPresentation2Context && requiresPresentation3Context)
throw new InvalidOperationException(
"You cannot have Presentation 2 and Presentation 3 contexts in the same resource.");
// These have to come last
if (requiresPresentation3Context) newContexts.Add(Presentation3Context);
if (requiresPresentation2Context) newContexts.Add(Presentation2Context);

// Now JSON-LD rules. The @context is the only Presentation 3 element that has this.
if (newContexts.Count == 1)
resource.Context = newContexts[0];
else
resource.Context = newContexts;
}
}

0 comments on commit b61d2a9

Please sign in to comment.