From b197a114c05c8add557e6bf3dfd7681f7af4b5a5 Mon Sep 17 00:00:00 2001 From: Donald Gray Date: Wed, 9 Aug 2023 17:09:43 +0100 Subject: [PATCH 1/2] Move EnsureContext method to own class + add imageApi to known list --- src/IIIF/IIIF.Tests/ContextHelperTests.cs | 108 ++++++++++++++++++++++ src/IIIF/IIIF.Tests/Usings.cs | 2 + src/IIIF/IIIF/ContextHelper.cs | 61 ++++++++++++ src/IIIF/IIIF/Presentation/Context.cs | 56 +---------- 4 files changed, 172 insertions(+), 55 deletions(-) create mode 100644 src/IIIF/IIIF.Tests/ContextHelperTests.cs create mode 100644 src/IIIF/IIIF.Tests/Usings.cs create mode 100644 src/IIIF/IIIF/ContextHelper.cs diff --git a/src/IIIF/IIIF.Tests/ContextHelperTests.cs b/src/IIIF/IIIF.Tests/ContextHelperTests.cs new file mode 100644 index 0000000..40d623d --- /dev/null +++ b/src/IIIF/IIIF.Tests/ContextHelperTests.cs @@ -0,0 +1,108 @@ +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() + .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 { context, customContext }; + + // Act + jsonLdBase.EnsureContext(customContext); + + // Assert + (jsonLdBase.Context as List).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 { customContext, iiifContext }; + + // Act + jsonLdBase.EnsureContext(customContext); + + // Assert + (jsonLdBase.Context as List).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 { context, customContext, iiifContext }; + + // Act + jsonLdBase.EnsureContext(iiifContext); + jsonLdBase.EnsureContext(customContext); + + // Assert + (jsonLdBase.Context as List).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(); + } + + public static IEnumerable SampleContexts => + new List + { + 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 + { + } +} \ No newline at end of file diff --git a/src/IIIF/IIIF.Tests/Usings.cs b/src/IIIF/IIIF.Tests/Usings.cs new file mode 100644 index 0000000..a9ca8da --- /dev/null +++ b/src/IIIF/IIIF.Tests/Usings.cs @@ -0,0 +1,2 @@ +global using FluentAssertions; +global using Xunit; \ No newline at end of file diff --git a/src/IIIF/IIIF/ContextHelper.cs b/src/IIIF/IIIF/ContextHelper.cs new file mode 100644 index 0000000..c87551d --- /dev/null +++ b/src/IIIF/IIIF/ContextHelper.cs @@ -0,0 +1,61 @@ +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 knownContexts = new() + { + Presentation.Context.Presentation2Context, + Presentation.Context.Presentation3Context, + ImageApi.V2.ImageService2.Image2Context, + ImageApi.V3.ImageService3.Image3Context, + }; + + /// + /// Adds specified Context to list. + /// 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; + } + + var workingContexts = GetWorkingContexts(resource); + 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.Remove(iiifContext); + workingContexts.Add(iiifContext); + } + + // Now JSON-LD rules. The @context is the only Presentation 3 element that has this. + resource.Context = workingContexts.Count == 1 ? workingContexts[0] : workingContexts; + } + + private static List GetWorkingContexts(JsonLdBase resource) + { + if (resource.Context is List existingContexts) return existingContexts; + + if (resource.Context is string singleContext) return new List { singleContext }; + + return new List(1); + } +} \ No newline at end of file diff --git a/src/IIIF/IIIF/Presentation/Context.cs b/src/IIIF/IIIF/Presentation/Context.cs index c394e52..a1f89c3 100644 --- a/src/IIIF/IIIF/Presentation/Context.cs +++ b/src/IIIF/IIIF/Presentation/Context.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; - -namespace IIIF.Presentation; +namespace IIIF.Presentation; /// /// Contains JSON-LD Contexts for IIIF Presentation API. @@ -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 workingContexts = new(); - if (resource.Context is List existingContexts) workingContexts = existingContexts; - - if (resource.Context is string singleContext) workingContexts = new List { singleContext }; - - List 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; - } } \ No newline at end of file From ea0db1afb58eb18c76e4a7af8d2479a86eda089a Mon Sep 17 00:00:00 2001 From: Donald Gray Date: Wed, 9 Aug 2023 17:37:50 +0100 Subject: [PATCH 2/2] EnsureContext noop of context already exists --- src/IIIF/IIIF.Tests/ContextHelperTests.cs | 41 +++++++++++++++++++++++ src/IIIF/IIIF/ContextHelper.cs | 13 +++++-- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/IIIF/IIIF.Tests/ContextHelperTests.cs b/src/IIIF/IIIF.Tests/ContextHelperTests.cs index 40d623d..15f7d4c 100644 --- a/src/IIIF/IIIF.Tests/ContextHelperTests.cs +++ b/src/IIIF/IIIF.Tests/ContextHelperTests.cs @@ -36,6 +36,47 @@ public void EnsureContext_AddsContext_IfOneExisting_WithoutReordering() // Assert (jsonLdBase.Context as List).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() + .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 { customContext, context }; + + // Act + jsonLdBase.EnsureContext(customContext); + + // Assert + (jsonLdBase.Context as List).Should().ContainInOrder(expected); + } [Theory] [InlineData(IIIF.Presentation.Context.Presentation2Context)] diff --git a/src/IIIF/IIIF/ContextHelper.cs b/src/IIIF/IIIF/ContextHelper.cs index c87551d..5413052 100644 --- a/src/IIIF/IIIF/ContextHelper.cs +++ b/src/IIIF/IIIF/ContextHelper.cs @@ -29,6 +29,12 @@ public static void EnsureContext(this JsonLdBase resource, string contextToEnsur } 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) @@ -40,16 +46,19 @@ public static void EnsureContext(this JsonLdBase resource, string contextToEnsur var iiifContext = workingContexts.FirstOrDefault(wc => knownContexts.Contains(wc)); // If we have an IIIF context it must be last in list - if (!string.IsNullOrEmpty(iiifContext)) + 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. - resource.Context = workingContexts.Count == 1 ? workingContexts[0] : workingContexts; + SetContext(resource, workingContexts); } + private static void SetContext(JsonLdBase resource, IReadOnlyList workingContexts) + => resource.Context = workingContexts.Count == 1 ? workingContexts[0] : workingContexts; + private static List GetWorkingContexts(JsonLdBase resource) { if (resource.Context is List existingContexts) return existingContexts;