Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: create a copy for CODAP v3 #356

Merged
merged 2 commits into from
Feb 21, 2024
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
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ export interface CFMAppOptions {
// true if the application sets the page title
// false (default) if CFM sets page title from document name
appSetsWindowTitle?: boolean
// true if the content stored to file/disk should be wrapped with CFM metadata
// false if the content should be unwrapped before storing to file/disk
wrapFileContent?: boolean
mimeType?: string
// note different capitalization from CFMBaseProviderOptions
Expand Down
2 changes: 1 addition & 1 deletion src/code/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ class CloudFileManagerClient {
this.appOptions = appOptions
if (this.appOptions.wrapFileContent == null) { this.appOptions.wrapFileContent = true }
CloudContent.wrapFileContent = this.appOptions.wrapFileContent
if (this.appOptions.isClientContent) cloudContentFactory.isClientContent = this.appOptions.isClientContent
if (this.appOptions.isClientContent) CloudContent.isClientContent = this.appOptions.isClientContent

type ProviderClass = any
const allProviders: Record<string, ProviderClass> = {}
Expand Down
33 changes: 33 additions & 0 deletions src/code/providers/provider-interface.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { CloudContent, cloudContentFactory } from "./provider-interface"

describe("ProviderInterface", () => {

it("wraps/unwraps client content with `metadata` property (e.g. CODAP v2)", () => {
const docContent = { metadata: {} }
expect(CloudContent.isClientContent(docContent)).toBe(true)
const wrappedContent = cloudContentFactory.createEnvelopedCloudContent(docContent)
const unwrappedContent = wrappedContent.getClientContent()
expect(unwrappedContent).toEqual(docContent)
expect(CloudContent.isClientContent(unwrappedContent)).toBe(true)
})

it("can't wrap/unwrap client content with `content` property (e.g. CODAP v3) without isClientContent", () => {
const docContent = { content: { isContent: true } }
const wrappedContent = cloudContentFactory.createEnvelopedCloudContent(docContent)
const unwrappedContentFail = wrappedContent.getClientContent()
// without the isClientContent override, unwrapping fails
expect(unwrappedContentFail).not.toEqual(docContent)
})

it("wraps/unwraps client content with `content` property (e.g. CODAP v3) with isClientContent", () => {
const docContent = { content: { isContent: true } }
// with the isClientContent override, unwrapping succeeds
CloudContent.isClientContent = (inContent: any) => !!inContent?.content?.isContent
expect(CloudContent.isClientContent(docContent)).toBe(true)
const wrappedContent = cloudContentFactory.createEnvelopedCloudContent(docContent)
const unwrappedContent = wrappedContent.getClientContent()
expect(CloudContent.isClientContent(unwrappedContent)).toBe(true)
expect(unwrappedContent).toEqual(docContent)
})

})
37 changes: 23 additions & 14 deletions src/code/providers/provider-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,14 +166,8 @@ interface IEnvelopeMetaData {

// singleton that can create CloudContent wrapped with global options
class CloudContentFactory {
// For backward compatibility, by default we assume that a top-level `metadata`
// property indicates an unwrapped client document (e.g. CODAP v2). Clients can
// override this assumption with the `isClientContent` configuration option.
isClientContent = (content: unknown) => {
return typeof content === "object" && "metadata" in content && !!content.metadata
}

envelopeMetadata: IEnvelopeMetaData

constructor() {
this.envelopeMetadata = {
// replaced by version number at build time
Expand Down Expand Up @@ -215,7 +209,7 @@ class CloudContentFactory {
}
}
// If looks like client content, then it's neither wrapped nor pre-CFM.
if (this.isClientContent(content)) {
if (CloudContent.isClientContent(content)) {
return result
}
if (
Expand All @@ -237,7 +231,7 @@ class CloudContentFactory {
// noop, just checking if it's json or plain text
}
}
if ((typeof content === "object") && (content?.content != null)) {
if ((typeof content === "object") && (content?.content != null) && !CloudContent.isClientContent(content)) {
return content
} else {
return {content}
Expand All @@ -251,8 +245,18 @@ export interface CloudContentFormat {
}

class CloudContent {
// Client content is always wrapped by the CFM while it is being handled internally.
// This setting controls whether content is stored to file/disk in its wrapped form
// or whether it should be unwrapped before serializing to file/disk.
static wrapFileContent: boolean = true

// For backward compatibility, by default we assume that a top-level `metadata`
// property indicates an unwrapped client document (e.g. CODAP v2). Clients can
// override this assumption with the `isClientContent` configuration option.
static isClientContent = (content: unknown) => {
return typeof content === "object" && "metadata" in content && !!content.metadata
}

// TODO: These should probably be private, but there is some refactoring
// that has to happen to make this possible
cfmVersion?: string
Expand All @@ -264,7 +268,8 @@ class CloudContent {
this.contentFormat = contentFormat
}

// getContent and getContentAsJSON return the file content as stored on disk
// getContent and getContentAsJSON return the file content as stored on disk.
// They are expected to be called on internally wrapped content.
getContent() {
return CloudContent.wrapFileContent
? this.content
Expand All @@ -275,11 +280,15 @@ class CloudContent {
return JSON.stringify(this.getContent())
}

// returns the client-visible content (excluding wrapper for wrapped clients)
// Returns the client-visible content (excluding wrapper).
// Note that this can be called with wrapped or unwrapped content independent of the `wrapFileContent`
// setting, because CFM wraps content internally, so we need to inspect the content.
getClientContent() {
return CloudContent.wrapFileContent
? this.content.content
: this.content
// if we can specifically identify client content, then return it
if (CloudContent.isClientContent(this.content?.content)) return this.content.content
if (CloudContent.isClientContent(this.content)) return this.content
// otherwise, assume that a nested `content` property means we are wrapped
return this.content?.content ?? this.content
}

requiresConversion() {
Expand Down
Loading