mirror of
https://github.com/foomo/gocontentful.git
synced 2025-10-16 12:25:39 +00:00
fix: switch to Mutex to avoid RUnlock panics, docs update
This commit is contained in:
parent
52ca38a8ea
commit
19740694eb
175
README.md
175
README.md
@ -10,15 +10,26 @@ A Contentful API code generator for Go. Initial features:
|
||||
Rationale
|
||||
-----------------------------------------
|
||||
|
||||
While an unofficial/experimental Go client by Contentful Labs has been available for a long time (see credits at the end of this document), working with the response of a JSON REST API is far from optimal.
|
||||
While an unofficial/experimental Go client by Contentful Labs has been available for a long time (see credits at the end
|
||||
of this document), working with the response of a JSON REST API is far from optimal.
|
||||
|
||||
Loading and converting data into (and from) Go value objects can quickly become a nightmare and the amount of code to access each field of each content type of a complex space can literally explode and if you're not careful how and when you interact with an API over the Internet, performance can be impacted dramatically. Not to mention what happens to your code if you need to make a significant change the space data model.
|
||||
Loading and converting data into (and from) Go value objects can quickly become a nightmare and the amount of code to
|
||||
access each field of each content type of a complex space can literally explode and if you're not careful how and when
|
||||
you interact with an API over the Internet, performance can be impacted dramatically. Not to mention the amount of
|
||||
changes you need to make to your code when you change the space data model.
|
||||
|
||||
Also, the response of the REST API is very nicely modeled for a dynamic language where _any type_ can be returned in a slice of objects. The strict typing of Go makes this tricky to manage and you end up writing (and re-writing) a lot of similar code.
|
||||
Also, the response of the REST API is very nicely modeled for a dynamic language where _any type_ can be returned in a
|
||||
slice of objects. The strict typing of Go makes this tricky to manage and you end up writing (and re-writing) a lot
|
||||
of similar code.
|
||||
|
||||
The scenario above suggested the need for a code generator that can scan a Contentful space and create a complete, native Go API to interact with the remote service and provide code that can be regenerated in some seconds when the data model change.
|
||||
The scenario above suggested the need for a code generator that can scan a Contentful space and create a complete,
|
||||
native and idiomatic Go API to interact with the remote service and provide code that can be regenerated in some
|
||||
seconds when the data model change.
|
||||
|
||||
How much code is that? As an example of a real-world scenario, a space content model with 11 content types with ranging from 3 to over 40 fields each generated 14,500 lines of Go code. Do you need all those lines? Yes, you do, otherwise it means you never need to read/write some of the content types and fields you defined in the model. In other words, your model needs some spring cleaning.
|
||||
How much code is that? As an example of a real-world production scenario where gocontentful is in use as of 2022,
|
||||
a space content model with 11 content types ranging from 3 to over 40 fields each generated around 50,000 lines of
|
||||
Go code. Do you need all those lines? You might not need all setters if you don't manage content through the API
|
||||
but you'll definitely need most of the getters otherwise those should not be in the model at all.
|
||||
|
||||
Quickstart
|
||||
-----------------------------------------
|
||||
@ -100,21 +111,33 @@ The script will scan the space, download locales and content types and generate
|
||||
|-gocontentfulvo.go
|
||||
</code></pre>
|
||||
|
||||
_Note: Do NOT modify these files! If you change the content model in Contentful you will need to run the generator again and overwrite the files._
|
||||
_Note: Do NOT modify these files! If you change the content model in Contentful you will need to run the generator
|
||||
again and overwrite the files._
|
||||
|
||||
### Get a client
|
||||
|
||||
The generated files will be in the "people" subdirectory of your project. Your go program can get a Contentful client from them:
|
||||
The generated files will be in the "people" subdirectory of your project. Your go program can get a Contentful
|
||||
client from them:
|
||||
|
||||
`cc, err := people.NewContentfulClient(YOUR_SPACE_ID, people.ClientModeCDA, YOUR_API_KEY, 1000, contentfulLogger, people.LogDebug,false)`
|
||||
|
||||
The parameters to pass to NewContentfulClient are:
|
||||
|
||||
- *spaceID* (string)
|
||||
- *clientMode* (string) supports the constants ClientModeCDA, ClientModeCPA and ClientModeCMA. If you need to operate on multiple APIs (e.g. one for reading and CMA for writing) you need to get two clients
|
||||
- *clientMode* (string) supports the constants ClientModeCDA, ClientModeCPA and ClientModeCMA. If you need to operate
|
||||
on multiple APIs (e.g. one for reading and CMA for writing) you need to get two clients
|
||||
- *clientKey* (string) is your API key (generate one for your API at Contentful)
|
||||
- *optimisticPageSize* (uint16) is the page size the client will use to download entries from the space for caching. Contentful's default is 100 but you can specify up to 1000: this might get you into an error because Contentful limits the payload response size to 70 KB but the client will handle the error and reduce the page size automatically until it finds a proper value. Hint: using a big page size that always fails is a waste of time and resources because a lot of initial calls will fail, whereas a too small one will not leverage the full download bandwidth. It's a trial-and-error and you need to find the best value for your case. For simple content types you can start with 1000, for very complex ones that include fat fields you might want to get down to 100 or even less.
|
||||
- *logFn* is a func(fields map[string]interface{}, level int, args ...interface{}) that the client will call whenever it needs to log something. It can be nil if you don't need logging and that will be handled gracefully but it's not recommended. A simple function you can pass that uses the https://github.com/Sirupsen/logrus package might look something like this:
|
||||
- *optimisticPageSize* (uint16) is the page size the client will use to download entries from the space for caching.
|
||||
Contentful's default is 100 but you can specify up to 1000: this might get you into an error because Contentful
|
||||
limits the payload response size to 70 KB but the client will handle the error and reduce the page size automatically
|
||||
until it finds a proper value. Hint: using a big page size that always fails is a waste of time and resources because
|
||||
a lot of initial calls will fail, whereas a too small one will not leverage the full download bandwidth. It's a
|
||||
trial-and-error and you need to find the best value for your case. For simple content types you can start with 1000,
|
||||
for very complex ones that include fat fields you might want to get down to 100 or even less.
|
||||
- *logFn* is a func(fields map[string]interface{}, level int, args ...interface{}) that the client will call whenever
|
||||
it needs to log something. It can be nil if you don't need logging and that will be handled gracefully but it's not
|
||||
recommended. A simple function you can pass that uses the https://github.com/Sirupsen/logrus package might look
|
||||
something like this:
|
||||
<pre><code>contentfulLogger := func(fields map[string]interface{}, level int, args ...interface{}) {
|
||||
switch level {
|
||||
case people.LogDebug:
|
||||
@ -130,10 +153,15 @@ The parameters to pass to NewContentfulClient are:
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
- *logLevel* (int) is the debug level (see function above). Please note that LogDebug is very verbose and even logs when you request a field value but that is not set for the entry.
|
||||
- *debug* (bool) is the Contentful API client debug switch. If set to *true* it will log on stdout all the CURL calls to Contentful. This is extremely verbose and extremely valuable when something fails in a call to the API because it's the only way to see the REST API response.
|
||||
- *logLevel* (int) is the debug level (see function above). Please note that LogDebug is very verbose and even logs
|
||||
- when you request a field value but that is not set for the entry.
|
||||
- *debug* (bool) is the Contentful API client debug switch. If set to *true* it will log on stdout all the CURL calls
|
||||
- to Contentful. This is extremely verbose and extremely valuable when something fails in a call to the API because
|
||||
- it's the only way to see the REST API response.
|
||||
|
||||
_NOTE:_ Gocontentful provides an offline version of the client that can load data from a JSON space export file (as exported by the _contentful_ CLI tool). This is the way you can write unit tests against your generated API that don't require to be online and the management of a safe API key storage. See function reference below
|
||||
_NOTE:_ Gocontentful provides an offline version of the client that can load data from a JSON space export file
|
||||
(as exported by the _contentful_ CLI tool). This is the way you can write unit tests against your generated API that
|
||||
don't require to be online and the management of a safe API key storage. See function reference below
|
||||
|
||||
### Caching
|
||||
|
||||
@ -141,22 +169,33 @@ _NOTE:_ Gocontentful provides an offline version of the client that can load dat
|
||||
err = cc.UpdateCache(context, contentTypes, true)
|
||||
</code></pre>
|
||||
|
||||
If your client mode is ClientModeCDA you can ask the client to cache the space (limited to the content types you pass to the UpdateCache function call). The client will download all the entries, convert and store them in the case as native Go value objects. This makes subsequent accesses to the space data an in-memory operation removing all the HTTP overhead you'd normally experience.
|
||||
If your client mode is ClientModeCDA you can ask the client to cache the space (limited to the content types you pass
|
||||
to the UpdateCache function call). The client will download all the entries, convert and store them in the case as
|
||||
native Go value objects. This makes subsequent accesses to the space data an in-memory operation removing all the HTTP
|
||||
overhead you'd normally experience.
|
||||
|
||||
The first parameter is the context. If you don't use a context in your application or service just pass _context.TODO()_
|
||||
|
||||
The third parameter of UpdateCache toggles asset caching on or off. If you deal with assets you want this to be always on.
|
||||
|
||||
The cache update uses 4 workers to speed up the process. This is safe since Contentful allows up to 5 concurrent connections. If you have content types that have a lot of entries, it might make sense to keep them close to each other in the content types slice passed to UpdateCache(), so that they will run in parallel and not one after the other (in case you have more than 4 content types, that is).
|
||||
The cache update uses 4 workers to speed up the process. This is safe since Contentful allows up to 5 concurrent
|
||||
connections. If you have content types that have a lot of entries, it might make sense to keep them close to each other
|
||||
in the content types slice passed to UpdateCache(), so that they will run in parallel and not one after the other
|
||||
(in case you have more than 4 content types, that is).
|
||||
|
||||
All functions that query the space through ERM are cache-transparent: if a cache is available data will be loaded from there, otherwise it will be sourced from Contentful.
|
||||
All functions that query the space through ERM are cache-transparent: if a cache is available data will be loaded from
|
||||
there, otherwise it will be sourced from Contentful.
|
||||
|
||||
gocontentful also supports selective entry and asset cache updates through the following method:
|
||||
|
||||
<pre><code>err = cc.UpdateCacheForEntity(context, sysType, contentType, entityID string)
|
||||
</code></pre>
|
||||
|
||||
Note that when something changes in the space at Contentful you need to regenerate the cache. This can be done setting up a webhook at Contentful and handling its call in your service through one of the cache update methods. It's highly recommended that you regenerate the entire CDA cache when something is published (because you want production data to be 100% up to date in your application) and that you only update a single entry in the cache for the CPA cache (because it's a whole lot faster for preview features).
|
||||
Note that when something changes in the space at Contentful you need to regenerate the cache. This can be done setting
|
||||
up a webhook at Contentful and handling its call in your service through one of the cache update methods. It's highly
|
||||
recommended that you regenerate the entire CDA cache when something is published (because you want production data to
|
||||
be 100% up to date in your application) and that you only update a single entry in the cache for the CPA cache (because
|
||||
it's a whole lot faster for preview features).
|
||||
|
||||
|
||||
### Have fun with persons and pets
|
||||
@ -171,7 +210,8 @@ Load a specific person:
|
||||
|
||||
`person, err := cc.GetPersonByID(THE_PERSON_ID)`
|
||||
|
||||
Gocontentful provides getters and setters for all fields in the entry. Get that person's name (assuming the entry has a "name" field):
|
||||
Gocontentful provides getters and setters for all fields in the entry. Get that person's name (assuming the entry has
|
||||
a "name" field):
|
||||
|
||||
`name := person.Name() // returns Jane`
|
||||
|
||||
@ -179,23 +219,30 @@ Get Jane's work title in a different locale:
|
||||
|
||||
`name := person.Title(people.SpaceLocaleItalian)`
|
||||
|
||||
Note that constants are available for all locales supported by the space. If a space is configured to have a fallback from one locale to the default one, the getter functions will return that in case the value is not set for locale passed to the function.
|
||||
Note that constants are available for all locales supported by the space. If a space is configured to have a fallback
|
||||
from one locale to the default one, the getter functions will return that in case the value is not set for locale passed
|
||||
to the function.
|
||||
|
||||
Contentful supports Rich Text editing and sooner or later you'll want to convert that to HTML:
|
||||
|
||||
`htmlText := people.RichTextToHtml(person.Resume(), linkResolver, entryLinkResolver, imageResolver, embeddedEntryResolver locale)`
|
||||
|
||||
_Note: linkResolver, entryLinkResolver, embeddedEntryResolver and imageResolver are functions that resolve URLs for links and attributes for embedded image assets or return the HTML snippet for an embedded entry in RichText. See API documentation below._
|
||||
_Note: linkResolver, entryLinkResolver, embeddedEntryResolver and imageResolver are functions that resolve URLs for
|
||||
links and attributes for embedded image assets or return the HTML snippet for an embedded entry in RichText. See API
|
||||
documentation below._
|
||||
|
||||
...or the other way around (often used when digesting data from external sources):
|
||||
|
||||
`myRichText := HtmlToRichText(htmlSrc)`
|
||||
|
||||
Gocontentful supports references out of the box, the internals of how those are managed at the API level are completely transparet. To get a list of Jane's pets (assuming _pets_ is a multiple reference field) just do:
|
||||
Gocontentful supports references out of the box, the internals of how those are managed at the API level are completely
|
||||
transparet. To get a list of Jane's pets (assuming _pets_ is a multiple reference field) just do:
|
||||
|
||||
`pets := person.Pets()`
|
||||
|
||||
The value returned is a slice *[]*EntryReference* where each element carries the ID (string), the content type (string) and the value object as an *interface{}*. In case of references that can return multiple types this is useful to perform a switch on the content type and assert the type of the returned value object:
|
||||
The value returned is a slice *[]*EntryReference* where each element carries the ID (string), the content type (string)
|
||||
and the value object as an *interface{}*. In case of references that can return multiple types this is useful to perform
|
||||
a switch on the content type and assert the type of the returned value object:
|
||||
|
||||
<pre><code>for _, pet := range pets {
|
||||
switch pet.ContentType {
|
||||
@ -230,8 +277,11 @@ When you need to change the value of the field, you can use any of the setter fu
|
||||
|
||||
but consider the following:
|
||||
|
||||
- To save the entry you need to use a client you instantiated with *ClientModeCMA*. Entries retrieved with ClientModeCDA or ClientModeCPA can be saved in memory (for example if you need to enrich the built-in cache) but not persisted to Contentful.
|
||||
- Make sure you Get and entry right before you manipulate it and upsert it / publish it to Contentful. In case it's been saved by someone else in the meantime, the upsert will fail with a version mismatch error.
|
||||
- To save the entry you need to use a client you instantiated with *ClientModeCMA*. Entries retrieved with ClientModeCDA
|
||||
or ClientModeCPA can be saved in memory (for example if you need to enrich the built-in cache) but not persisted to
|
||||
Contentful.
|
||||
- Make sure you Get and entry right before you manipulate it and upsert it / publish it to Contentful. In case it's
|
||||
been saved by someone else in the meantime, the upsert will fail with a version mismatch error.
|
||||
|
||||
To upsert (save) an entry:
|
||||
|
||||
@ -271,6 +321,11 @@ a test suite you can run with
|
||||
This generates the API in the test/testapi folder and runs a dozen unit tests to
|
||||
make sure everything is working correctly.
|
||||
|
||||
### Caveats
|
||||
|
||||
- Avoid creating content types that have field IDs equal to reserved Go words (e.g. "type"). Gocontentful won't scan
|
||||
for all of them and the generated code will break.
|
||||
|
||||
Public functions and methods
|
||||
---------------------
|
||||
|
||||
@ -278,7 +333,8 @@ Public functions and methods
|
||||
|
||||
>**NewContentfulClient**(spaceID string, clientMode string, clientKey string, optimisticPageSize uint16, logFn func(fields map[string]interface{}, level int, args ...interface{}), logLevel int, debug bool) (*ContentfulClient, error)
|
||||
|
||||
Creates a Contentful client, this is the first function you need to call. For usage details please refer to the Quickstart above.
|
||||
Creates a Contentful client, this is the first function you need to call. For usage details please refer to the
|
||||
Quickstart above.
|
||||
|
||||
>**SetOfflineFallback**(filename string) error
|
||||
|
||||
@ -287,11 +343,13 @@ Contentful is not reachable when you call UpdateCache() on the client
|
||||
|
||||
>**NewOfflineContentfulClient**(filename string, logFn func(fields map[string]interface{}, level int, args ...interface{}), logLevel int, cacheAssets bool) (*ContentfulClient, error)
|
||||
|
||||
Creates an offline Contentful client that loads space data from a JSON file containing a space export (use the contentful CLI tool to get one).
|
||||
Creates an offline Contentful client that loads space data from a JSON file containing a space export (use the
|
||||
contentful CLI tool to get one).
|
||||
|
||||
>(cc *ContentfulClient) **SetEnvironment**(environment string)
|
||||
|
||||
Sets the Contentful client's environment. All subsequent API calls will be directed to that environment in the selected space. Pass an empty string to reset to the _master_ environment.
|
||||
Sets the Contentful client's environment. All subsequent API calls will be directed to that environment in the selected
|
||||
space. Pass an empty string to reset to the _master_ environment.
|
||||
|
||||
**SPACE CACHING**
|
||||
|
||||
@ -321,7 +379,8 @@ might want to pass it most of the times or you won't be able to save the entry.
|
||||
|
||||
>(cc *ContentfulClient) **GetAllPerson**() (voMap map[string]*CfPerson, err error)
|
||||
|
||||
Retrieves all Person entries from the client and returnes a map where the key is the ID of the entry and the value is the Go value object for that entry.
|
||||
Retrieves all Person entries from the client and returnes a map where the key is the ID of the entry and the value is
|
||||
the Go value object for that entry.
|
||||
|
||||
>(cc *ContentfulClient) **GetFilteredPerson**(query *contentful.Query) (voMap map[string]*CfPerson, err error)
|
||||
|
||||
@ -346,7 +405,9 @@ Returns the Contentful content type of an entry ID.
|
||||
|
||||
>(vo *CfPerson) **ToReference**() (refSys ContentTypeSys)
|
||||
|
||||
Converts a value object into a reference that can be added to a reference field of an entry. Note that functions that retrieve referenced entries return a more flexible and useful _[]*EntryReference_ (see Quickstart above) but to store a reference you need a ContentTypeSys.
|
||||
Converts a value object into a reference that can be added to a reference field of an entry. Note that functions that
|
||||
retrieve referenced entries return a more flexible and useful _[]*EntryReference_ (see Quickstart above) but to store
|
||||
a reference you need a ContentTypeSys.
|
||||
|
||||
>(vo *CfPerson) **GetParents**() (parents []EntryReference, err error)
|
||||
|
||||
@ -354,7 +415,8 @@ Converts a value object into a reference that can be added to a reference field
|
||||
|
||||
Return a slice of EntryReference objects that represent entries that reference the value object or the entry reference.
|
||||
|
||||
Note that in case of parents of an entry reference you need to pass a pointer to a ContentfulClient because EntryReference objects are generic and can't carry any.
|
||||
Note that in case of parents of an entry reference you need to pass a pointer to a ContentfulClient because
|
||||
EntryReference objects are generic and can't carry any.
|
||||
|
||||
>(vo *CfPerson) **GetPublishingStatus**() string
|
||||
|
||||
@ -371,11 +433,15 @@ Value returned is one of the following:
|
||||
|
||||
**ENTRY FIELD GETTERS**
|
||||
|
||||
Field getters are named after the field ID in Contentful and return the proper type. For example, if the Person content type has a Symbol (short text) field named 'Name', this will be the getter:
|
||||
Field getters are named after the field ID in Contentful and return the proper type. For example, if the Person content
|
||||
type has a Symbol (short text) field named 'Name', this will be the getter:
|
||||
|
||||
>(vo *CfPerson) **Name**(locale ...string) (string)
|
||||
|
||||
The locale parameter is optional and if not passed, the function will return the value for the default locale of the space. If the locale is specified and it's not available for the space, an error is returned. If the locale is valid but a value doesn't exist for the field and locale, the function will return the value for the default locale if that's specified as a fallback locale in the space definition in Contentful, otherwise will return an error.
|
||||
The locale parameter is optional and if not passed, the function will return the value for the default locale of the
|
||||
space. If the locale is specified and it's not available for the space, an error is returned. If the locale is valid
|
||||
but a value doesn't exist for the field and locale, the function will return the value for the default locale if that's
|
||||
specified as a fallback locale in the space definition in Contentful, otherwise will return an error.
|
||||
|
||||
Possible return types are:
|
||||
|
||||
@ -388,13 +454,15 @@ Possible return types are:
|
||||
- _*ContentTypeFieldLocation_ for fields of type Location
|
||||
- *interface{} for fields of type Object or RichText
|
||||
|
||||
If logLevel is set to LogDebug retrieving the value of a field that is not set and so not available in the API response even as a fallback to the default locale will log the event. This can become incredibly verbose, use with care.
|
||||
If logLevel is set to LogDebug retrieving the value of a field that is not set and so not available in the API response
|
||||
even as a fallback to the default locale will log the event. This can become incredibly verbose, use with care.
|
||||
|
||||
---
|
||||
|
||||
**ENTRY FIELD SETTERS (only available for _ClientModeCMA_)**
|
||||
|
||||
Field setters are named after the field ID in Contentful and require to pass in the proper type. See FIELD GETTERS above for a reference. Example:
|
||||
Field setters are named after the field ID in Contentful and require to pass in the proper type. See FIELD GETTERS above
|
||||
for a reference. Example:
|
||||
|
||||
>(vo *CfPerson) **SetName**(title string, locale ...string) (err error)
|
||||
|
||||
@ -404,19 +472,27 @@ Field setters are named after the field ID in Contentful and require to pass in
|
||||
|
||||
>(vo *CfPerson) **UpsertEntry**(cc *ContentfulClient) (err error)
|
||||
|
||||
Upserts the entry. This will appear as "Draft" (if it's a new entry) or "Changed" if it's already existing. In the latter case, you will need to retrieve the entry with one of the Manage* functions above to acquire the Sys object that contains the version information. Otherwise the API call will fail with a "Version mismatch" error.
|
||||
Upserts the entry. This will appear as "Draft" (if it's a new entry) or "Changed" if it's already existing. In the
|
||||
latter case, you will need to retrieve the entry with one of the Manage* functions above to acquire the Sys object
|
||||
that contains the version information. Otherwise the API call will fail with a "Version mismatch" error.
|
||||
|
||||
>(vo *CfPerson) **PublishEntry**(cc *ContentfulClient) (err error)
|
||||
|
||||
Publishes the entry. Note that before publishing you will need to retrieve the entry with one of the Manage* functions above to acquire the Sys object that contains the version information. Otherwise the API call will fail with a "Version mismatch" error. This is needed even if you have just upserted the entry with the function above!
|
||||
Publishes the entry. Note that before publishing you will need to retrieve the entry with one of the Manage* functions
|
||||
above to acquire the Sys object that contains the version information. Otherwise the API call will fail with a "Version
|
||||
mismatch" error. This is needed even if you have just upserted the entry with the function above!
|
||||
|
||||
>(vo *CfPerson) **UnpublishEntry**(cc *ContentfulClient) (err error)
|
||||
|
||||
Unpublishes the entry. Note that before unpublishing you will need to retrieve the entry with one of the Manage* functions above to acquire the Sys object that contains the version information. Otherwise the API call will fail with a "Version mismatch" error. This is needed even if you have just upserted the entry with the function above!
|
||||
Unpublishes the entry. Note that before unpublishing you will need to retrieve the entry with one of the Manage*
|
||||
functions above to acquire the Sys object that contains the version information. Otherwise the API call will fail with
|
||||
a "Version mismatch" error. This is needed even if you have just upserted the entry with the function above!
|
||||
|
||||
>(vo *CfPerson) **UpdateEntry**(cc *ContentfulClient) (err error)
|
||||
|
||||
Shortcut function that upserts and publishes the entry. Note that before calling this you will need to retrieve the entry with one of the Manage* functions above to acquire the Sys object that contains the version information. Otherwise the API call will fail with a "Version mismatch" error. Using this shortcut function avoids retrieving the entry twice.
|
||||
Shortcut function that upserts and publishes the entry. Note that before calling this you will need to retrieve the
|
||||
entry with one of the Manage* functions above to acquire the Sys object that contains the version information. Otherwise
|
||||
the API call will fail with a "Version mismatch" error. Using this shortcut function avoids retrieving the entry twice.
|
||||
|
||||
>(vo *CfPerson) **DeleteEntry**(cc *ContentfulClient) (err error)
|
||||
|
||||
@ -465,15 +541,25 @@ positives for content types that are in the space but not included in the cache.
|
||||
|
||||
>**FieldToObject**(jsonField interface{}, targetObject interface{}) error
|
||||
|
||||
Converts a JSON field into an object. Make sure you pass a pointer to an object which type has JSON definition for all fields you want to retrieve.
|
||||
Converts a JSON field into an object. Make sure you pass a pointer to an object which type has JSON definition for all
|
||||
fields you want to retrieve.
|
||||
|
||||
>**HtmlToRichText**(htmlSrc string) *RichTextNode
|
||||
|
||||
Converts an HTML fragment to a RichTextNode. This is useful to migrate data from third-party systems to Contentful or support HTML paste operations in Web applications. It currently supports headings, paragraphs, hyperlinks, italic and bold tags, horizontal rules, blockquote, ordered and unordered lists, code. Unknown tags are stripped. This function doesn't return any error as it converts the input text into something as good as possible, without validation.
|
||||
Converts an HTML fragment to a RichTextNode. This is useful to migrate data from third-party systems to Contentful or
|
||||
support HTML paste operations in Web applications. It currently supports headings, paragraphs, hyperlinks, italic and
|
||||
bold tags, horizontal rules, blockquote, ordered and unordered lists, code. Unknown tags are stripped. This function
|
||||
doesn't return any error as it converts the input text into something as good as possible, without validation.
|
||||
|
||||
>**RichTextToHtml**(rt interface{}, linkResolver LinkResolverFunc, entryLinkResolver EntryLinkResolverFunc, imageResolver ImageResolverFunc, locale Locale) (string, error) {
|
||||
|
||||
Converts an interface representing a Contentful RichText value (usually from a field getter) into HTML. It currently supports all tags except for embedded and inline entries and assets. It takes in three (optional) functions to resolve hyperlink URLs, permalinks to entries and to derive IMG tag attributes for embedded image assets. The three functions return a map of attributes for the HTML tag the RichTextToHtml function will emit (either an A or an IMG) and have the following signature. Note that the ImageResolverFunc function must return a customHTML value that can be empty but if set it will substitute the IMG tag with the returned HTML snippet. This allows you to emit custom mark-up for your images, e.g. a PICTURE tag.
|
||||
Converts an interface representing a Contentful RichText value (usually from a field getter) into HTML. It currently
|
||||
supports all tags except for embedded and inline entries and assets. It takes in three (optional) functions to resolve
|
||||
hyperlink URLs, permalinks to entries and to derive IMG tag attributes for embedded image assets. The three functions
|
||||
return a map of attributes for the HTML tag the RichTextToHtml function will emit (either an A or an IMG) and have the
|
||||
following signature. Note that the ImageResolverFunc function must return a customHTML value that can be empty but if
|
||||
set it will substitute the IMG tag with the returned HTML snippet. This allows you to emit custom mark-up for your
|
||||
images, e.g. a PICTURE tag.
|
||||
|
||||
>type LinkResolverFunc func(url string) (resolvedAttrs map[string]string, resolveError error)
|
||||
|
||||
@ -489,7 +575,8 @@ All the three functions above can be passed as nil with different levels of grac
|
||||
|
||||
**CONSTANTS AND GLOBAL VARIABLES**
|
||||
|
||||
Each generated content type library file exports a constant with the Contentful ID of the content type itself, for example in _contentful_vo_lib_person.go_:
|
||||
Each generated content type library file exports a constant with the Contentful ID of the content type itself, for
|
||||
example in _contentful_vo_lib_person.go_:
|
||||
|
||||
>const ContentTypePerson = "person"
|
||||
|
||||
@ -518,4 +605,6 @@ The Go package generated by ERM only relies on one external library:
|
||||
|
||||
`https://github.com/foomo/contentful`
|
||||
|
||||
That is the raw API client used to interact with Contentful's API. This was originally found at https://github.com/contentful-labs/contentful-go and was forked first by https://github.com/ohookins and then by us (https://github.com/foomo/contentful). You will need the foomo version.
|
||||
That is the raw API client used to interact with Contentful's API. This was originally found at
|
||||
https://github.com/contentful-labs/contentful-go and was forked first by https://github.com/ohookins and then by us
|
||||
(https://github.com/foomo/contentful). You will need the foomo version.
|
||||
@ -14,7 +14,7 @@ type Cf{{ firstCap $contentType.Sys.ID }} struct {
|
||||
type Cf{{ firstCap $contentType.Sys.ID }}Fields struct {
|
||||
{{ range $fieldIndex, $field := $contentType.Fields }}
|
||||
{{ firstCap $field.ID }} map[string]{{ mapFieldType $contentType.Sys.ID $field }} `json:"{{ $field.ID }},omitempty"`
|
||||
RWLock{{ firstCap $field.ID }} sync.RWMutex `json:"-"`{{ end }}
|
||||
RWLock{{ firstCap $field.ID }} sync.Mutex `json:"-"`{{ end }}
|
||||
}
|
||||
{{ range $fieldIndex, $field := $contentType.Fields }}
|
||||
{{ if fieldIsAsset $field }}
|
||||
|
||||
@ -20,7 +20,7 @@ import (
|
||||
|
||||
type cacheEntryMaps struct {
|
||||
{{ range $index , $contentType := $contentTypes }} {{ $contentType.Sys.ID }} map[string]*Cf{{ firstCap $contentType.Sys.ID }}
|
||||
{{ $contentType.Sys.ID }}GcLock sync.RWMutex
|
||||
{{ $contentType.Sys.ID }}GcLock sync.Mutex
|
||||
{{ end }}
|
||||
}
|
||||
|
||||
@ -28,13 +28,13 @@ type ClientMode string
|
||||
|
||||
type ContentfulCache struct {
|
||||
assets assetCacheMap
|
||||
assetsGcLock sync.RWMutex
|
||||
assetsGcLock sync.Mutex
|
||||
contentTypes []string
|
||||
entryMaps cacheEntryMaps
|
||||
idContentTypeMap map[string]string
|
||||
idContentTypeMapGcLock sync.RWMutex
|
||||
idContentTypeMapGcLock sync.Mutex
|
||||
parentMap map[string][]EntryReference
|
||||
parentMapGcLock sync.RWMutex
|
||||
parentMapGcLock sync.Mutex
|
||||
}
|
||||
|
||||
type assetCacheMap map[string]*contentful.Asset
|
||||
@ -156,10 +156,10 @@ func (cc *ContentfulClient) BrokenReferences() (brokenReferences []BrokenReferen
|
||||
if cc.Cache == nil {
|
||||
return
|
||||
}
|
||||
cc.Cache.parentMapGcLock.RLock()
|
||||
defer cc.Cache.parentMapGcLock.RUnlock()
|
||||
cc.Cache.idContentTypeMapGcLock.RLock()
|
||||
defer cc.Cache.idContentTypeMapGcLock.RUnlock()
|
||||
cc.Cache.parentMapGcLock.Lock()
|
||||
defer cc.Cache.parentMapGcLock.Unlock()
|
||||
cc.Cache.idContentTypeMapGcLock.Lock()
|
||||
defer cc.Cache.idContentTypeMapGcLock.Unlock()
|
||||
for childID, parents := range cc.Cache.parentMap {
|
||||
if _, okGotEntry := cc.Cache.idContentTypeMap[childID]; !okGotEntry {
|
||||
for _, parent := range parents {
|
||||
@ -249,9 +249,9 @@ func (cc *ContentfulClient) GetAssetByID(id string, forceNoCache ...bool) (*cont
|
||||
return nil, errors.New("GetAssetByID: No client available")
|
||||
}
|
||||
if cc.Cache != nil && cc.Cache.assets != nil && (len(forceNoCache) == 0 || !forceNoCache[0]) {
|
||||
cc.Cache.assetsGcLock.RLock()
|
||||
cc.Cache.assetsGcLock.Lock()
|
||||
asset, okAsset := cc.Cache.assets[id]
|
||||
cc.Cache.assetsGcLock.RUnlock()
|
||||
cc.Cache.assetsGcLock.Unlock()
|
||||
if okAsset {
|
||||
return asset, nil
|
||||
}
|
||||
@ -290,9 +290,9 @@ func (cc *ContentfulClient) GetContentTypeOfID(id string) (string, error) {
|
||||
if cc.Cache != nil {
|
||||
okVo := false
|
||||
{{ range $index , $contentType := $contentTypes }}
|
||||
cc.Cache.entryMaps.{{ $contentType.Sys.ID }}GcLock.RLock()
|
||||
cc.Cache.entryMaps.{{ $contentType.Sys.ID }}GcLock.Lock()
|
||||
_, okVo = cc.Cache.entryMaps.{{ $contentType.Sys.ID }}[id]
|
||||
cc.Cache.entryMaps.{{ $contentType.Sys.ID }}GcLock.RUnlock()
|
||||
cc.Cache.entryMaps.{{ $contentType.Sys.ID }}GcLock.Unlock()
|
||||
if okVo {
|
||||
return ContentType{{ firstCap $contentType.Sys.ID }}, nil
|
||||
}
|
||||
|
||||
@ -67,9 +67,9 @@ func (cc *ContentfulClient) Get{{ firstCap $contentType.Sys.ID }}ByID(id string,
|
||||
return nil, errors.New("Get{{ firstCap $contentType.Sys.ID }}ByID: No client available")
|
||||
}
|
||||
if cc.Cache != nil && (len(forceNoCache) == 0 || !forceNoCache[0]) {
|
||||
cc.Cache.entryMaps.{{ $contentType.Sys.ID }}GcLock.RLock()
|
||||
cc.Cache.entryMaps.{{ $contentType.Sys.ID }}GcLock.Lock()
|
||||
vo, ok := cc.Cache.entryMaps.{{ $contentType.Sys.ID }}[id]
|
||||
cc.Cache.entryMaps.{{ $contentType.Sys.ID }}GcLock.RUnlock()
|
||||
cc.Cache.entryMaps.{{ $contentType.Sys.ID }}GcLock.Unlock()
|
||||
if ok {
|
||||
return vo, nil
|
||||
}
|
||||
@ -138,8 +138,8 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale
|
||||
if vo.CC == nil {
|
||||
return {{ mapFieldTypeLiteral $contentType.Sys.ID $field }}
|
||||
}
|
||||
vo.Fields.RWLock{{ firstCap $field.ID }}.RLock()
|
||||
defer vo.Fields.RWLock{{ firstCap $field.ID }}.RUnlock()
|
||||
vo.Fields.RWLock{{ firstCap $field.ID }}.Lock()
|
||||
defer vo.Fields.RWLock{{ firstCap $field.ID }}.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
@ -176,8 +176,8 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale
|
||||
if vo.CC == nil {
|
||||
return {{ mapFieldTypeLiteral $contentType.Sys.ID $field }}
|
||||
}
|
||||
vo.Fields.RWLock{{ firstCap $field.ID }}.RLock()
|
||||
defer vo.Fields.RWLock{{ firstCap $field.ID }}.RUnlock()
|
||||
vo.Fields.RWLock{{ firstCap $field.ID }}.Lock()
|
||||
defer vo.Fields.RWLock{{ firstCap $field.ID }}.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
@ -215,8 +215,8 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale
|
||||
if vo.CC == nil {
|
||||
return nil
|
||||
}
|
||||
vo.Fields.RWLock{{ firstCap $field.ID }}.RLock()
|
||||
defer vo.Fields.RWLock{{ firstCap $field.ID }}.RUnlock()
|
||||
vo.Fields.RWLock{{ firstCap $field.ID }}.Lock()
|
||||
defer vo.Fields.RWLock{{ firstCap $field.ID }}.Unlock()
|
||||
{{ $field.ID }} := []*EntryReference{}
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
@ -276,8 +276,8 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale
|
||||
if vo.CC == nil {
|
||||
return nil
|
||||
}
|
||||
vo.Fields.RWLock{{ firstCap $field.ID }}.RLock()
|
||||
defer vo.Fields.RWLock{{ firstCap $field.ID }}.RUnlock()
|
||||
vo.Fields.RWLock{{ firstCap $field.ID }}.Lock()
|
||||
defer vo.Fields.RWLock{{ firstCap $field.ID }}.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
@ -335,8 +335,8 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale
|
||||
if vo.CC == nil {
|
||||
return nil
|
||||
}
|
||||
vo.Fields.RWLock{{ firstCap $field.ID }}.RLock()
|
||||
defer vo.Fields.RWLock{{ firstCap $field.ID }}.RUnlock()
|
||||
vo.Fields.RWLock{{ firstCap $field.ID }}.Lock()
|
||||
defer vo.Fields.RWLock{{ firstCap $field.ID }}.Unlock()
|
||||
{{ $field.ID }} := []*contentful.AssetNoLocale{}
|
||||
loc := defaultLocale
|
||||
reqLoc := defaultLocale
|
||||
@ -404,8 +404,8 @@ func (vo *Cf{{ firstCap $contentType.Sys.ID }}) {{ firstCap $field.ID }}(locale
|
||||
if vo.CC == nil {
|
||||
return nil
|
||||
}
|
||||
vo.Fields.RWLock{{ firstCap $field.ID }}.RLock()
|
||||
defer vo.Fields.RWLock{{ firstCap $field.ID }}.RUnlock()
|
||||
vo.Fields.RWLock{{ firstCap $field.ID }}.Lock()
|
||||
defer vo.Fields.RWLock{{ firstCap $field.ID }}.Unlock()
|
||||
loc := defaultLocale
|
||||
reqLoc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
|
||||
4
main.go
4
main.go
@ -79,7 +79,7 @@ func main() {
|
||||
os.Exit(0)
|
||||
}
|
||||
cmaKey := *flagCMAKey
|
||||
if cmaKey == "" {
|
||||
if cmaKey == "" && *flagGenerateFromExport == "" {
|
||||
cmaKey = getCmaKeyFromRcFile()
|
||||
}
|
||||
if *flagGenerateFromExport == "" && (*flagSpaceID == "" || cmaKey == "") ||
|
||||
@ -99,7 +99,7 @@ func main() {
|
||||
usageError("Please specify the package name correctly (only small caps letters)")
|
||||
}
|
||||
|
||||
fmt.Printf("Contentful API Generator starting...\n\n")
|
||||
fmt.Printf("Contentful API Generator %s starting...\n\n", VERSION)
|
||||
|
||||
var flagContentTypesSlice []string
|
||||
if *flagContentTypes != "" {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/foomo/gocontentful/test/testapi"
|
||||
"sync"
|
||||
"testing"
|
||||
@ -46,7 +47,19 @@ func TestConcurrentReadWrites(t *testing.T) {
|
||||
testLogger.Errorf("testConcurrentReadWrites: %v", err)
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
for i := 1; i <= 50; i++ {
|
||||
for i := 1; i <= 100; i++ {
|
||||
wg.Add(1)
|
||||
i := i
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
testLogger.Infof("testConcurrentReadWrites: caching run %d", i)
|
||||
err = contentfulClient.UpdateCache(context.TODO(), nil, false)
|
||||
if err != nil {
|
||||
testLogger.Errorf("testConcurrentReadWrites: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
for i := 1; i <= 10000; i++ {
|
||||
wg.Add(1)
|
||||
i := i
|
||||
go func() {
|
||||
@ -57,7 +70,7 @@ func TestConcurrentReadWrites(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
}
|
||||
for i := 1; i <= 50; i++ {
|
||||
for i := 1; i <= 10000; i++ {
|
||||
wg.Add(1)
|
||||
i := i
|
||||
go func() {
|
||||
|
||||
@ -16,19 +16,19 @@ type CfBrand struct {
|
||||
// CfBrandFields is a CfNameFields VO
|
||||
type CfBrandFields struct {
|
||||
CompanyName map[string]string `json:"companyName,omitempty"`
|
||||
RWLockCompanyName sync.RWMutex `json:"-"`
|
||||
RWLockCompanyName sync.Mutex `json:"-"`
|
||||
Logo map[string]ContentTypeSys `json:"logo,omitempty"`
|
||||
RWLockLogo sync.RWMutex `json:"-"`
|
||||
RWLockLogo sync.Mutex `json:"-"`
|
||||
CompanyDescription map[string]string `json:"companyDescription,omitempty"`
|
||||
RWLockCompanyDescription sync.RWMutex `json:"-"`
|
||||
RWLockCompanyDescription sync.Mutex `json:"-"`
|
||||
Website map[string]string `json:"website,omitempty"`
|
||||
RWLockWebsite sync.RWMutex `json:"-"`
|
||||
RWLockWebsite sync.Mutex `json:"-"`
|
||||
Twitter map[string]string `json:"twitter,omitempty"`
|
||||
RWLockTwitter sync.RWMutex `json:"-"`
|
||||
RWLockTwitter sync.Mutex `json:"-"`
|
||||
Email map[string]string `json:"email,omitempty"`
|
||||
RWLockEmail sync.RWMutex `json:"-"`
|
||||
RWLockEmail sync.Mutex `json:"-"`
|
||||
Phone map[string][]string `json:"phone,omitempty"`
|
||||
RWLockPhone sync.RWMutex `json:"-"`
|
||||
RWLockPhone sync.Mutex `json:"-"`
|
||||
}
|
||||
|
||||
type CfBrandFieldsLogo struct {
|
||||
@ -45,11 +45,11 @@ type CfCategory struct {
|
||||
// CfCategoryFields is a CfNameFields VO
|
||||
type CfCategoryFields struct {
|
||||
Title map[string]string `json:"title,omitempty"`
|
||||
RWLockTitle sync.RWMutex `json:"-"`
|
||||
RWLockTitle sync.Mutex `json:"-"`
|
||||
Icon map[string]ContentTypeSys `json:"icon,omitempty"`
|
||||
RWLockIcon sync.RWMutex `json:"-"`
|
||||
RWLockIcon sync.Mutex `json:"-"`
|
||||
CategoryDescription map[string]string `json:"categoryDescription,omitempty"`
|
||||
RWLockCategoryDescription sync.RWMutex `json:"-"`
|
||||
RWLockCategoryDescription sync.Mutex `json:"-"`
|
||||
}
|
||||
|
||||
type CfCategoryFieldsIcon struct {
|
||||
@ -66,29 +66,29 @@ type CfProduct struct {
|
||||
// CfProductFields is a CfNameFields VO
|
||||
type CfProductFields struct {
|
||||
ProductName map[string]string `json:"productName,omitempty"`
|
||||
RWLockProductName sync.RWMutex `json:"-"`
|
||||
RWLockProductName sync.Mutex `json:"-"`
|
||||
Slug map[string]string `json:"slug,omitempty"`
|
||||
RWLockSlug sync.RWMutex `json:"-"`
|
||||
RWLockSlug sync.Mutex `json:"-"`
|
||||
ProductDescription map[string]string `json:"productDescription,omitempty"`
|
||||
RWLockProductDescription sync.RWMutex `json:"-"`
|
||||
RWLockProductDescription sync.Mutex `json:"-"`
|
||||
Sizetypecolor map[string]string `json:"sizetypecolor,omitempty"`
|
||||
RWLockSizetypecolor sync.RWMutex `json:"-"`
|
||||
RWLockSizetypecolor sync.Mutex `json:"-"`
|
||||
Image map[string][]ContentTypeSys `json:"image,omitempty"`
|
||||
RWLockImage sync.RWMutex `json:"-"`
|
||||
RWLockImage sync.Mutex `json:"-"`
|
||||
Tags map[string][]string `json:"tags,omitempty"`
|
||||
RWLockTags sync.RWMutex `json:"-"`
|
||||
RWLockTags sync.Mutex `json:"-"`
|
||||
Categories map[string][]ContentTypeSys `json:"categories,omitempty"`
|
||||
RWLockCategories sync.RWMutex `json:"-"`
|
||||
RWLockCategories sync.Mutex `json:"-"`
|
||||
Price map[string]float64 `json:"price,omitempty"`
|
||||
RWLockPrice sync.RWMutex `json:"-"`
|
||||
RWLockPrice sync.Mutex `json:"-"`
|
||||
Brand map[string]ContentTypeSys `json:"brand,omitempty"`
|
||||
RWLockBrand sync.RWMutex `json:"-"`
|
||||
RWLockBrand sync.Mutex `json:"-"`
|
||||
Quantity map[string]float64 `json:"quantity,omitempty"`
|
||||
RWLockQuantity sync.RWMutex `json:"-"`
|
||||
RWLockQuantity sync.Mutex `json:"-"`
|
||||
Sku map[string]string `json:"sku,omitempty"`
|
||||
RWLockSku sync.RWMutex `json:"-"`
|
||||
RWLockSku sync.Mutex `json:"-"`
|
||||
Website map[string]string `json:"website,omitempty"`
|
||||
RWLockWebsite sync.RWMutex `json:"-"`
|
||||
RWLockWebsite sync.Mutex `json:"-"`
|
||||
}
|
||||
|
||||
type genericEntryNoFields struct {
|
||||
|
||||
@ -21,24 +21,24 @@ import (
|
||||
|
||||
type cacheEntryMaps struct {
|
||||
brand map[string]*CfBrand
|
||||
brandGcLock sync.RWMutex
|
||||
brandGcLock sync.Mutex
|
||||
category map[string]*CfCategory
|
||||
categoryGcLock sync.RWMutex
|
||||
categoryGcLock sync.Mutex
|
||||
product map[string]*CfProduct
|
||||
productGcLock sync.RWMutex
|
||||
productGcLock sync.Mutex
|
||||
}
|
||||
|
||||
type ClientMode string
|
||||
|
||||
type ContentfulCache struct {
|
||||
assets assetCacheMap
|
||||
assetsGcLock sync.RWMutex
|
||||
assetsGcLock sync.Mutex
|
||||
contentTypes []string
|
||||
entryMaps cacheEntryMaps
|
||||
idContentTypeMap map[string]string
|
||||
idContentTypeMapGcLock sync.RWMutex
|
||||
idContentTypeMapGcLock sync.Mutex
|
||||
parentMap map[string][]EntryReference
|
||||
parentMapGcLock sync.RWMutex
|
||||
parentMapGcLock sync.Mutex
|
||||
}
|
||||
|
||||
type assetCacheMap map[string]*contentful.Asset
|
||||
@ -172,10 +172,10 @@ func (cc *ContentfulClient) BrokenReferences() (brokenReferences []BrokenReferen
|
||||
if cc.Cache == nil {
|
||||
return
|
||||
}
|
||||
cc.Cache.parentMapGcLock.RLock()
|
||||
defer cc.Cache.parentMapGcLock.RUnlock()
|
||||
cc.Cache.idContentTypeMapGcLock.RLock()
|
||||
defer cc.Cache.idContentTypeMapGcLock.RUnlock()
|
||||
cc.Cache.parentMapGcLock.Lock()
|
||||
defer cc.Cache.parentMapGcLock.Unlock()
|
||||
cc.Cache.idContentTypeMapGcLock.Lock()
|
||||
defer cc.Cache.idContentTypeMapGcLock.Unlock()
|
||||
for childID, parents := range cc.Cache.parentMap {
|
||||
if _, okGotEntry := cc.Cache.idContentTypeMap[childID]; !okGotEntry {
|
||||
for _, parent := range parents {
|
||||
@ -265,9 +265,9 @@ func (cc *ContentfulClient) GetAssetByID(id string, forceNoCache ...bool) (*cont
|
||||
return nil, errors.New("GetAssetByID: No client available")
|
||||
}
|
||||
if cc.Cache != nil && cc.Cache.assets != nil && (len(forceNoCache) == 0 || !forceNoCache[0]) {
|
||||
cc.Cache.assetsGcLock.RLock()
|
||||
cc.Cache.assetsGcLock.Lock()
|
||||
asset, okAsset := cc.Cache.assets[id]
|
||||
cc.Cache.assetsGcLock.RUnlock()
|
||||
cc.Cache.assetsGcLock.Unlock()
|
||||
if okAsset {
|
||||
return asset, nil
|
||||
}
|
||||
@ -306,23 +306,23 @@ func (cc *ContentfulClient) GetContentTypeOfID(id string) (string, error) {
|
||||
if cc.Cache != nil {
|
||||
okVo := false
|
||||
|
||||
cc.Cache.entryMaps.brandGcLock.RLock()
|
||||
cc.Cache.entryMaps.brandGcLock.Lock()
|
||||
_, okVo = cc.Cache.entryMaps.brand[id]
|
||||
cc.Cache.entryMaps.brandGcLock.RUnlock()
|
||||
cc.Cache.entryMaps.brandGcLock.Unlock()
|
||||
if okVo {
|
||||
return ContentTypeBrand, nil
|
||||
}
|
||||
|
||||
cc.Cache.entryMaps.categoryGcLock.RLock()
|
||||
cc.Cache.entryMaps.categoryGcLock.Lock()
|
||||
_, okVo = cc.Cache.entryMaps.category[id]
|
||||
cc.Cache.entryMaps.categoryGcLock.RUnlock()
|
||||
cc.Cache.entryMaps.categoryGcLock.Unlock()
|
||||
if okVo {
|
||||
return ContentTypeCategory, nil
|
||||
}
|
||||
|
||||
cc.Cache.entryMaps.productGcLock.RLock()
|
||||
cc.Cache.entryMaps.productGcLock.Lock()
|
||||
_, okVo = cc.Cache.entryMaps.product[id]
|
||||
cc.Cache.entryMaps.productGcLock.RUnlock()
|
||||
cc.Cache.entryMaps.productGcLock.Unlock()
|
||||
if okVo {
|
||||
return ContentTypeProduct, nil
|
||||
}
|
||||
@ -874,10 +874,27 @@ func richTextHtmlLinesToNode(htmlLines []string, start int, pendingTag string, m
|
||||
currentNode.NodeType = richTextMapTagNodeType(tt)
|
||||
currentNode.Content = make([]interface{}, 0)
|
||||
innerContent, nextLine, isBasic := richTextHtmlLinesToNode(htmlLines, i+1, tt, marks, isBasic)
|
||||
currentNode.Content = append(currentNode.Content, RichTextNode{
|
||||
NodeType: RichTextNodeParagraph,
|
||||
Content: innerContent,
|
||||
})
|
||||
if len(innerContent) == 1 {
|
||||
switch innerContent[0].(type) {
|
||||
case RichTextNodeTextNode:
|
||||
currentNode.Content = append(currentNode.Content, RichTextNode{
|
||||
NodeType: RichTextNodeParagraph,
|
||||
Content: innerContent,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
for _, ct := range innerContent {
|
||||
switch ct.(type) {
|
||||
case RichTextNode:
|
||||
currentNode.Content = append(currentNode.Content, ct)
|
||||
case RichTextNodeTextNode:
|
||||
currentNode.Content = append(currentNode.Content, RichTextNode{
|
||||
NodeType: RichTextNodeParagraph,
|
||||
Content: []interface{}{ct},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
nodeSlice = append(nodeSlice, currentNode)
|
||||
if nextLine == -1 {
|
||||
return nodeSlice, -1, isBasic
|
||||
|
||||
@ -69,9 +69,9 @@ func (cc *ContentfulClient) GetBrandByID(id string, forceNoCache ...bool) (vo *C
|
||||
return nil, errors.New("GetBrandByID: No client available")
|
||||
}
|
||||
if cc.Cache != nil && (len(forceNoCache) == 0 || !forceNoCache[0]) {
|
||||
cc.Cache.entryMaps.brandGcLock.RLock()
|
||||
cc.Cache.entryMaps.brandGcLock.Lock()
|
||||
vo, ok := cc.Cache.entryMaps.brand[id]
|
||||
cc.Cache.entryMaps.brandGcLock.RUnlock()
|
||||
cc.Cache.entryMaps.brandGcLock.Unlock()
|
||||
if ok {
|
||||
return vo, nil
|
||||
}
|
||||
@ -151,8 +151,8 @@ func (vo *CfBrand) CompanyName(locale ...Locale) string {
|
||||
if vo.CC == nil {
|
||||
return ""
|
||||
}
|
||||
vo.Fields.RWLockCompanyName.RLock()
|
||||
defer vo.Fields.RWLockCompanyName.RUnlock()
|
||||
vo.Fields.RWLockCompanyName.Lock()
|
||||
defer vo.Fields.RWLockCompanyName.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
@ -188,8 +188,8 @@ func (vo *CfBrand) Logo(locale ...Locale) *contentful.AssetNoLocale {
|
||||
if vo.CC == nil {
|
||||
return nil
|
||||
}
|
||||
vo.Fields.RWLockLogo.RLock()
|
||||
defer vo.Fields.RWLockLogo.RUnlock()
|
||||
vo.Fields.RWLockLogo.Lock()
|
||||
defer vo.Fields.RWLockLogo.Unlock()
|
||||
loc := defaultLocale
|
||||
reqLoc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
@ -253,8 +253,8 @@ func (vo *CfBrand) CompanyDescription(locale ...Locale) string {
|
||||
if vo.CC == nil {
|
||||
return ""
|
||||
}
|
||||
vo.Fields.RWLockCompanyDescription.RLock()
|
||||
defer vo.Fields.RWLockCompanyDescription.RUnlock()
|
||||
vo.Fields.RWLockCompanyDescription.Lock()
|
||||
defer vo.Fields.RWLockCompanyDescription.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
@ -290,8 +290,8 @@ func (vo *CfBrand) Website(locale ...Locale) string {
|
||||
if vo.CC == nil {
|
||||
return ""
|
||||
}
|
||||
vo.Fields.RWLockWebsite.RLock()
|
||||
defer vo.Fields.RWLockWebsite.RUnlock()
|
||||
vo.Fields.RWLockWebsite.Lock()
|
||||
defer vo.Fields.RWLockWebsite.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
@ -327,8 +327,8 @@ func (vo *CfBrand) Twitter(locale ...Locale) string {
|
||||
if vo.CC == nil {
|
||||
return ""
|
||||
}
|
||||
vo.Fields.RWLockTwitter.RLock()
|
||||
defer vo.Fields.RWLockTwitter.RUnlock()
|
||||
vo.Fields.RWLockTwitter.Lock()
|
||||
defer vo.Fields.RWLockTwitter.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
@ -364,8 +364,8 @@ func (vo *CfBrand) Email(locale ...Locale) string {
|
||||
if vo.CC == nil {
|
||||
return ""
|
||||
}
|
||||
vo.Fields.RWLockEmail.RLock()
|
||||
defer vo.Fields.RWLockEmail.RUnlock()
|
||||
vo.Fields.RWLockEmail.Lock()
|
||||
defer vo.Fields.RWLockEmail.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
@ -401,8 +401,8 @@ func (vo *CfBrand) Phone(locale ...Locale) []string {
|
||||
if vo.CC == nil {
|
||||
return nil
|
||||
}
|
||||
vo.Fields.RWLockPhone.RLock()
|
||||
defer vo.Fields.RWLockPhone.RUnlock()
|
||||
vo.Fields.RWLockPhone.Lock()
|
||||
defer vo.Fields.RWLockPhone.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
|
||||
@ -69,9 +69,9 @@ func (cc *ContentfulClient) GetCategoryByID(id string, forceNoCache ...bool) (vo
|
||||
return nil, errors.New("GetCategoryByID: No client available")
|
||||
}
|
||||
if cc.Cache != nil && (len(forceNoCache) == 0 || !forceNoCache[0]) {
|
||||
cc.Cache.entryMaps.categoryGcLock.RLock()
|
||||
cc.Cache.entryMaps.categoryGcLock.Lock()
|
||||
vo, ok := cc.Cache.entryMaps.category[id]
|
||||
cc.Cache.entryMaps.categoryGcLock.RUnlock()
|
||||
cc.Cache.entryMaps.categoryGcLock.Unlock()
|
||||
if ok {
|
||||
return vo, nil
|
||||
}
|
||||
@ -143,8 +143,8 @@ func (vo *CfCategory) Title(locale ...Locale) string {
|
||||
if vo.CC == nil {
|
||||
return ""
|
||||
}
|
||||
vo.Fields.RWLockTitle.RLock()
|
||||
defer vo.Fields.RWLockTitle.RUnlock()
|
||||
vo.Fields.RWLockTitle.Lock()
|
||||
defer vo.Fields.RWLockTitle.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
@ -180,8 +180,8 @@ func (vo *CfCategory) Icon(locale ...Locale) *contentful.AssetNoLocale {
|
||||
if vo.CC == nil {
|
||||
return nil
|
||||
}
|
||||
vo.Fields.RWLockIcon.RLock()
|
||||
defer vo.Fields.RWLockIcon.RUnlock()
|
||||
vo.Fields.RWLockIcon.Lock()
|
||||
defer vo.Fields.RWLockIcon.Unlock()
|
||||
loc := defaultLocale
|
||||
reqLoc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
@ -245,8 +245,8 @@ func (vo *CfCategory) CategoryDescription(locale ...Locale) string {
|
||||
if vo.CC == nil {
|
||||
return ""
|
||||
}
|
||||
vo.Fields.RWLockCategoryDescription.RLock()
|
||||
defer vo.Fields.RWLockCategoryDescription.RUnlock()
|
||||
vo.Fields.RWLockCategoryDescription.Lock()
|
||||
defer vo.Fields.RWLockCategoryDescription.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
|
||||
@ -69,9 +69,9 @@ func (cc *ContentfulClient) GetProductByID(id string, forceNoCache ...bool) (vo
|
||||
return nil, errors.New("GetProductByID: No client available")
|
||||
}
|
||||
if cc.Cache != nil && (len(forceNoCache) == 0 || !forceNoCache[0]) {
|
||||
cc.Cache.entryMaps.productGcLock.RLock()
|
||||
cc.Cache.entryMaps.productGcLock.Lock()
|
||||
vo, ok := cc.Cache.entryMaps.product[id]
|
||||
cc.Cache.entryMaps.productGcLock.RUnlock()
|
||||
cc.Cache.entryMaps.productGcLock.Unlock()
|
||||
if ok {
|
||||
return vo, nil
|
||||
}
|
||||
@ -161,8 +161,8 @@ func (vo *CfProduct) ProductName(locale ...Locale) string {
|
||||
if vo.CC == nil {
|
||||
return ""
|
||||
}
|
||||
vo.Fields.RWLockProductName.RLock()
|
||||
defer vo.Fields.RWLockProductName.RUnlock()
|
||||
vo.Fields.RWLockProductName.Lock()
|
||||
defer vo.Fields.RWLockProductName.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
@ -198,8 +198,8 @@ func (vo *CfProduct) Slug(locale ...Locale) string {
|
||||
if vo.CC == nil {
|
||||
return ""
|
||||
}
|
||||
vo.Fields.RWLockSlug.RLock()
|
||||
defer vo.Fields.RWLockSlug.RUnlock()
|
||||
vo.Fields.RWLockSlug.Lock()
|
||||
defer vo.Fields.RWLockSlug.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
@ -235,8 +235,8 @@ func (vo *CfProduct) ProductDescription(locale ...Locale) string {
|
||||
if vo.CC == nil {
|
||||
return ""
|
||||
}
|
||||
vo.Fields.RWLockProductDescription.RLock()
|
||||
defer vo.Fields.RWLockProductDescription.RUnlock()
|
||||
vo.Fields.RWLockProductDescription.Lock()
|
||||
defer vo.Fields.RWLockProductDescription.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
@ -272,8 +272,8 @@ func (vo *CfProduct) Sizetypecolor(locale ...Locale) string {
|
||||
if vo.CC == nil {
|
||||
return ""
|
||||
}
|
||||
vo.Fields.RWLockSizetypecolor.RLock()
|
||||
defer vo.Fields.RWLockSizetypecolor.RUnlock()
|
||||
vo.Fields.RWLockSizetypecolor.Lock()
|
||||
defer vo.Fields.RWLockSizetypecolor.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
@ -309,8 +309,8 @@ func (vo *CfProduct) Image(locale ...Locale) []*contentful.AssetNoLocale {
|
||||
if vo.CC == nil {
|
||||
return nil
|
||||
}
|
||||
vo.Fields.RWLockImage.RLock()
|
||||
defer vo.Fields.RWLockImage.RUnlock()
|
||||
vo.Fields.RWLockImage.Lock()
|
||||
defer vo.Fields.RWLockImage.Unlock()
|
||||
image := []*contentful.AssetNoLocale{}
|
||||
loc := defaultLocale
|
||||
reqLoc := defaultLocale
|
||||
@ -377,8 +377,8 @@ func (vo *CfProduct) Tags(locale ...Locale) []string {
|
||||
if vo.CC == nil {
|
||||
return nil
|
||||
}
|
||||
vo.Fields.RWLockTags.RLock()
|
||||
defer vo.Fields.RWLockTags.RUnlock()
|
||||
vo.Fields.RWLockTags.Lock()
|
||||
defer vo.Fields.RWLockTags.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
@ -414,8 +414,8 @@ func (vo *CfProduct) Categories(locale ...Locale) []*EntryReference {
|
||||
if vo.CC == nil {
|
||||
return nil
|
||||
}
|
||||
vo.Fields.RWLockCategories.RLock()
|
||||
defer vo.Fields.RWLockCategories.RUnlock()
|
||||
vo.Fields.RWLockCategories.Lock()
|
||||
defer vo.Fields.RWLockCategories.Unlock()
|
||||
categories := []*EntryReference{}
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
@ -494,8 +494,8 @@ func (vo *CfProduct) Price(locale ...Locale) float64 {
|
||||
if vo.CC == nil {
|
||||
return 0
|
||||
}
|
||||
vo.Fields.RWLockPrice.RLock()
|
||||
defer vo.Fields.RWLockPrice.RUnlock()
|
||||
vo.Fields.RWLockPrice.Lock()
|
||||
defer vo.Fields.RWLockPrice.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
@ -531,8 +531,8 @@ func (vo *CfProduct) Brand(locale ...Locale) *EntryReference {
|
||||
if vo.CC == nil {
|
||||
return nil
|
||||
}
|
||||
vo.Fields.RWLockBrand.RLock()
|
||||
defer vo.Fields.RWLockBrand.RUnlock()
|
||||
vo.Fields.RWLockBrand.Lock()
|
||||
defer vo.Fields.RWLockBrand.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
@ -609,8 +609,8 @@ func (vo *CfProduct) Quantity(locale ...Locale) float64 {
|
||||
if vo.CC == nil {
|
||||
return 0
|
||||
}
|
||||
vo.Fields.RWLockQuantity.RLock()
|
||||
defer vo.Fields.RWLockQuantity.RUnlock()
|
||||
vo.Fields.RWLockQuantity.Lock()
|
||||
defer vo.Fields.RWLockQuantity.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
@ -646,8 +646,8 @@ func (vo *CfProduct) Sku(locale ...Locale) string {
|
||||
if vo.CC == nil {
|
||||
return ""
|
||||
}
|
||||
vo.Fields.RWLockSku.RLock()
|
||||
defer vo.Fields.RWLockSku.RUnlock()
|
||||
vo.Fields.RWLockSku.Lock()
|
||||
defer vo.Fields.RWLockSku.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
@ -683,8 +683,8 @@ func (vo *CfProduct) Website(locale ...Locale) string {
|
||||
if vo.CC == nil {
|
||||
return ""
|
||||
}
|
||||
vo.Fields.RWLockWebsite.RLock()
|
||||
defer vo.Fields.RWLockWebsite.RUnlock()
|
||||
vo.Fields.RWLockWebsite.Lock()
|
||||
defer vo.Fields.RWLockWebsite.Unlock()
|
||||
loc := defaultLocale
|
||||
if len(locale) != 0 {
|
||||
loc = locale[0]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user