├── docs ├── search.json ├── docsets │ ├── ContentfulDeliveryAPI.docset │ │ └── Contents │ │ │ ├── Resources │ │ │ ├── Documents │ │ │ │ ├── search.json │ │ │ │ ├── img │ │ │ │ │ ├── gh.png │ │ │ │ │ ├── carat.png │ │ │ │ │ └── dash.png │ │ │ │ └── js │ │ │ │ │ └── jazzy.js │ │ │ └── docSet.dsidx │ │ │ └── Info.plist │ └── ContentfulDeliveryAPI.tgz ├── img │ ├── dash.png │ ├── gh.png │ └── carat.png ├── undocumented.json ├── badge.svg └── js │ └── jazzy.js ├── .env ├── .envrc ├── Tests ├── Data │ ├── Recordings │ │ ├── ClientConfiguration.json │ │ ├── ComplexSyncTests.json │ │ └── UtilityTests.json │ ├── SyncTests │ │ ├── space-for-empty.json │ │ ├── space.json │ │ ├── deleted.json │ │ ├── deleted-asset.json │ │ ├── content-types-for-empty.json │ │ ├── update-for-empty.json │ │ ├── initial-for-empty.json │ │ ├── added.json │ │ ├── added-asset.json │ │ ├── asset-multiple-locales.json │ │ ├── initialWithoutToken.json │ │ ├── initial.json │ │ ├── update.json │ │ └── all-content-types.json │ ├── ComplexSyncTests │ │ ├── space.json │ │ ├── ComplexLinkTestUpdate1.json │ │ ├── AddContentTypesContentTypes.json │ │ ├── LinkTestResolve.json │ │ ├── AssetTestResolve2.json │ │ ├── NewEntryAndDeletionResolve2.json │ │ ├── AddContentTypesUpdate.json │ │ ├── AssetTestResolve.json │ │ ├── NewEntryAndDeletionResolve.json │ │ ├── AssetTestUpdate.json │ │ ├── ComplexLinkTestUpdate2.json │ │ ├── ComplexLinkTestContentTypes2.json │ │ ├── LinkTestUpdate.json │ │ ├── NewEntryAndDeletionUpdate.json │ │ ├── LinkTestInitial.json │ │ ├── ComplexLinkTestEntries.json │ │ ├── ComplexLinkTestContentTypes.json │ │ └── AssetTestInitial.json │ └── Fixtures │ │ ├── Crash-001.json │ │ └── ContentType-001.json ├── FakeNavigationController.h ├── Tests-Info.plist ├── SyncBaseTestCase.h ├── FakeNavigationController.m ├── ContentfulBaseTestCase.h ├── RobustnessTests.m ├── PreviewModeSyncTests.m ├── StagingTests.m ├── LocaleTests.m ├── LinkedData.xcdatamodeld │ └── LinkedData.xcdatamodel │ │ └── contents ├── AddContentTypesSyncTests.m ├── LocalizedModel.xcdatamodeld │ └── LocalizedModel.xcdatamodel │ │ └── contents ├── LinkedAssetSyncTest.m ├── ComplexSyncTests.m ├── NewEntryAndDeletionSyncTest.m ├── SyncSpecificContentTests.m └── ClientConfiguration.m ├── Screenshots ├── Frameworks.png ├── GeneralTab.png └── SearchForFramework.png ├── .slather.yml ├── scripts ├── unarcify-headers.sh ├── set-delivery-version.sh ├── travis-build-test.sh └── reference-docs.sh ├── ContentfulSDK.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── ManagementTests.xcscheme │ ├── CoreDataExample.xcscheme │ ├── DeliveryTests.xcscheme │ ├── ContentfulManagementAPI.xcscheme │ └── ContentfulDeliveryAPI.xcscheme ├── Versions.h ├── ContentfulDeliveryAPI ├── Resources │ ├── CDADeletedAsset.h │ ├── CDADeletedEntry.h │ ├── CDADeletedAsset.m │ ├── CDADeletedEntry.m │ ├── CDAError+Private.h │ ├── CDASpace+Private.h │ ├── CDAOrganizationContainer.h │ ├── CDAArray+Private.h │ ├── CDAError.h │ ├── CDASpace.h │ ├── CDAEntry+Private.h │ ├── CDAArray.h │ ├── CDAContentType.h │ ├── CDAResource+Private.h │ ├── CDASpace.m │ ├── CDAError.m │ ├── CDAEntry.h │ ├── CDAResource.h │ └── CDAContentType.m ├── CDAInputSanitizer.h ├── CDAConfiguration+Private.h ├── CDAResponse+Private.h ├── CDALocalizablePersistedEntry.m ├── CDAFallbackDictionary.h ├── CDARequestSerializer.h ├── CDARequest+Private.h ├── CDAResponseSerializer.h ├── CDAResponse.h ├── CDASyncedSpace+Private.h ├── CDANullabilityStubs.h ├── CDAFieldValueTransformer.h ├── CDAPersistedEntry.h ├── CDAField+Private.h ├── CDALocalizedPersistedEntry.h ├── ContentfulDeliveryAPI.h ├── CDAPersistedSpace.h ├── CDAContentTypeRegistry.h ├── CDAResponse.m ├── CDAUtilities.h ├── CDALocalizablePersistedEntry.h ├── CDAConfiguration.m ├── CDAInputSanitizer.m ├── CDAPersistedAsset.h ├── CDARequest.h ├── CDAFallbackDictionary.m ├── CDARequest.m ├── CDAContentTypeRegistry.m ├── CDARequestOperationManager.h ├── CDAConfiguration.h ├── CDAField.h ├── CDAClient+Private.h ├── CDARequestSerializer.m └── CDAFieldValueTransformer.m ├── Gemfile ├── ContentfulSDK.xcworkspace └── contents.xcworkspacedata ├── .gitignore ├── LICENSE ├── Podfile ├── .travis.yml ├── ContentfulDeliveryAPI.podspec ├── Podfile.lock └── Makefile /docs/search.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | DELIVERY_SDK_VERSION=3.0.0 2 | 3 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | export DELIVERY_SDK_VERSION=3.0.0 2 | -------------------------------------------------------------------------------- /Tests/Data/Recordings/ClientConfiguration.json: -------------------------------------------------------------------------------- 1 | [ 2 | 3 | ] -------------------------------------------------------------------------------- /docs/docsets/ContentfulDeliveryAPI.docset/Contents/Resources/Documents/search.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/contentful.objc/master/docs/img/dash.png -------------------------------------------------------------------------------- /docs/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/contentful.objc/master/docs/img/gh.png -------------------------------------------------------------------------------- /docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/contentful.objc/master/docs/img/carat.png -------------------------------------------------------------------------------- /Screenshots/Frameworks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/contentful.objc/master/Screenshots/Frameworks.png -------------------------------------------------------------------------------- /Screenshots/GeneralTab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/contentful.objc/master/Screenshots/GeneralTab.png -------------------------------------------------------------------------------- /Screenshots/SearchForFramework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/contentful.objc/master/Screenshots/SearchForFramework.png -------------------------------------------------------------------------------- /docs/docsets/ContentfulDeliveryAPI.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/contentful.objc/master/docs/docsets/ContentfulDeliveryAPI.tgz -------------------------------------------------------------------------------- /docs/undocumented.json: -------------------------------------------------------------------------------- 1 | { 2 | "warnings": [ 3 | 4 | ], 5 | "source_directory": "/Users/jpwright/Contentful/objc/SDK/contentful.objc" 6 | } -------------------------------------------------------------------------------- /.slather.yml: -------------------------------------------------------------------------------- 1 | coverage_service: coveralls 2 | workspace: ContentfulSDK.xcworkspace 3 | xcodeproj: ContentfulSDK.xcodeproj 4 | scheme: "ContentfulDeliveryAPI" 5 | ignore: 6 | - Examples/* 7 | - Tests/* 8 | -------------------------------------------------------------------------------- /docs/docsets/ContentfulDeliveryAPI.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/contentful.objc/master/docs/docsets/ContentfulDeliveryAPI.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /docs/docsets/ContentfulDeliveryAPI.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/contentful.objc/master/docs/docsets/ContentfulDeliveryAPI.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /docs/docsets/ContentfulDeliveryAPI.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/contentful.objc/master/docs/docsets/ContentfulDeliveryAPI.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /docs/docsets/ContentfulDeliveryAPI.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentful/contentful.objc/master/docs/docsets/ContentfulDeliveryAPI.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /scripts/unarcify-headers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sed -i '' 's/(nonatomic) BOOL/(nonatomic, assign) BOOL/' *.h 4 | sed -i '' 's/(nonatomic) CDAResourceType/(nonatomic, assign) CDAResourceType/' *.h 5 | sed -i '' 's/(nonatomic)/(nonatomic, retain)/' *.h 6 | -------------------------------------------------------------------------------- /ContentfulSDK.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Versions.h: -------------------------------------------------------------------------------- 1 | // 2 | // Versions.h 3 | // ContentfulSDK 4 | // 5 | // Created by JP Wright on 26.05.17. 6 | // 7 | // 8 | 9 | #ifndef Versions_h 10 | #define Versions_h 11 | 12 | #define DELIVERY_SDK_VERSION @"3.0.0" 13 | 14 | #endif /* Versions_h */ 15 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDADeletedAsset.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDADeletedAsset.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 26/03/14. 6 | // 7 | // 8 | 9 | #import "CDAResource.h" 10 | 11 | @interface CDADeletedAsset : CDAResource 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDADeletedEntry.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDADeletedEntry.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 26/03/14. 6 | // 7 | // 8 | 9 | #import "CDAResource.h" 10 | 11 | @interface CDADeletedEntry : CDAResource 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /scripts/set-delivery-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sed -i '' 's%\(^.*DELIVERY_SDK_VERSION @"\)\(.*\)\("\)%\1'$1'\3%g' Versions.h 4 | sed -i '' 's%\(^.*DELIVERY_SDK_VERSION=\)\(.*\)%\1'$1'%g' .env 5 | sed -i '' 's%\(^.*DELIVERY_SDK_VERSION=\)\(.*\)%\1'$1'%g' .envrc 6 | direnv allow 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'cocoapods' 4 | gem 'xcpretty' 5 | gem 'xcode-install' 6 | gem 'cocoapods-packager' 7 | gem 'cocoapods-docstats' 8 | gem 'cocoapods-deintegrate' 9 | gem 'cocoapods-clean' 10 | gem 'slather' 11 | gem 'jazzy' 12 | gem 'dotenv' 13 | 14 | -------------------------------------------------------------------------------- /Tests/Data/SyncTests/space-for-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Space", 4 | "id": "a7uc4j82xa5d" 5 | }, 6 | "name": "testtestets", 7 | "locales": [ 8 | { 9 | "code": "en-US", 10 | "default": true, 11 | "name": "U.S. English" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /Tests/Data/SyncTests/space.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Space", 4 | "id": "emh6o2ireilu" 5 | }, 6 | "name": "Test Space for Sync API", 7 | "locales": [ 8 | { 9 | "code": "en-US", 10 | "default": true, 11 | "name": "U.S. English" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /Tests/FakeNavigationController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FakeNavigationController.h 3 | // ContentfulSDK 4 | // 5 | // Created by JP Wright on 13/01/2017. 6 | // 7 | // 8 | 9 | #ifndef FakeNavigationController_h 10 | #define FakeNavigationController_h 11 | 12 | 13 | #endif /* FakeNavigationController_h */ 14 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAInputSanitizer.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAInputSanitizer.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 09/05/14. 6 | // 7 | // 8 | 9 | @import Foundation; 10 | 11 | @interface CDAInputSanitizer : NSObject 12 | 13 | +(id)sanitizeObject:(id)object; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Tests/Data/ComplexSyncTests/space.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Space", 4 | "id": "emh6o2ireilu" 5 | }, 6 | "name": "Test Space for Sync API", 7 | "locales": [ 8 | { 9 | "code": "en-US", 10 | "default": true, 11 | "name": "U.S. English" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /ContentfulSDK.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAConfiguration+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAConfiguration.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #import "CDAConfiguration.h" 10 | 11 | @interface CDAConfiguration (Private) 12 | 13 | @property (nonatomic) BOOL usesManagementAPI; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAResponse+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAResponse+Private.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #import "CDAResponse.h" 10 | 11 | @interface CDAResponse () 12 | 13 | +(instancetype)responseWithHTTPURLResponse:(NSHTTPURLResponse*)response; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDADeletedAsset.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDADeletedAsset.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 26/03/14. 6 | // 7 | // 8 | 9 | #import "CDADeletedAsset.h" 10 | 11 | @implementation CDADeletedAsset 12 | 13 | +(NSString*)CDAType { 14 | return @"DeletedAsset"; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDADeletedEntry.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDADeletedEntry.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 26/03/14. 6 | // 7 | // 8 | 9 | #import "CDADeletedEntry.h" 10 | 11 | @implementation CDADeletedEntry 12 | 13 | +(NSString*)CDAType { 14 | return @"DeletedEntry"; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDALocalizablePersistedEntry.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDALocalizablePersistedEntry.m 3 | // 4 | // 5 | // Created by Boris Bügling on 23/09/15. 6 | // 7 | // 8 | 9 | #import "CDALocalizablePersistedEntry.h" 10 | 11 | @implementation CDALocalizablePersistedEntry 12 | 13 | @dynamic identifier; 14 | @dynamic localizedEntries; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAFallbackDictionary.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAFallbackDictionary.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 28/03/14. 6 | // 7 | // 8 | 9 | @import Foundation; 10 | 11 | @interface CDAFallbackDictionary : NSDictionary 12 | 13 | -(instancetype)initWithDictionary:(NSDictionary *)dict fallbackDictionary:(NSDictionary*)fallbackDict; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDAError+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAError.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 05/03/14. 6 | // 7 | // 8 | 9 | #import "CDAError.h" 10 | 11 | @interface CDAError (Private) 12 | 13 | +(NSError*)buildErrorWithCode:(NSInteger)code userInfo:(NSDictionary*)userInfo; 14 | 15 | -(NSError*)errorRepresentationWithCode:(NSInteger)code; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDASpace+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDASpace+Private.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 06/03/14. 6 | // 7 | // 8 | 9 | #import "CDASpace.h" 10 | 11 | @interface CDASpace () 12 | 13 | @property (nonatomic) NSString* defaultLocale; 14 | @property (nonatomic, readonly) NSArray* localeCodes; 15 | @property (nonatomic) NSArray* locales; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDARequestSerializer.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDARequestSerializer.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 21/07/14. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface CDARequestSerializer : AFJSONRequestSerializer 12 | 13 | @property (nonatomic) NSString* userAgent; 14 | 15 | -(instancetype)initWithAccessToken:(NSString*)accessToken; 16 | @end 17 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDAOrganizationContainer.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAOrganizationContainer.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 29/07/14. 6 | // 7 | // 8 | 9 | #if __has_feature(modules) 10 | @import Foundation; 11 | #else 12 | #import 13 | #endif 14 | 15 | 16 | @protocol CDAOrganizationContainer 17 | 18 | @property (nonatomic) NSArray* organizations; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDARequest+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDARequest+Private.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 10/03/14. 6 | // 7 | // 8 | 9 | #if __has_feature(modules) 10 | @import Foundation; 11 | #else 12 | #import 13 | #endif 14 | 15 | #import "CDARequest.h" 16 | 17 | @interface CDARequest () 18 | 19 | -(instancetype)initWithSessionTask:(NSURLSessionTask *)task; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Appledoc 2 | 3 | doc 4 | 5 | ## OS X 6 | 7 | .DS_Store 8 | 9 | ## Cocoapods 10 | Pods/ 11 | 12 | ### Seed database 13 | Examples/SeedDatabase/*/Pods 14 | 15 | .pt 16 | Examples/ContentfulDeliveryAPI.zip 17 | Examples/UFO.zip 18 | Examples/UFO/Distribution/ContentfulDeliveryAPI.framework/ 19 | compile_commands.json 20 | .gutter.json 21 | 22 | ## Xcode 23 | 24 | *.gcda 25 | *.gcno 26 | *.xccheckout 27 | xcuserdata 28 | .idea 29 | *.xcscmblueprint 30 | 31 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAResponseSerializer.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAResponseSerializer.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @class CDAClient; 12 | 13 | @interface CDAResponseSerializer : AFJSONResponseSerializer 14 | 15 | @property (nonatomic, weak, readonly) CDAClient* client; 16 | 17 | -(instancetype)initWithClient:(CDAClient*)client; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAResponse.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAResponse.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #if __has_feature(modules) 10 | @import Foundation; 11 | #else 12 | #import 13 | #endif 14 | 15 | /** 16 | A `CDAResponse` encapsulates meta-information about a reponse received from the server, such as the 17 | HTTP status code or headers. 18 | */ 19 | @interface CDAResponse : NSHTTPURLResponse 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDAArray+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAArray+Private.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #import "CDAArray.h" 10 | 11 | @interface CDAArray () 12 | 13 | @property (nonatomic, readonly) NSURL* nextPageUrl; 14 | @property (nonatomic, readonly) NSURL* nextSyncUrl; 15 | @property (nonatomic) NSDictionary* query; 16 | 17 | -(instancetype)initWithItems:(NSArray*)items client:(CDAClient*)client; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /scripts/travis-build-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -x -o pipefail 4 | 5 | # -jobs -- specify the number of concurrent jobs 6 | # `sysctl -n hw.ncpu` -- fetch number of 'logical' cores in macOS machine 7 | xcodebuild -jobs `sysctl -n hw.ncpu` test -workspace ContentfulSDK.xcworkspace -scheme ContentfulDeliveryAPI \ 8 | -sdk ${IOS_SDK} -destination "platform=iOS Simulator,name=${DEVICE_NAME},OS=${IOS_VERSION}" \ 9 | ONLY_ACTIVE_ARCH=NO CODE_SIGNING_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty -c 10 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDASyncedSpace+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDASyncedSpace+Private.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 25/03/14. 6 | // 7 | // 8 | 9 | #import "CDASyncedSpace.h" 10 | 11 | @interface CDASyncedSpace () 12 | 13 | @property (nonatomic, weak) CDAClient* client; 14 | @property (nonatomic) NSURL* nextPageUrl; 15 | @property (nonatomic) NSURL* nextSyncUrl; 16 | 17 | -(instancetype)initWithAssets:(NSArray*)assets entries:(NSArray*)entries; 18 | -(void)updateLastSyncTimestamp; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDANullabilityStubs.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDANullabilityStubs.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 15/04/15. 6 | // 7 | // 8 | 9 | // Thanks to https://gist.github.com/steipete/d9f519858fe5fb5533eb 10 | #if !__has_feature(nullability) 11 | #define NS_ASSUME_NONNULL_BEGIN 12 | #define NS_ASSUME_NONNULL_END 13 | #define nullable 14 | #define nonnull 15 | #define null_unspecified 16 | #define null_resettable 17 | #define __nullable 18 | #define __nonnull 19 | #define __null_unspecified 20 | #endif 21 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAFieldValueTransformer.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAFieldValueTransformer.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 09/03/14. 6 | // 7 | // 8 | 9 | #import "CDAField.h" 10 | 11 | @class CDAClient; 12 | 13 | @interface CDAFieldValueTransformer : NSValueTransformer 14 | 15 | @property (nonatomic) CDAFieldType itemType; 16 | 17 | +(instancetype)transformerOfType:(CDAFieldType)type 18 | client:(CDAClient*)client 19 | localizationAvailable:(BOOL)localizationAvailable; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAPersistedEntry.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAPersistedEntry.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 14/04/14. 6 | // 7 | // 8 | 9 | #if __has_feature(modules) 10 | @import Foundation; 11 | #else 12 | #import 13 | #endif 14 | 15 | /** 16 | * Any class representing Entries saved to a persistent store needs to conform to this protocol. 17 | */ 18 | @protocol CDAPersistedEntry 19 | 20 | /** The `sys.id` of the Entry. */ 21 | @property (nonatomic) NSString* identifier; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAField+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAField+Private.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #import "CDAField.h" 10 | 11 | @class CDAClient; 12 | 13 | @interface CDAField () 14 | 15 | @property (nonatomic) NSString* name; 16 | @property (nonatomic) CDAFieldType type; 17 | 18 | -(instancetype)initWithDictionary:(NSDictionary *)dictionary 19 | client:(CDAClient*)client 20 | localizationAvailable:(BOOL)localizationAvailable; 21 | -(id)parseValue:(id)value; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDALocalizedPersistedEntry.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDALocalizedPersistedEntry.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 22/09/15. 6 | // 7 | // 8 | 9 | #if __has_feature(modules) 10 | @import Foundation; 11 | #else 12 | #import 13 | #endif 14 | 15 | #import "CDAPersistedEntry.h" 16 | 17 | /** 18 | * Any class representing localized Entries saved to a persistent store needs to conform to this 19 | * protocol. 20 | */ 21 | @protocol CDALocalizedPersistedEntry 22 | 23 | /** The locale of this persisted entry */ 24 | @property (nonatomic) NSString* locale; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/ContentfulDeliveryAPI.h: -------------------------------------------------------------------------------- 1 | // 2 | // ContentfulDeliveryAPI.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #if __has_feature(modules) 10 | @import Foundation; 11 | #else 12 | #import 13 | #endif 14 | 15 | #import "CDAArray.h" 16 | #import "CDAAsset.h" 17 | #import "CDAClient.h" 18 | #import "CDAConfiguration.h" 19 | #import "CDAContentType.h" 20 | #import "CDAEntry.h" 21 | #import "CDAError.h" 22 | #import "CDAField.h" 23 | #import "CDAPersistenceManager.h" 24 | #import "CDARequest.h" 25 | #import "CDAResponse.h" 26 | #import "CDASpace.h" 27 | #import "CDASyncedSpace.h" 28 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAPersistedSpace.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAPersistedSpace.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 14/04/14. 6 | // 7 | // 8 | 9 | #if __has_feature(modules) 10 | @import Foundation; 11 | #else 12 | #import 13 | #endif 14 | 15 | /** 16 | * Any class representing synchronized Spaces saved to a persistent store needs to conform 17 | * to this protocol. 18 | */ 19 | @protocol CDAPersistedSpace 20 | 21 | /** Timestamp of the last synchronization operation. */ 22 | @property (nonatomic) NSDate* lastSyncTimestamp; 23 | /** Token for the next synchronization operation. */ 24 | @property (nonatomic) NSString* syncToken; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDAError.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAError.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 05/03/14. 6 | // 7 | // 8 | 9 | #import "CDANullabilityStubs.h" 10 | #import "CDAResource.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | extern NSString* const CDAErrorDomain; 15 | 16 | /** 17 | * Details about errors which occured on the server. 18 | */ 19 | @interface CDAError : CDAResource 20 | 21 | /** 22 | * Detailed representation of the error. 23 | */ 24 | @property (nonatomic, readonly, nullable) NSDictionary* details; 25 | 26 | /** 27 | * Short message on the cause of the error. 28 | */ 29 | @property (nonatomic, readonly, nullable) NSString* message; 30 | 31 | @end 32 | 33 | NS_ASSUME_NONNULL_END 34 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDASpace.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDASpace.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 06/03/14. 6 | // 7 | // 8 | 9 | #import "CDANullabilityStubs.h" 10 | #import "CDAResource.h" 11 | 12 | /** 13 | Spaces are containers for Content Types, Entries and Assets. 14 | */ 15 | @interface CDASpace : CDAResource 16 | 17 | /** @name Accessing Meta-Data */ 18 | 19 | /** Default locale for this Space. */ 20 | @property (nonatomic, readonly) NSString* __nonnull defaultLocale; 21 | /** Possible locales used for Entries in this Space. */ 22 | @property (nonatomic, readonly) NSArray* __nullable locales; 23 | /** The name of this Space. */ 24 | @property (nonatomic, readonly) NSString* __nullable name; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDAEntry+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAEntry+Private.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #import "CDAEntry.h" 10 | 11 | @interface CDAEntry () 12 | 13 | -(NSArray*)findUnresolvedAssets; 14 | -(NSArray*)findUnresolvedEntries; 15 | -(NSDictionary*)localizedFields; 16 | -(void)resolveLinksWithIncludedAssets:(NSDictionary*)assets 17 | entries:(NSDictionary*)entries 18 | usingBlock:(CDAResource* (^)(CDAResource* resource, NSDictionary* assets, 19 | NSDictionary* entries))resolver; 20 | -(void)setValue:(id)value forFieldWithName:(NSString *)key; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAContentTypeRegistry.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAContentTypeRegistry.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | @import Foundation; 10 | 11 | @class CDAContentType; 12 | 13 | @interface CDAContentTypeRegistry : NSObject 14 | 15 | @property (nonatomic, readonly) BOOL hasCustomClasses; 16 | 17 | -(void)addContentType:(CDAContentType*)contentType; 18 | -(CDAContentType*)contentTypeForIdentifier:(NSString*)identifier; 19 | -(Class)customClassForContentType:(CDAContentType*)contentType; 20 | -(void)registerClass:(Class)customClass forContentType:(CDAContentType*)contentType; 21 | -(void)registerClass:(Class)customClass forContentTypeWithIdentifier:(NSString*)identifier; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Tests/Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /docs/docsets/ContentfulDeliveryAPI.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.contentfuldeliveryapi 7 | CFBundleName 8 | ContentfulDeliveryAPI 9 | DocSetPlatformFamily 10 | contentfuldeliveryapi 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | documentationdocumentation0%0% -------------------------------------------------------------------------------- /Tests/Data/SyncTests/deleted.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "type": "DeletedEntry", 9 | "id": "1gQ4P2tG7QaGkQwkC4a6Gg", 10 | "space": { 11 | "sys": { 12 | "type": "Link", 13 | "linkType": "Space", 14 | "id": "emh6o2ireilu" 15 | } 16 | }, 17 | "revision": 1, 18 | "createdAt": "2014-03-25T11:05:16.051Z", 19 | "updatedAt": "2014-03-25T11:05:16.051Z", 20 | "deletedAt": "2014-03-25T11:05:16.051Z" 21 | } 22 | } 23 | ], 24 | "nextSyncUrl": "https://cdn.contentful.com/spaces/emh6o2ireilu/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyZew5xDN04dJg3DkmBAw4XDh8OEw5o5UVhIw6nDlFjDoBxIasKIDsKIw4VcIV18GicdwoTDjCtoMiFAfcKiwrRKIsKYwrzCmMKBw4ZhwrdhwrsGa8KTwpQ6w6A" 25 | } 26 | -------------------------------------------------------------------------------- /Tests/Data/SyncTests/deleted-asset.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "type": "DeletedAsset", 9 | "id": "2q1Ns7Oygo2mAgoweuMCAA", 10 | "space": { 11 | "sys": { 12 | "type": "Link", 13 | "linkType": "Space", 14 | "id": "emh6o2ireilu" 15 | } 16 | }, 17 | "revision": 1, 18 | "createdAt": "2014-03-25T11:09:14.488Z", 19 | "updatedAt": "2014-03-25T11:09:14.488Z", 20 | "deletedAt": "2014-03-25T11:09:14.488Z" 21 | } 22 | } 23 | ], 24 | "nextSyncUrl": "https://cdn.contentful.com/spaces/emh6o2ireilu/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyYHPUPDhggxwr5qw5RBbMKWw4VjOg3DumTDg0_CgsKcYsO8UcOZfMKLw4sKUcOnJcKxfDUkGWwxNMOVw4AiacK5Bmo4ScOhI0g2cXLClxTClsOyE8OOc8O3" 25 | } 26 | -------------------------------------------------------------------------------- /scripts/reference-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Generating Jazzy Reference Documentation" 4 | 5 | SDK_NAME="ContentfulDeliveryAPI" 6 | xcodebuild_arguments="--objc,ContentfulDeliveryAPI/ContentfulDeliveryAPI.h,--,-x,objective-c,-isysroot,$(xcrun --show-sdk-path),-I,$(pwd)" 7 | 8 | jazzy \ 9 | --clean \ 10 | --objc \ 11 | --author Contentful \ 12 | --author_url https://www.contentful.com \ 13 | --github_url https://github.com/contentful/contentful.objc \ 14 | --github-file-prefix https://github.com/contentful/contentful.objc/tree/$DELIVERY_SDK_VERSION \ 15 | --module-version $DELIVERY_SDK_VERSION \ 16 | --module $SDK_NAME \ 17 | --umbrella-header ContentfulDeliveryAPI/ContentfulDeliveryAPI.h \ 18 | --framework-root ./ContentfulDeliveryAPI/ \ 19 | --sdk iphonesimulator \ 20 | --theme apple 21 | # --xcodebuild-arguments ${xcodebuild_arguments} \ 22 | -------------------------------------------------------------------------------- /Tests/Data/ComplexSyncTests/ComplexLinkTestUpdate1.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "type": "DeletedEntry", 9 | "id": "5ZmiC472OAg2SY2W0QM6Gs", 10 | "space": { 11 | "sys": { 12 | "type": "Link", 13 | "linkType": "Space", 14 | "id": "emh6o2ireilu" 15 | } 16 | }, 17 | "revision": 1, 18 | "createdAt": "2014-04-11T11:40:02.150Z", 19 | "updatedAt": "2014-04-11T11:40:02.150Z", 20 | "deletedAt": "2014-04-11T11:40:02.150Z" 21 | } 22 | } 23 | ], 24 | "nextSyncUrl": "https://cdn.contentful.com/spaces/emh6o2ireilu/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyY9wrEfW8KYwpROLH55G8O-U2rCq8OsQn3DvcOrw4cGwpkjIAvDgWxYwrITw4xUa8O4UCXDojMJDk8fw6RzSMK6J2vDqMOUJm_CiMKaw6lVF1jCg2vCosOFwpo" 25 | } 26 | -------------------------------------------------------------------------------- /Tests/Data/ComplexSyncTests/AddContentTypesContentTypes.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "fields": [ 11 | { 12 | "name": "such field", 13 | "id": "suchField", 14 | "type": "Symbol" 15 | } 16 | ], 17 | "name": "⚡️ new content type", 18 | "sys": { 19 | "space": { 20 | "sys": { 21 | "type": "Link", 22 | "linkType": "Space", 23 | "id": "emh6o2ireilu" 24 | } 25 | }, 26 | "type": "ContentType", 27 | "id": "5kLp8FbRwAG0kcOOYa6GMa", 28 | "revision": 1, 29 | "createdAt": "2014-04-11T08:39:04.421Z", 30 | "updatedAt": "2014-04-11T08:39:04.421Z" 31 | }, 32 | "displayField": "suchField" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAResponse.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDAResponse.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #import "CDAResponse+Private.h" 10 | #import "CDAUtilities.h" 11 | 12 | @implementation CDAResponse 13 | 14 | +(instancetype)responseWithHTTPURLResponse:(NSHTTPURLResponse*)response { 15 | return [[[self class] alloc] initWithHTTPURLResponse:response]; 16 | } 17 | 18 | #pragma mark - 19 | 20 | -(instancetype)initWithHTTPURLResponse:(NSHTTPURLResponse*)response { 21 | NSURL* responseURL = response.URL ?: [NSURL URLWithString:@"http://example.com/"]; 22 | NSParameterAssert(responseURL); 23 | 24 | self = [super initWithURL:responseURL 25 | statusCode:response.statusCode 26 | HTTPVersion:@"HTTP/1.1" 27 | headerFields:response.allHeaderFields]; 28 | return self; 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /Tests/Data/ComplexSyncTests/LinkTestResolve.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "emh6o2ireilu" 16 | } 17 | }, 18 | "type": "Entry", 19 | "contentType": { 20 | "sys": { 21 | "type": "Link", 22 | "linkType": "ContentType", 23 | "id": "51LZmvenywOe8aig28sCgY" 24 | } 25 | }, 26 | "id": "6nRlw4CofeeICEYgIqaIIg", 27 | "revision": 1, 28 | "createdAt": "2014-04-09T11:48:42.407Z", 29 | "updatedAt": "2014-04-09T11:48:42.407Z", 30 | "locale": "en-US" 31 | }, 32 | "fields": { 33 | "name": "foobar" 34 | } 35 | } 36 | ], 37 | "includes": {} 38 | } 39 | -------------------------------------------------------------------------------- /Tests/Data/ComplexSyncTests/AssetTestResolve2.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "emh6o2ireilu" 16 | } 17 | }, 18 | "type": "Entry", 19 | "contentType": { 20 | "sys": { 21 | "type": "Link", 22 | "linkType": "ContentType", 23 | "id": "51LZmvenywOe8aig28sCgY" 24 | } 25 | }, 26 | "id": "6nRlw4CofeeICEYgIqaIIg", 27 | "revision": 1, 28 | "createdAt": "2014-04-09T11:48:42.407Z", 29 | "updatedAt": "2014-04-09T11:48:42.407Z", 30 | "locale": "en-US" 31 | }, 32 | "fields": { 33 | "name": "foobar" 34 | } 35 | } 36 | ], 37 | "includes": {} 38 | } 39 | -------------------------------------------------------------------------------- /Tests/Data/SyncTests/content-types-for-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 1, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "a7uc4j82xa5d" 16 | } 17 | }, 18 | "id": "test", 19 | "type": "ContentType", 20 | "createdAt": "2016-05-11T15:15:36.054Z", 21 | "updatedAt": "2016-05-11T15:15:36.054Z", 22 | "revision": 1 23 | }, 24 | "displayField": "test", 25 | "name": "test", 26 | "description": "", 27 | "fields": [ 28 | { 29 | "id": "test", 30 | "name": "test", 31 | "type": "Symbol", 32 | "localized": false, 33 | "required": false, 34 | "disabled": false 35 | } 36 | ] 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /Tests/Data/ComplexSyncTests/NewEntryAndDeletionResolve2.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "emh6o2ireilu" 16 | } 17 | }, 18 | "type": "Entry", 19 | "contentType": { 20 | "sys": { 21 | "type": "Link", 22 | "linkType": "ContentType", 23 | "id": "51LZmvenywOe8aig28sCgY" 24 | } 25 | }, 26 | "id": "6nRlw4CofeeICEYgIqaIIg", 27 | "revision": 1, 28 | "createdAt": "2014-04-09T11:48:42.407Z", 29 | "updatedAt": "2014-04-09T11:48:42.407Z", 30 | "locale": "en-US" 31 | }, 32 | "fields": { 33 | "name": "foobar" 34 | } 35 | } 36 | ], 37 | "includes": {} 38 | } 39 | -------------------------------------------------------------------------------- /Tests/SyncBaseTestCase.h: -------------------------------------------------------------------------------- 1 | // 2 | // SyncBaseTestCase.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 09/04/14. 6 | // 7 | // 8 | 9 | #import "ContentfulBaseTestCase.h" 10 | 11 | @interface SyncBaseTestCase : ContentfulBaseTestCase 12 | 13 | @property (nonatomic, readonly) BOOL contentTypesWereFetched; 14 | @property (nonatomic) BOOL expectFieldsInDeletedResources; 15 | @property (nonatomic, readonly) NSUInteger numberOfAssetsCreated; 16 | @property (nonatomic, readonly) NSUInteger numberOfEntriesCreated; 17 | @property (nonatomic, readonly) NSUInteger numberOfAssetsDeleted; 18 | @property (nonatomic, readonly) NSUInteger numberOfEntriesDeleted; 19 | @property (nonatomic, readonly) NSUInteger numberOfAssetsUpdated; 20 | @property (nonatomic, readonly) NSUInteger numberOfEntriesUpdated; 21 | 22 | -(void)addDummyContentType; 23 | -(CDAClient*)buildClient; 24 | -(CDAClient*)mockContentTypeRetrievalForClient:(CDAClient*)client; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAUtilities.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAUtilities.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | @import Foundation; 10 | 11 | #import "CDAClient.h" 12 | 13 | @class CDAResource; 14 | 15 | NSString* CDACacheDirectory(void); 16 | NSString* CDACacheFileNameForQuery(CDAClient* client, CDAResourceType resourceType, NSDictionary* query); 17 | NSString* CDACacheFileNameForResource(CDAResource* resource); 18 | NSArray* CDAClassGetSubclasses(Class parentClass); 19 | BOOL CDAClassIsOfType(Class someClass, Class otherClass); 20 | void CDADecodeObjectWithCoder(id object, NSCoder* aDecoder); 21 | void CDAEncodeObjectWithCoder(id object, NSCoder* aCoder); 22 | BOOL CDAIsNoNetworkError(NSError* error); 23 | id CDAReadItemFromFileURL(NSURL* fileURL, CDAClient* client); 24 | NSString* CDASquashCharactersFromSetInString(NSCharacterSet* characterSet, NSString* string); 25 | NSString* CDAValueForQueryParameter(NSURL* url, NSString* queryParameter); 26 | -------------------------------------------------------------------------------- /Tests/FakeNavigationController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FakeNavigationController.m 3 | // ContentfulSDK 4 | // 5 | // Created by JP Wright on 13/01/2017. 6 | // 7 | // 8 | 9 | #import "FakeNavigationController.h" 10 | 11 | @interface FakeNavigationController () 12 | 13 | @end 14 | 15 | @implementation FakeNavigationController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view. 20 | } 21 | 22 | - (void)didReceiveMemoryWarning { 23 | [super didReceiveMemoryWarning]; 24 | // Dispose of any resources that can be recreated. 25 | } 26 | 27 | /* 28 | #pragma mark - Navigation 29 | 30 | // In a storyboard-based application, you will often want to do a little preparation before navigation 31 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 32 | // Get the new view controller using [segue destinationViewController]. 33 | // Pass the selected object to the new view controller. 34 | } 35 | */ 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /Tests/Data/SyncTests/update-for-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "space": { 9 | "sys": { 10 | "type": "Link", 11 | "linkType": "Space", 12 | "id": "a7uc4j82xa5d" 13 | } 14 | }, 15 | "id": "3f5qqrqbsAGMqAeIkkMYeK", 16 | "type": "Entry", 17 | "createdAt": "2016-05-11T15:15:46.680Z", 18 | "updatedAt": "2016-05-11T15:17:12.148Z", 19 | "revision": 2, 20 | "contentType": { 21 | "sys": { 22 | "type": "Link", 23 | "linkType": "ContentType", 24 | "id": "test" 25 | } 26 | } 27 | }, 28 | "fields": { 29 | "test": { 30 | "en-US": null 31 | } 32 | } 33 | } 34 | ], 35 | "nextSyncUrl": "https://cdn.contentful.com/spaces/a7uc4j82xa5d/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdybDiMOEwoFaGsOjw7_DvwQdCmZXwqnChsOcAcOkUysgwrTCsy4bw6jCkcO9wqchwoFvwppIGWxiwp7ClwfCmQfDunpyUy1KOEptd8Oyf2rCn0TDvcO6LXbDhEEG" 36 | } 37 | -------------------------------------------------------------------------------- /Tests/Data/SyncTests/initial-for-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "space": { 9 | "sys": { 10 | "type": "Link", 11 | "linkType": "Space", 12 | "id": "a7uc4j82xa5d" 13 | } 14 | }, 15 | "id": "3f5qqrqbsAGMqAeIkkMYeK", 16 | "type": "Entry", 17 | "createdAt": "2016-05-11T15:15:46.680Z", 18 | "updatedAt": "2016-05-11T15:15:46.680Z", 19 | "revision": 1, 20 | "contentType": { 21 | "sys": { 22 | "type": "Link", 23 | "linkType": "ContentType", 24 | "id": "test" 25 | } 26 | } 27 | }, 28 | "fields": { 29 | "test": { 30 | "en-US": "yolo" 31 | } 32 | } 33 | } 34 | ], 35 | "nextSyncUrl": "https://cdn.contentful.com/spaces/a7uc4j82xa5d/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyY9ZMK9AsOcwqzCqmEWwr7CucOhw7LCm8ONZQICw4PCo8Olwq0lwofCocO2C3rDmAM_wr_DuMOcDBVGwqnCpcOBXsKXw6M9J8O4w4EUw7Zww6TCtsKwOzfCucOpVkLDtWXCsMOydg" 36 | } 37 | -------------------------------------------------------------------------------- /Tests/Data/ComplexSyncTests/AddContentTypesUpdate.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "space": { 9 | "sys": { 10 | "type": "Link", 11 | "linkType": "Space", 12 | "id": "emh6o2ireilu" 13 | } 14 | }, 15 | "type": "Entry", 16 | "contentType": { 17 | "sys": { 18 | "type": "Link", 19 | "linkType": "ContentType", 20 | "id": "5kLp8FbRwAG0kcOOYa6GMa" 21 | } 22 | }, 23 | "id": "7FQCYfimc0aQa8okuCakAS", 24 | "revision": 1, 25 | "createdAt": "2014-04-11T08:39:23.206Z", 26 | "updatedAt": "2014-04-11T08:39:23.206Z" 27 | }, 28 | "fields": { 29 | "suchField": { 30 | "en-US": "new entry such crash wow" 31 | } 32 | } 33 | } 34 | ], 35 | "nextSyncUrl": "https://cdn.contentful.com/spaces/emh6o2ireilu/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyZhfMOjXcKJw54MwqHChsKFYwhawqzDui4qwrfClCzCoHcuwrEFHsO_X1UFZMOpw7DCoUY0w4sMw6BcbDLDlsKkJ8KTRsK6M8K-eMKGwoPDslnDnDDDrcK0MmoCWMK1" 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Contentful GmbH - https://www.contentful.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Tests/Data/ComplexSyncTests/AssetTestResolve.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "fields": { 11 | "title": "contentful-logo", 12 | "file": { 13 | "fileName": "512_black.png", 14 | "contentType": "image/png", 15 | "details": { 16 | "image": { 17 | "width": 512, 18 | "height": 512 19 | }, 20 | "size": 4189 21 | }, 22 | "url": "//images.contentful.com/emh6o2ireilu/2q1Ns7Oygo2mAgoweuMCAA/7a4eb574952f359b851b3f353573b708/512_black.png" 23 | } 24 | }, 25 | "sys": { 26 | "space": { 27 | "sys": { 28 | "type": "Link", 29 | "linkType": "Space", 30 | "id": "emh6o2ireilu" 31 | } 32 | }, 33 | "type": "Asset", 34 | "id": "2q1Ns7Oygo2mAgoweuMCAA", 35 | "revision": 2, 36 | "createdAt": "2014-03-25T11:01:41.057Z", 37 | "updatedAt": "2014-04-10T08:46:27.961Z", 38 | "locale": "en-US" 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDALocalizablePersistedEntry.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDALocalizablePersistedEntry.h 3 | // 4 | // 5 | // Created by Boris Bügling on 23/09/15. 6 | // 7 | // 8 | 9 | #if __has_feature(modules) 10 | @import CoreData; 11 | #else 12 | #import 13 | #endif 14 | 15 | #import "CDALocalizedPersistedEntry.h" 16 | 17 | /** 18 | * Any class representing a set of localizable persisted entries should inherit from this class. 19 | */ 20 | @interface CDALocalizablePersistedEntry : NSManagedObject 21 | 22 | /** Set of localized entries of type `CDALocalizedPersistedEntry` */ 23 | @property (nonatomic) NSSet* localizedEntries; 24 | 25 | @end 26 | 27 | #pragma mark - 28 | 29 | @interface CDALocalizablePersistedEntry (Additions) 30 | 31 | /** 32 | Add a localized entry to the collection. 33 | 34 | @param entry The entry to add to the collection. 35 | */ 36 | - (void)addLocalizedEntriesObject:(id)entry; 37 | 38 | /** 39 | Remove a localized entry to the collection. 40 | 41 | @param entry The entry to remove from the collection. 42 | */ 43 | - (void)removeLocalizedEntriesObject:(id)entry; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Tests/Data/ComplexSyncTests/NewEntryAndDeletionResolve.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "fields": { 11 | "title": "contentful-logo", 12 | "file": { 13 | "fileName": "512_black.png", 14 | "contentType": "image/png", 15 | "details": { 16 | "image": { 17 | "width": 512, 18 | "height": 512 19 | }, 20 | "size": 4189 21 | }, 22 | "url": "//images.contentful.com/emh6o2ireilu/2q1Ns7Oygo2mAgoweuMCAA/7a4eb574952f359b851b3f353573b708/512_black.png" 23 | } 24 | }, 25 | "sys": { 26 | "space": { 27 | "sys": { 28 | "type": "Link", 29 | "linkType": "Space", 30 | "id": "emh6o2ireilu" 31 | } 32 | }, 33 | "type": "Asset", 34 | "id": "2q1Ns7Oygo2mAgoweuMCAA", 35 | "revision": 2, 36 | "createdAt": "2014-03-25T11:01:41.057Z", 37 | "updatedAt": "2014-04-10T08:46:27.961Z", 38 | "locale": "en-US" 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /Tests/Data/SyncTests/added.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "space": { 9 | "sys": { 10 | "type": "Link", 11 | "linkType": "Space", 12 | "id": "emh6o2ireilu" 13 | } 14 | }, 15 | "type": "Entry", 16 | "contentType": { 17 | "sys": { 18 | "type": "Link", 19 | "linkType": "ContentType", 20 | "id": "6bAvxqodl6s4MoKuWYkmqe" 21 | } 22 | }, 23 | "id": "1gQ4P2tG7QaGkQwkC4a6Gg", 24 | "revision": 1, 25 | "createdAt": "2014-03-25T11:04:03.822Z", 26 | "updatedAt": "2014-03-25T11:04:03.822Z" 27 | }, 28 | "fields": { 29 | "title": { 30 | "en-US": "Second entry" 31 | }, 32 | "body": { 33 | "en-US": "some text" 34 | } 35 | } 36 | } 37 | ], 38 | "nextSyncUrl": "https://cdn.contentful.com/spaces/emh6o2ireilu/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyY0w4bCiMKOWDIFw61bwqQ_w73CnMKsB8KpwrFZPsOZw5ZQwqDDnUA0w5tOPRtwwoAkwpJMTzghdEnDjCkiw5fCuynDlsO5DyvCsjgQa2TDisKNZ8Kqw4TCjhZIGQ" 39 | } 40 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAConfiguration.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDAConfiguration.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #import "CDAConfiguration+Private.h" 10 | 11 | NSString* const CDA_DEFAULT_SERVER = @"cdn.contentful.com"; 12 | static NSString* const CDA_PREVIEW_SERVER = @"preview.contentful.com"; 13 | 14 | @interface CDAConfiguration () 15 | 16 | @property (nonatomic) BOOL usesManagementAPI; 17 | 18 | @end 19 | 20 | #pragma mark - 21 | 22 | @implementation CDAConfiguration 23 | 24 | +(instancetype)defaultConfiguration { 25 | CDAConfiguration* configuration = [CDAConfiguration new]; 26 | configuration.filterNonExistingResources = NO; 27 | configuration.previewMode = NO; 28 | configuration.rateLimiting = NO; 29 | configuration.secure = YES; 30 | configuration.server = CDA_DEFAULT_SERVER; 31 | return configuration; 32 | } 33 | 34 | #pragma mark - 35 | 36 | -(void)setPreviewMode:(BOOL)previewMode { 37 | if (_previewMode == previewMode) { 38 | return; 39 | } 40 | 41 | _previewMode = previewMode; 42 | 43 | if (previewMode) { 44 | self.server = CDA_PREVIEW_SERVER; 45 | } 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | source 'https://github.com/CocoaPods/Specs.git' 4 | source 'https://github.com/contentful/CocoaPodsSpecs.git' 5 | 6 | ## iOS 7 | target 'ContentfulDeliveryAPI' do 8 | podspec :path => 'ContentfulDeliveryAPI.podspec' 9 | platform :ios, "9.3" 10 | end 11 | 12 | ## tvOS 13 | target 'ContentfulDeliveryAPI_tvOS' do 14 | podspec :path => 'ContentfulDeliveryAPI.podspec' 15 | platform :tvos, "9.2" 16 | end 17 | 18 | target 'ContentfulDeliveryAPI_macOS' do 19 | podspec :path => 'ContentfulDeliveryAPI.podspec' 20 | platform :osx, "10.12" 21 | end 22 | 23 | 24 | # Cocoapods docs are wrong and don't work for 25 | target 'DeliveryTests' do 26 | platform :ios, "9.3" 27 | 28 | pod 'CCLRequestReplay', :git => 'https://github.com/neonichu/CCLRequestReplay.git' 29 | pod 'OCMock', :inhibit_warnings => true 30 | pod 'VCRURLConnection', '= 0.2.4', :inhibit_warnings => true 31 | pod 'FBSnapshotTestCase/Core', :inhibit_warnings => true 32 | end 33 | 34 | 35 | 36 | 37 | ## Post install 38 | post_install do |installer| 39 | 40 | installer.pods_project.targets.each do |target| 41 | target.build_configurations.each do |config| 42 | config.build_settings['ONLY_ACTIVE_ARCH'] = "NO" 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDAArray.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAArray.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #import "CDANullabilityStubs.h" 10 | #import "CDAResource.h" 11 | 12 | /** 13 | A list of resources which was retrieved from the server. 14 | */ 15 | @interface CDAArray : CDAResource 16 | 17 | /** @name Accessing Local Data */ 18 | 19 | /** A list of items available locally. */ 20 | @property (nonatomic, readonly) NSArray* __nonnull items; 21 | 22 | /** @name Information about Remote Data */ 23 | 24 | /** The maximum number of resources available in items. */ 25 | @property (nonatomic, readonly) NSUInteger limit; 26 | /** The offset of items in terms of all data available on the server. */ 27 | @property (nonatomic, readonly) NSUInteger skip; 28 | /** The total number of resources which are available on the server. */ 29 | @property (nonatomic, readonly) NSUInteger total; 30 | 31 | /** @name Errors in Remote Data */ 32 | 33 | /** 34 | * An array of non-fatal errors encountered while fetching the receiver. 35 | * 36 | * For example, if there is a link to an Entry which no longer exists, there will be a `notResolvable` 37 | * error in the `errors` array. 38 | */ 39 | @property (nonatomic, readonly) NSArray* __nonnull errors; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /Tests/ContentfulBaseTestCase.h: -------------------------------------------------------------------------------- 1 | // 2 | // ContentfulBaseTestCase.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 06/03/14. 6 | // 7 | // 8 | 9 | @import XCTest; 10 | 11 | #import 12 | #import 13 | 14 | @interface ContentfulBaseTestCase : XCTestCase 15 | 16 | @property (nonatomic) CDAClient* client; 17 | 18 | - (void)addRecordingWithJSONNamed:(NSString*)JSONName 19 | inDirectory:(NSString*)directory 20 | matcher:(CCLURLRequestMatcher)matcher; 21 | - (void)addResponseWithData:(NSData*)data 22 | statusCode:(NSInteger)statusCode 23 | headers:(NSDictionary*)headers 24 | matcher:(CCLURLRequestMatcher)matcher; 25 | - (void)addResponseWithError:(NSError*)error matcher:(CCLURLRequestMatcher)matcher; 26 | - (void)assertField:(CDAField*)field 27 | hasIdentifier:(NSString*)identifier 28 | name:(NSString*)name 29 | type:(CDAFieldType)type; 30 | - (CDAEntry*)customEntryHelperWithFields:(NSDictionary*)fields; 31 | - (void)removeAllStubs; 32 | - (void)setUpCCLRequestReplayForNSURLSession; 33 | - (void)stubHTTPRequestUsingFixtures:(NSDictionary*)fixtureMap inDirectory:(NSString*)directoryName; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /Tests/Data/SyncTests/added-asset.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "fields": { 8 | "title": { 9 | "en-US": "doge" 10 | }, 11 | "file": { 12 | "en-US": { 13 | "fileName": "vaa4by0.png", 14 | "contentType": "image/png", 15 | "details": { 16 | "image": { 17 | "width": 265, 18 | "height": 265 19 | }, 20 | "size": 103652 21 | }, 22 | "url": "//images.contentful.com/emh6o2ireilu/6koKmTXVzUquae6ewQQ8Eu/5f79cef98e3feb466c30f0b38bec0359/vaa4by0.png" 23 | } 24 | } 25 | }, 26 | "sys": { 27 | "space": { 28 | "sys": { 29 | "type": "Link", 30 | "linkType": "Space", 31 | "id": "emh6o2ireilu" 32 | } 33 | }, 34 | "type": "Asset", 35 | "id": "6koKmTXVzUquae6ewQQ8Eu", 36 | "revision": 1, 37 | "createdAt": "2014-03-25T11:08:25.412Z", 38 | "updatedAt": "2014-03-25T11:08:25.412Z" 39 | } 40 | } 41 | ], 42 | "nextSyncUrl": "https://cdn.contentful.com/spaces/emh6o2ireilu/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyZ_NnHDoQzCtcKoMh9KZHtAWcObw7XCimZgVGPChUfDuxQHwoHDosO6CcKodsO2MWJQwrrCrsOswpl5w6LCuV0tw4Njwo9Ww5fCl8KqEgB6XgAJNVF2wpk3Lg" 43 | } 44 | -------------------------------------------------------------------------------- /Tests/RobustnessTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // RobustnessTests.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 31/10/14. 6 | // 7 | // 8 | 9 | #import "CDAClient+Private.h" 10 | #import "CDAContentTypeRegistry.h" 11 | #import "CDAResponseSerializer.h" 12 | #import "CDAResource+Private.h" 13 | #import "ContentfulBaseTestCase.h" 14 | 15 | @interface RobustnessTests : ContentfulBaseTestCase 16 | 17 | @end 18 | 19 | #pragma mark - 20 | 21 | @implementation RobustnessTests 22 | 23 | -(void)testCrash001 { 24 | CDAResponseSerializer* serializer = [[CDAResponseSerializer alloc] initWithClient:self.client]; 25 | NSError* error; 26 | 27 | NSData* jsonData = [NSData dataWithContentsOfURL:[[NSBundle bundleForClass:self.class] URLForResource:@"ContentType-001" withExtension:@"json" subdirectory:@"Fixtures"]]; 28 | XCTAssertNotNil(jsonData); 29 | 30 | CDAContentType* contentType = [serializer responseObjectForResponse:nil data:jsonData error:&error]; 31 | XCTAssertNotNil(contentType); 32 | [self.client.contentTypeRegistry addContentType:contentType]; 33 | 34 | jsonData = [NSData dataWithContentsOfURL:[[NSBundle bundleForClass:self.class] URLForResource:@"Crash-001" withExtension:@"json" subdirectory:@"Fixtures"]]; 35 | XCTAssertNotNil(jsonData); 36 | 37 | CDAEntry* entry = [serializer responseObjectForResponse:nil data:jsonData error:&error]; 38 | XCTAssertNotNil(entry); 39 | } 40 | 41 | @end -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | git: 3 | depth: 50 4 | submodules: false 5 | osx_image: xcode9.3 6 | rvm: 7 | - 2.4.1 8 | before_install: 9 | - bundle install 10 | - bundle exec pod repo update --silent 11 | install: 12 | - bundle exec pod install 13 | script: 14 | - "./scripts/travis-build-test.sh" 15 | after_success: 16 | - bundle exec pod lib lint ContentfulDeliveryAPI.podspec --allow-warnings 17 | notifications: 18 | slack: 19 | - secure: pbLVGeA+577zmld+ie/1KUWxin8PFB+uXogz1zrVhEqnparjbHNMvFfaAGG1gbaAJC0skGksCe+LeBrhLargbLSgAT3HrObwDCIu7H8IjaYOEhpNTHCObrwRIyOeBVHHAhjgjeElM7hvptJjRMuERErcnL65EOlaIdm4ZqOg3/0= 20 | env: 21 | global: 22 | - IOS_SDK=iphonesimulator IOS_VERSION=11.3 DEVICE_NAME='iPhone 6s' 23 | - secure: n/hnbmOoeLt4g+Mzm6Nm/fnxwr7fFjfFTlNxEco7kk9oeNklj0vudOwbYtxL2AWuMNWX/tSHlUieKf4UGOiwzFeQmWyLdi0dkwSKRMNBNz2XQrlzjemZNf8V9+E6+JPeH8BVBKyscYgNevrp7R0Lx2h5mcDVogHm416yUiNzGOQ= 24 | - secure: FAIgFb/XlYTHWXAE7dzl5KFAMmST39forxsfmJdysXdaIUXK1Y44PJnS4HQ0cRa79e+o+NSOJSHLGI+qboXNoDAHqaDGBqtC7QROjW7ed0ZQp0iyNeebSq8TQzuOwdrqEjh9SGK24bgbTgQx+i8brYRJA+N8igfNtVoTUXj7buI= 25 | - secure: LyiQhHG89Zn0FdprFntjvT34DOcvFU/IITWhhowfgYrXGObUTEOyIyE3ve6fJpMm23B8/u1EUNxBVvqxevdGmA2RuoJiMMy+V/uFsBHmuOeJ8TeDYOgjyWJgsJt1bfDLpFwX2Ztu0smdMeuBpmsOA2I3FW0mDCx5tiAuScNidnE= 26 | - secure: L0njJqmusJJZPCWwMUTvb1vMJP3r4rNpLgDcWW1XtdZyNHE/F4CFzbawX/ve2ApzMXRV69XBIGLmBfl3hW0XCwyHR9nCe3u8/MRSZxapSNuArSjodHrPrddUGtAsgjKfKw1DlzNGByg2LpXLnfEBLnJYcYCosza3TI52JNcEv3c= 27 | 28 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAInputSanitizer.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDAInputSanitizer.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 09/05/14. 6 | // 7 | // 8 | 9 | #import "CDAInputSanitizer.h" 10 | 11 | @implementation CDAInputSanitizer 12 | 13 | +(NSArray*)sanitizeArray:(NSArray*)array { 14 | NSMutableArray* result = [array mutableCopy]; 15 | 16 | [result removeObject:[NSNull null]]; 17 | 18 | [array enumerateObjectsUsingBlock:^(id value, NSUInteger idx, BOOL *stop) { 19 | [result replaceObjectAtIndex:idx withObject:[self sanitizeObject:value]]; 20 | }]; 21 | 22 | return [result copy]; 23 | } 24 | 25 | +(NSDictionary*)sanitizeDictionary:(NSDictionary*)dictionary { 26 | NSMutableDictionary* result = [dictionary mutableCopy]; 27 | 28 | for (id key in dictionary.allKeys) { 29 | id value = result[key]; 30 | 31 | if (value == [NSNull null]) { 32 | [result removeObjectForKey:key]; 33 | } else { 34 | result[key] = [self sanitizeObject:value]; 35 | } 36 | } 37 | 38 | return [result copy]; 39 | } 40 | 41 | +(id)sanitizeObject:(id)object { 42 | if ([object isKindOfClass:[NSArray class]]) { 43 | return [self sanitizeArray:object]; 44 | } 45 | 46 | if ([object isKindOfClass:[NSDictionary class]]) { 47 | return [self sanitizeDictionary:object]; 48 | } 49 | 50 | return object; 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAPersistedAsset.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAPersistedAsset.h 3 | // ContentfulSDK 4 | // 5 | // Created by JP Wright on 11.05.17. 6 | // 7 | // 8 | 9 | #ifndef CDAPersistedAsset_h 10 | #define CDAPersistedAsset_h 11 | 12 | #if __has_feature(modules) 13 | @import Foundation; 14 | #else 15 | #import 16 | #endif 17 | 18 | /** 19 | * Any class representing Assets saved to a persistent store needs to conform to this protocol. 20 | * 21 | * If any of the optional properties are implemented, they will automatically be mapped to the 22 | * corresponding Asset fields from Contentful. 23 | */ 24 | @protocol CDAPersistedAsset 25 | 26 | /** The `sys.id` of the Asset. */ 27 | @property (nonatomic, nonnull) NSString* identifier; 28 | /** File type of the Asset. */ 29 | @property (nonatomic, nullable) NSString* internetMediaType; 30 | /** URL for the underlying file represented by the Asset. */ 31 | @property (nonatomic, nullable) NSString* url; 32 | 33 | @optional 34 | 35 | /** The description of the Asset. */ 36 | @property (nonatomic, nullable) NSString* assetDescription; 37 | /** The title of the Asset. */ 38 | @property (nonatomic, nullable) NSString* title; 39 | /** The width of the Asset, if it is an image. */ 40 | @property (nonatomic, nullable) NSNumber* width; 41 | /** The height of the Asset, if it is an image. */ 42 | @property (nonatomic, nullable) NSNumber* height; 43 | 44 | @end 45 | 46 | #endif /* CDAPersistedAsset_h */ 47 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDAContentType.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAContentType.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #import "CDANullabilityStubs.h" 10 | #import "CDAResource.h" 11 | 12 | @class CDAField; 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | /** 17 | Content Types are schemas describing the shape of Entries. They mainly consist of a list of fields 18 | acting as a blueprint for Entries. 19 | 20 | Note: They're not related to the HTTP Content-Type header. 21 | */ 22 | @interface CDAContentType : CDAResource 23 | 24 | /** @name Accessing Fields */ 25 | 26 | /** List of all fields as an array of `CDAField` objects. */ 27 | @property (nonatomic, readonly) NSArray* fields; 28 | 29 | /** 30 | Retrieve a specific field by its identifier. 31 | 32 | @param identifier The `sys.id` to look for in the list of fields. 33 | @return The specific field requested, or `nil` if none matches. 34 | */ 35 | -(CDAField* __nullable)fieldForIdentifier:(NSString*)identifier; 36 | 37 | /** @name Accessing Meta-Data */ 38 | 39 | /** The identifier of the Field which should be displayed as a title for Entries */ 40 | @property (nonatomic, readonly) NSString* __nullable displayField; 41 | /** Name of the Content Type. */ 42 | @property (nonatomic, readonly) NSString* name; 43 | /** Description of the Content Type. */ 44 | @property (nonatomic, readonly) NSString* __nullable userDescription; 45 | 46 | @end 47 | 48 | NS_ASSUME_NONNULL_END 49 | -------------------------------------------------------------------------------- /Tests/Data/Recordings/ComplexSyncTests.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "body" : "ewogICJzeXMiOiB7CiAgICAidHlwZSI6ICJFcnJvciIsCiAgICAiaWQiOiAiQWNj\r\nZXNzVG9rZW5JbnZhbGlkIgogIH0sCiAgIm1lc3NhZ2UiOiAiVGhlIGFjY2VzcyB0\r\nb2tlbiB5b3Ugc2VudCBjb3VsZCBub3QgYmUgZm91bmQgb3IgaXMgaW52YWxpZC4i\r\nLAogICJyZXF1ZXN0SWQiOiAiOTBhLTIxMzA4NzIxODQiCn0K", 4 | "headers" : { 5 | "Date" : "Thu, 24 Sep 2015 20:03:43 GMT", 6 | "X-Served-By" : "cache-fra1227-FRA", 7 | "Access-Control-Allow-Origin" : "*", 8 | "Via" : "1.1 varnish", 9 | "Accept-Ranges" : "bytes", 10 | "Content-Type" : "application\/vnd.contentful.delivery.v1+json", 11 | "Access-Control-Allow-Headers" : "Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization,X-Contentful-Skip-Transformation", 12 | "Connection" : "keep-alive", 13 | "Access-Control-Max-Age" : "86400", 14 | "Server" : "nginx", 15 | "X-Cache" : "MISS", 16 | "X-Cache-Hits" : "0", 17 | "Content-Length" : "180", 18 | "Access-Control-Allow-Methods" : "GET,HEAD,OPTIONS", 19 | "Cache-Control" : "max-age=0" 20 | }, 21 | "method" : "GET", 22 | "status" : 401, 23 | "uri" : "https:\/\/cdn.contentful.com\/spaces\/test\/" 24 | } 25 | ] -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDARequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDARequest.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 10/03/14. 6 | // 7 | // 8 | 9 | #if __has_feature(modules) 10 | @import Foundation; 11 | #else 12 | #import 13 | #endif 14 | 15 | #import "CDANullabilityStubs.h" 16 | 17 | /** 18 | A request encapsulates all necessary information for retrieving specific resources. 19 | */ 20 | @interface CDARequest : NSOperation 21 | 22 | /** @name Accessing URL Connection Information */ 23 | 24 | /** Error which occured during the lifetime of the request, if any. */ 25 | @property (readonly, nonatomic) NSError* __nullable error; 26 | /** The actual request being sent to the server. */ 27 | @property (readonly, nonatomic) NSURLRequest* __nullable request; 28 | /** The underlying response received from the server. */ 29 | @property (readonly, nonatomic) NSHTTPURLResponse* __nullable response; 30 | 31 | /** @name Accessing Response Data */ 32 | 33 | /** The raw data received from the server. */ 34 | @property (readonly, nonatomic) NSData* __nullable responseData; 35 | /** The processed response data, usually an instance of a `CDAResource` subclass. */ 36 | @property (readonly, nonatomic) id __nullable responseObject; 37 | /** String representation of the data received from the server. */ 38 | @property (readonly, nonatomic) NSString* __nullable responseString; 39 | /** Encoding used for the response. */ 40 | @property (readonly, nonatomic) NSStringEncoding responseStringEncoding; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDAResource+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAResource+Private.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #import "CDAResource.h" 10 | 11 | @class CDAClient; 12 | 13 | @interface CDAResource () 14 | 15 | @property (nonatomic, weak) CDAClient* client; 16 | @property (nonatomic) NSString* defaultLocaleOfSpace; // FIXME: should this be `readonly`? 17 | 18 | +(NSString*)CDAType; 19 | +(BOOL)classIsOfType:(Class)class; 20 | +(instancetype)resourceObjectForDictionary:(NSDictionary*)dictionary 21 | client:(CDAClient*)client 22 | localizationAvailable:(BOOL)localizationAvailable; 23 | 24 | -(BOOL)createdAfterDate:(NSDate*)date; 25 | -(instancetype)initWithDictionary:(NSDictionary*)dictionary 26 | client:(CDAClient*)client 27 | localizationAvailable:(BOOL)localizationAvailable; 28 | -(NSDictionary*)localizeFieldsFromDictionary:(NSDictionary*)fields; 29 | -(NSDictionary*)localizedDictionaryFromDictionary:(NSDictionary*)dictionary 30 | forLocale:(NSString*)locale 31 | default:(BOOL)isDefault; 32 | -(NSDictionary*)parseDictionary:(NSDictionary*)dictionary; 33 | -(void)resolveLinksWithIncludedAssets:(NSDictionary*)assets entries:(NSDictionary*)entries; 34 | -(BOOL)updatedAfterDate:(NSDate*)date; 35 | -(void)updateWithContentsOfDictionary:(NSDictionary*)dictionary client:(CDAClient*)client; 36 | -(void)updateWithResource:(CDAResource*)resource; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | var tokenOffset = "15px"; 27 | var original = link.css('marginLeft') == tokenOffset; 28 | link.animate({'margin-left':original ? "0px" : tokenOffset}, animationDuration); 29 | $content = link.parent().parent().next(); 30 | $content.slideToggle(animationDuration); 31 | 32 | // Keeps the document from jumping to the hash. 33 | var href = $(this).attr('href'); 34 | if (history.pushState) { 35 | history.pushState({}, '', href); 36 | } else { 37 | location.hash = href; 38 | } 39 | event.preventDefault(); 40 | }); 41 | 42 | // Dumb down quotes within code blocks that delimit strings instead of quotations 43 | // https://github.com/realm/jazzy/issues/714 44 | $("code q").replaceWith(function () { 45 | return ["\"", $(this).contents(), "\""]; 46 | }); 47 | -------------------------------------------------------------------------------- /docs/docsets/ContentfulDeliveryAPI.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | var tokenOffset = "15px"; 27 | var original = link.css('marginLeft') == tokenOffset; 28 | link.animate({'margin-left':original ? "0px" : tokenOffset}, animationDuration); 29 | $content = link.parent().parent().next(); 30 | $content.slideToggle(animationDuration); 31 | 32 | // Keeps the document from jumping to the hash. 33 | var href = $(this).attr('href'); 34 | if (history.pushState) { 35 | history.pushState({}, '', href); 36 | } else { 37 | location.hash = href; 38 | } 39 | event.preventDefault(); 40 | }); 41 | 42 | // Dumb down quotes within code blocks that delimit strings instead of quotations 43 | // https://github.com/realm/jazzy/issues/714 44 | $("code q").replaceWith(function () { 45 | return ["\"", $(this).contents(), "\""]; 46 | }); 47 | -------------------------------------------------------------------------------- /Tests/Data/ComplexSyncTests/AssetTestUpdate.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "space": { 9 | "sys": { 10 | "type": "Link", 11 | "linkType": "Space", 12 | "id": "emh6o2ireilu" 13 | } 14 | }, 15 | "type": "Entry", 16 | "contentType": { 17 | "sys": { 18 | "type": "Link", 19 | "linkType": "ContentType", 20 | "id": "6bAvxqodl6s4MoKuWYkmqe" 21 | } 22 | }, 23 | "id": "1Wl5HnguK8CiaykiQAiGu6", 24 | "revision": 2, 25 | "createdAt": "2014-04-09T11:48:47.866Z", 26 | "updatedAt": "2014-04-09T13:53:33.420Z" 27 | }, 28 | "fields": { 29 | "title": { 30 | "en-US": "WithCategory" 31 | }, 32 | "body": { 33 | "en-US": "some text" 34 | }, 35 | "category": { 36 | "en-US": { 37 | "sys": { 38 | "type": "Link", 39 | "linkType": "Entry", 40 | "id": "6nRlw4CofeeICEYgIqaIIg" 41 | } 42 | } 43 | }, 44 | "picture": { 45 | "en-US": { 46 | "sys": { 47 | "type": "Link", 48 | "linkType": "Asset", 49 | "id": "2q1Ns7Oygo2mAgoweuMCAA" 50 | } 51 | } 52 | } 53 | } 54 | } 55 | ], 56 | "nextSyncUrl": "http://cdn.contentful.com/spaces/emh6o2ireilu/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyYew4AvJMO0M2lFFcOkE2oaw50kwrUGHcKow7vDtcKbQ8KzIMKww73DvFvCq2TDmiLClS3DkwZVdncGXMKxXS3CgGQ3woUwHVnDlMKAIsO6w6DClkYfwprClsKfGw" 57 | } 58 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAFallbackDictionary.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDAFallbackDictionary.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 28/03/14. 6 | // 7 | // 8 | 9 | #import "CDAFallbackDictionary.h" 10 | 11 | @interface CDAFallbackDictionary () 12 | 13 | @property (nonatomic) NSDictionary* actualDictionary; 14 | @property (nonatomic) NSDictionary* fallbackDictionary; 15 | @property (nonatomic, readonly) NSSet* keySet; 16 | 17 | @end 18 | 19 | #pragma mark - 20 | 21 | @implementation CDAFallbackDictionary 22 | 23 | -(NSUInteger)count { 24 | return self.keySet.count; 25 | } 26 | 27 | -(instancetype)initWithDictionary:(NSDictionary *)dict fallbackDictionary:(NSDictionary *)fallbackDict { 28 | self = [self initWithObjects:nil forKeys:nil count:0]; 29 | if (self) { 30 | self.actualDictionary = dict; 31 | self.fallbackDictionary = fallbackDict; 32 | } 33 | return self; 34 | } 35 | 36 | -(instancetype)initWithObjects:(const __unsafe_unretained id [])objects 37 | forKeys:(const __unsafe_unretained id [])keys 38 | count:(NSUInteger)cnt { 39 | self = [super init]; 40 | return self; 41 | } 42 | 43 | -(NSEnumerator *)keyEnumerator { 44 | return [self.keySet objectEnumerator]; 45 | } 46 | 47 | -(NSSet*)keySet { 48 | NSMutableSet* keySet = [NSMutableSet setWithArray:self.actualDictionary.allKeys]; 49 | [keySet addObjectsFromArray:self.fallbackDictionary.allKeys]; 50 | return [keySet copy]; 51 | } 52 | 53 | -(id)objectForKey:(id)key { 54 | id value = [self.actualDictionary objectForKey:key]; 55 | return value ?: [self.fallbackDictionary objectForKey:key]; 56 | } 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /Tests/Data/Fixtures/Crash-001.json: -------------------------------------------------------------------------------- 1 | { 2 | "fields": { 3 | "beacon": null, 4 | "beaconProximity": null, 5 | "mainHeadline": "Lorem Ipsum", 6 | "mainBody": "Lorem Ipsum", 7 | "teaserHeadline": "Lorem Ipsum", 8 | "teaserTextLong": "Lorem Ipsum", 9 | "teaserText": "Lorem Ipsum", 10 | "locationIsAll": false, 11 | "entryType": "Image and Text", 12 | "showTeaser": false, 13 | "channel": { 14 | "sys": { 15 | "id": "2IhhbTcar6iqeeUwGsieIW", 16 | "type": "Link", 17 | "linkType": "Entry" 18 | } 19 | }, 20 | "multipleLocations": [ 21 | { 22 | "sys": { 23 | "id": "3af3Ya0PheQiQGaIC6C0kI", 24 | "type": "Link", 25 | "linkType": "Entry" 26 | } 27 | } 28 | ], 29 | "mainImage": { 30 | "sys": { 31 | "id": "5u2CzLsv1S20gA4044GeEg", 32 | "type": "Link", 33 | "linkType": "Asset" 34 | } 35 | }, 36 | "uuidAssgn": { 37 | "sys": { 38 | "id": "2lBvUjgDnmGKGmsM0wkem4", 39 | "type": "Link", 40 | "linkType": "Entry" 41 | } 42 | } 43 | }, 44 | "sys": { 45 | "space": { 46 | "sys": { 47 | "type": "Link", 48 | "linkType": "Space", 49 | "id": "k7cpvwpbb4c0" 50 | } 51 | }, 52 | "type": "Entry", 53 | "id": "5rpEqrj9UQ4eqAssqYmeOi", 54 | "contentType": { 55 | "sys": { 56 | "type": "Link", 57 | "linkType": "ContentType", 58 | "id": "2KZUNxrxfyYC8qO42o0oMQ" 59 | } 60 | }, 61 | "revision": 10, 62 | "createdAt": "2014-10-21T20:46:58.471Z", 63 | "updatedAt": "2014-10-28T16:49:26.057Z", 64 | "locale": "en-US" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDARequest.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDARequest.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 10/03/14. 6 | // 7 | // 8 | 9 | #import "CDARequest+Private.h" 10 | 11 | @interface CDARequest () 12 | 13 | @property (nonatomic) NSURLSessionTask* task; 14 | 15 | @end 16 | 17 | #pragma mark - 18 | 19 | @implementation CDARequest 20 | 21 | @dynamic error; 22 | @dynamic request; 23 | @dynamic response; 24 | @dynamic responseData; 25 | @dynamic responseObject; 26 | @dynamic responseString; 27 | @dynamic responseStringEncoding; 28 | 29 | #pragma mark - 30 | 31 | -(instancetype)initWithSessionTask:(NSURLSessionTask *)task { 32 | self = [super init]; 33 | if (self) { 34 | self.task = task; 35 | } 36 | return self; 37 | } 38 | 39 | #pragma mark - Compatibility with 1.x API 40 | 41 | - (NSURLRequest *)request { 42 | return self.task.originalRequest; 43 | } 44 | 45 | - (NSStringEncoding)responseStringEncoding { 46 | NSString *encoding = self.task.response.textEncodingName; 47 | 48 | if (!encoding) { 49 | return NSUTF8StringEncoding; 50 | } 51 | return CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)encoding)); 52 | } 53 | 54 | #pragma mark - Message forwarding to underlying AFHTTPRequestOperation 55 | 56 | -(void)forwardInvocation:(NSInvocation *)invocation { 57 | [invocation invokeWithTarget:self.task]; 58 | } 59 | 60 | -(NSMethodSignature*)methodSignatureForSelector:(SEL)selector { 61 | NSMethodSignature *signature = [super methodSignatureForSelector:selector]; 62 | if (!signature) { 63 | signature = [self.task methodSignatureForSelector:selector]; 64 | } 65 | return signature; 66 | } 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /Tests/PreviewModeSyncTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewModeSyncTests.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 14/04/14. 6 | // 7 | // 8 | 9 | #import "SyncBaseTestCase.h" 10 | 11 | @interface PreviewModeSyncTests : SyncBaseTestCase 12 | 13 | @end 14 | 15 | #pragma mark - 16 | 17 | @implementation PreviewModeSyncTests 18 | 19 | -(CDAClient*)buildClient { 20 | CDAConfiguration* configuration = [CDAConfiguration defaultConfiguration]; 21 | configuration.previewMode = YES; 22 | return [[CDAClient alloc] initWithSpaceKey:@"emh6o2ireilu" accessToken:@"3396581609dda9ddb19140eb8acb2216a9f33895b178e83a7dee7c75793c8243" configuration:configuration]; 23 | } 24 | 25 | -(void)testInitialSync { 26 | XCTestExpectation *expectation = [self expectationWithDescription:@""]; 27 | 28 | CDARequest* request = [self.client initialSynchronizationWithSuccess:^(CDAResponse *response, CDASyncedSpace *space) { 29 | XCTAssertEqual(2U, space.assets.count, @""); 30 | XCTAssertEqual(9U, space.entries.count, @""); 31 | 32 | [space performSynchronizationWithSuccess:^{ 33 | XCTAssertEqual(2U, space.assets.count, @""); 34 | XCTAssertEqual(9U, space.entries.count, @""); 35 | 36 | [expectation fulfill]; 37 | } failure:^(CDAResponse *response, NSError *error) { 38 | XCTFail(@"Error: %@", error); 39 | 40 | [expectation fulfill]; 41 | }]; 42 | } failure:^(CDAResponse *response, NSError *error) { 43 | XCTFail(@"Error: %@", error); 44 | 45 | [expectation fulfill]; 46 | }]; 47 | XCTAssertNotNil(request, @""); 48 | 49 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /Tests/Data/ComplexSyncTests/ComplexLinkTestUpdate2.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "space": { 9 | "sys": { 10 | "type": "Link", 11 | "linkType": "Space", 12 | "id": "emh6o2ireilu" 13 | } 14 | }, 15 | "type": "Entry", 16 | "contentType": { 17 | "sys": { 18 | "type": "Link", 19 | "linkType": "ContentType", 20 | "id": "4yCmJfmk1WeqACagaemOIs" 21 | } 22 | }, 23 | "id": "5ZmiC472OAg2SY2W0QM6Gs", 24 | "revision": 2, 25 | "createdAt": "2014-04-11T11:39:09.283Z", 26 | "updatedAt": "2014-04-11T11:41:29.967Z" 27 | }, 28 | "fields": { 29 | "link1": { 30 | "en-US": { 31 | "sys": { 32 | "type": "Link", 33 | "linkType": "Entry", 34 | "id": "4upDPGUMMEkG8w8UUs2OiO" 35 | } 36 | } 37 | }, 38 | "link2": { 39 | "en-US": { 40 | "sys": { 41 | "type": "Link", 42 | "linkType": "Entry", 43 | "id": "1gQ4P2tG7QaGkQwkC4a6Gg" 44 | } 45 | } 46 | }, 47 | "link3": { 48 | "en-US": { 49 | "sys": { 50 | "type": "Link", 51 | "linkType": "Entry", 52 | "id": "1Wl5HnguK8CiaykiQAiGu6" 53 | } 54 | } 55 | } 56 | } 57 | } 58 | ], 59 | "nextSyncUrl": "https://cdn.contentful.com/spaces/emh6o2ireilu/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyY0wpV-EsO_VcK9LDPCs8O0wpBiw7vCusOKesOZXsO9wqYTw5bCiBQZacKGwolKexPDtxApQcKvwoPClcKtSWzCgFvDrnBHAsKrwqHDth_CvgbCiTkFw4rCpygefTsj" 60 | } 61 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI.podspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | 3 | require 'dotenv/load' 4 | 5 | Pod::Spec.new do |s| 6 | s.name = "ContentfulDeliveryAPI" 7 | s.version = ENV['DELIVERY_SDK_VERSION'] 8 | s.summary = "Objective-C SDK for Contentful's Content Delivery API." 9 | s.homepage = "https://github.com/contentful/contentful.objc/" 10 | s.social_media_url = 'https://twitter.com/contentful' 11 | 12 | s.license = { 13 | :type => 'MIT', 14 | :file => 'LICENSE' 15 | } 16 | 17 | s.authors = { "Boris Bügling" => "boris@buegling.com", "JP Wright" => "jp@contentful.com" } 18 | s.source = { :git => "https://github.com/contentful/contentful.objc.git", 19 | :tag => "Delivery-#{s.version.to_s}" } 20 | s.requires_arc = true 21 | 22 | s.source_files = [ 23 | 'ContentfulDeliveryAPI/Resources/*.{h,m}', 24 | 'ContentfulDeliveryAPI/*.{h,m}', 25 | 'Versions.h' 26 | ] 27 | s.public_header_files = [ 28 | 'ContentfulDeliveryAPI/Resources/{CDAArray,CDAAsset,CDAContentType,CDAEntry,CDAError,CDASpace,CDAResource}.h', 29 | 'ContentfulDeliveryAPI/{CDAClient,CDAConfiguration,CDANullabilityStubs,CDARequest,CDAResponse,CDAField,CDASyncedSpace,ContentfulDeliveryAPI,CDAPersistenceManager,CDAPersistedAsset,CDAPersistedEntry,CDAPersistedSpace,CDALocalizablePersistedEntry,CDALocalizedPersistedEntry}.h' 30 | ] 31 | # iOS specific 32 | s.ios.deployment_target = '9.0' 33 | s.ios.frameworks = 'UIKit', 'MapKit' 34 | 35 | # macOS specific 36 | s.osx.deployment_target = '10.12' 37 | 38 | # tvOS specific 39 | s.tvos.deployment_target = '9.0' 40 | s.tvos.frameworks = 'UIKit', 'MapKit' 41 | 42 | 43 | s.dependency 'AFNetworking', '~> 3.2.1' 44 | s.dependency 'ISO8601', '~> 0.6.0' 45 | end 46 | 47 | -------------------------------------------------------------------------------- /Tests/Data/Recordings/UtilityTests.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "body" : "ewogICJzeXMiOiB7CiAgICAidHlwZSI6ICJTcGFjZSIsCiAgICAiaWQiOiAiY2Zl\r\neGFtcGxlYXBpIgogIH0sCiAgIm5hbWUiOiAiQ29udGVudGZ1bCBFeGFtcGxlIEFQ\r\nSSIsCiAgImxvY2FsZXMiOiBbCiAgICB7CiAgICAgICJjb2RlIjogImVuLVVTIiwK\r\nICAgICAgImRlZmF1bHQiOiB0cnVlLAogICAgICAibmFtZSI6ICJFbmdsaXNoIgog\r\nICAgfSwKICAgIHsKICAgICAgImNvZGUiOiAidGxoIiwKICAgICAgImRlZmF1bHQi\r\nOiBmYWxzZSwKICAgICAgIm5hbWUiOiAiS2xpbmdvbiIKICAgIH0KICBdCn0K", 4 | "headers" : { 5 | "Access-Control-Allow-Headers" : "Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization", 6 | "Cache-Control" : "max-age=0", 7 | "Vary" : "Accept-Encoding", 8 | "Access-Control-Allow-Origin" : "*", 9 | "X-Cache-Hits" : "1", 10 | "Etag" : "\"14a17843cf0cf8b116801f96c1c6fe23\"", 11 | "Content-Length" : "285", 12 | "Keep-Alive" : "timeout=10, max=50", 13 | "Access-Control-Max-Age" : "86400", 14 | "Connection" : "Keep-Alive", 15 | "Accept-Ranges" : "bytes", 16 | "X-Served-By" : "cache-fra1232-FRA", 17 | "Content-Type" : "application\/vnd.contentful.delivery.v1+json", 18 | "X-Cache" : "HIT", 19 | "Via" : "1.1 varnish", 20 | "Server" : "nginx\/1.1.19", 21 | "Access-Control-Allow-Methods" : "GET,HEAD,OPTIONS", 22 | "X-Contentful-Request-Id" : "f1c-737785047", 23 | "Age" : "574", 24 | "Date" : "Mon, 28 Apr 2014 14:01:49 GMT" 25 | }, 26 | "method" : "GET", 27 | "status" : 200, 28 | "uri" : "https:\/\/cdn.contentful.com\/spaces\/cfexampleapi\/" 29 | } 30 | ] -------------------------------------------------------------------------------- /Tests/Data/SyncTests/asset-multiple-locales.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 1, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "bht13amj0fva" 16 | } 17 | }, 18 | "id": "14bZJKTr6AoaGyeg4kYiWq", 19 | "type": "Asset", 20 | "createdAt": "2016-10-05T14:31:36.082Z", 21 | "updatedAt": "2016-10-05T14:31:36.082Z", 22 | "revision": 1 23 | }, 24 | "fields": { 25 | "title": { 26 | "en-US": "EN Title", 27 | "es": "ES Title" 28 | }, 29 | "description": { 30 | "en-US": "EN Description", 31 | "es": "ES Description" 32 | }, 33 | "file": { 34 | "en-US": { 35 | "url": "//assets.contentful.com/bht13amj0fva/14bZJKTr6AoaGyeg4kYiWq/13f00bdf75c1320061ce471a3881e831/Flag_of_the_United_States.svg", 36 | "details": { 37 | "size": 899 38 | }, 39 | "fileName": "Flag_of_the_United_States.svg", 40 | "contentType": "image/svg+xml" 41 | }, 42 | "es": { 43 | "url": "//assets.contentful.com/bht13amj0fva/14bZJKTr6AoaGyeg4kYiWq/5501c98c296af77b9acba1146ea3e211/Flag_of_Spain.svg", 44 | "details": { 45 | "size": 60255 46 | }, 47 | "fileName": "Flag_of_Spain.svg", 48 | "contentType": "image/svg+xml" 49 | } 50 | } 51 | } 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /Tests/Data/ComplexSyncTests/ComplexLinkTestContentTypes2.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 2, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "fields": [ 11 | { 12 | "name": "such field", 13 | "id": "suchField", 14 | "type": "Symbol" 15 | } 16 | ], 17 | "name": "⚡️ new content type", 18 | "sys": { 19 | "space": { 20 | "sys": { 21 | "type": "Link", 22 | "linkType": "Space", 23 | "id": "emh6o2ireilu" 24 | } 25 | }, 26 | "type": "ContentType", 27 | "id": "5kLp8FbRwAG0kcOOYa6GMa", 28 | "revision": 1, 29 | "createdAt": "2014-04-11T08:39:04.421Z", 30 | "updatedAt": "2014-04-11T08:39:04.421Z" 31 | }, 32 | "displayField": "suchField" 33 | }, 34 | { 35 | "fields": [ 36 | { 37 | "name": "link1", 38 | "id": "link1", 39 | "type": "Link", 40 | "linkType": "Entry" 41 | }, 42 | { 43 | "name": "link2", 44 | "id": "link2", 45 | "type": "Link", 46 | "linkType": "Entry" 47 | }, 48 | { 49 | "name": "link3", 50 | "id": "link3", 51 | "type": "Link", 52 | "linkType": "Entry" 53 | } 54 | ], 55 | "name": "ThreeLinks to other Entries", 56 | "sys": { 57 | "space": { 58 | "sys": { 59 | "type": "Link", 60 | "linkType": "Space", 61 | "id": "emh6o2ireilu" 62 | } 63 | }, 64 | "type": "ContentType", 65 | "id": "4yCmJfmk1WeqACagaemOIs", 66 | "revision": 1, 67 | "createdAt": "2014-04-11T11:38:32.120Z", 68 | "updatedAt": "2014-04-11T11:38:32.120Z" 69 | } 70 | } 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /Tests/StagingTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // StagingTests.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 06/03/14. 6 | // 7 | // 8 | 9 | #import "ContentfulBaseTestCase.h" 10 | 11 | @interface StagingTests : ContentfulBaseTestCase 12 | 13 | @end 14 | 15 | #pragma mark - 16 | 17 | @implementation StagingTests 18 | 19 | // FIXME: Source space has been deleted, test data needs to be recreated 20 | #if 0 21 | - (void)setUp 22 | { 23 | [super setUp]; 24 | 25 | CDAConfiguration* configuration = [CDAConfiguration defaultConfiguration]; 26 | configuration.server = @"cdn.flinkly.com"; 27 | 28 | self.client = [[CDAClient alloc] initWithSpaceKey:@"3vysoudsmwwo" accessToken:@"8efdb99a1b33b21edd6bd6f68aa702a0d688b4a4433ac3327d234a28ed825ca2" configuration:configuration]; 29 | } 30 | 31 | - (void)testContentTypes { 32 | XCTestExpectation *expectation = [self expectationWithDescription:@""]; 33 | 34 | [self.client fetchContentTypesWithSuccess:^(CDAResponse *response, CDAArray *array) { 35 | XCTAssertNotNil(array, @""); 36 | 37 | [expectation fulfill]; 38 | } failure:^(CDAResponse *response, NSError *error) { 39 | XCTFail(@"Error: %@", error); 40 | 41 | [expectation fulfill]; 42 | }]; 43 | 44 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 45 | } 46 | 47 | - (void)testEntries { 48 | XCTestExpectation *expectation = [self expectationWithDescription:@""]; 49 | 50 | [self.client fetchEntriesWithSuccess:^(CDAResponse *response, CDAArray *array) { 51 | XCTAssertNotNil(array, @""); 52 | 53 | [expectation fulfill]; 54 | } failure:^(CDAResponse *response, NSError *error) { 55 | XCTFail(@"Error: %@", error); 56 | 57 | [expectation fulfill]; 58 | }]; 59 | 60 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 61 | } 62 | #endif 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAContentTypeRegistry.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDAContentTypeRegistry.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #import "CDAContentType.h" 10 | 11 | #import "CDAContentTypeRegistry.h" 12 | 13 | @interface CDAContentTypeRegistry () 14 | 15 | @property (nonatomic) NSMutableDictionary* contentTypes; 16 | @property (nonatomic) NSMutableDictionary* customClasses; 17 | 18 | @end 19 | 20 | #pragma mark - 21 | 22 | @implementation CDAContentTypeRegistry 23 | 24 | -(void)addContentType:(CDAContentType*)contentType { 25 | @synchronized(self) { 26 | NSAssert(contentType.identifier, @"Content Type needs an identifier"); 27 | self.contentTypes[contentType.identifier] = contentType; 28 | } 29 | } 30 | 31 | -(CDAContentType*)contentTypeForIdentifier:(NSString*)identifier { 32 | return self.contentTypes[identifier]; 33 | } 34 | 35 | -(id)copyWithZone:(NSZone *)zone { 36 | CDAContentTypeRegistry* copy = [[[self class] allocWithZone:zone] init]; 37 | copy.contentTypes = self.contentTypes; 38 | copy.customClasses = self.customClasses; 39 | return copy; 40 | } 41 | 42 | -(Class)customClassForContentType:(CDAContentType *)contentType { 43 | return self.customClasses[contentType.identifier]; 44 | } 45 | 46 | -(BOOL)hasCustomClasses { 47 | return self.customClasses.count > 0; 48 | } 49 | 50 | -(instancetype)init { 51 | self = [super init]; 52 | if (self) { 53 | self.contentTypes = [@{} mutableCopy]; 54 | self.customClasses = [@{} mutableCopy]; 55 | } 56 | return self; 57 | } 58 | 59 | -(void)registerClass:(Class)customClass forContentType:(CDAContentType*)contentType { 60 | self.customClasses[contentType.identifier] = customClass; 61 | } 62 | 63 | -(void)registerClass:(Class)customClass forContentTypeWithIdentifier:(NSString*)identifier { 64 | self.customClasses[identifier] = customClass; 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /Tests/LocaleTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // LocaleTests.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 19/08/15. 6 | // 7 | // 8 | 9 | #import "ContentfulBaseTestCase.h" 10 | 11 | @interface LocaleTests : ContentfulBaseTestCase 12 | 13 | @end 14 | 15 | #pragma mark - 16 | 17 | @implementation LocaleTests 18 | 19 | -(void)testFallbackLocalesForAssets { 20 | XCTestExpectation *expectation = [self expectationWithDescription:@""]; 21 | 22 | self.client = [[CDAClient alloc] initWithSpaceKey:@"dmm6iymtengv" 23 | accessToken:@"b18e713cf2c3c8916cad0cca8e801a3c230e9e6781098dc50fb0810ebc36a4a1"]; 24 | 25 | [self.client fetchAssetsMatching:@{ @"locale": @"*" } success:^(CDAResponse* r, CDAArray* array) { 26 | CDAAsset* asset = array.items.firstObject; 27 | XCTAssertNotNil(asset.URL); 28 | 29 | asset.locale = @"es"; 30 | XCTAssertNotNil(asset.URL); 31 | 32 | [expectation fulfill]; 33 | } failure:^(CDAResponse* response, NSError* error) { 34 | XCTFail(@"Error: %@", error); 35 | 36 | [expectation fulfill]; 37 | }]; 38 | 39 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 40 | } 41 | 42 | -(void)testWildcardLocales { 43 | XCTestExpectation *expectation = [self expectationWithDescription:@""]; 44 | 45 | [self.client fetchEntriesMatching:@{ @"locale": @"*", @"sys.id": @"nyancat" } 46 | success:^(CDAResponse* response, CDAArray* array) { 47 | NSLog(@"yolo: %@", array); 48 | 49 | [expectation fulfill]; 50 | } failure:^(CDAResponse* response, NSError* error) { 51 | XCTFail(@"Error: %@", error); 52 | 53 | [expectation fulfill]; 54 | }]; 55 | 56 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /Tests/LinkedData.xcdatamodeld/LinkedData.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Tests/AddContentTypesSyncTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // AddContentTypesSyncTests.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 11/04/14. 6 | // 7 | // 8 | 9 | #import "SyncBaseTestCase.h" 10 | @import XCTest; 11 | 12 | @interface AddContentTypesSyncTests : SyncBaseTestCase 13 | 14 | @end 15 | 16 | #pragma mark - 17 | 18 | @implementation AddContentTypesSyncTests 19 | 20 | -(void)setUp { 21 | [super setUp]; 22 | 23 | /* 24 | Map URLs to JSON response files 25 | */ 26 | NSDictionary* stubs = @{ @"https://cdn.contentful.com/spaces/emh6o2ireilu/sync?initial=true": @"AddContentTypesInitial", @"https://cdn.contentful.com/spaces/emh6o2ireilu/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdybCr1IuSMKBHcKNbynCrcOdF8KoNCA_cwJHKx5VfW1EMsOBdkFww5_CjcOMQcOQw4_Dg8KjNcK8w7RrYCY8w57DmjM5wprDgcOxw7JOw7jDuQjDgsKVTsKBw7HDvcOX": @"AddContentTypesUpdate", @"https://cdn.contentful.com/spaces/emh6o2ireilu/": @"space", @"https://cdn.contentful.com/spaces/emh6o2ireilu/content_types?limit=1&sys.id%5Bin%5D=5kLp8FbRwAG0kcOOYa6GMa": @"AddContentTypesContentTypes", }; 27 | 28 | [self stubHTTPRequestUsingFixtures:stubs inDirectory:@"ComplexSyncTests"]; 29 | } 30 | 31 | -(void)testAddContentTypesDuringSyncSession { 32 | XCTestExpectation *expectation = [self expectationWithDescription:@""]; 33 | 34 | CDARequest* request = [self.client initialSynchronizationWithSuccess:^(CDAResponse *response, CDASyncedSpace *space) { 35 | space.delegate = self; 36 | 37 | [space performSynchronizationWithSuccess:^{ 38 | [expectation fulfill]; 39 | } failure:^(CDAResponse *response, NSError *error) { 40 | XCTFail(@"Error: %@", error); 41 | 42 | [expectation fulfill]; 43 | }]; 44 | } failure:^(CDAResponse *response, NSError *error) { 45 | XCTFail(@"Error: %@", error); 46 | 47 | [expectation fulfill]; 48 | }]; 49 | XCTAssertNotNil(request, @""); 50 | 51 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 52 | 53 | XCTAssertEqual(1U, self.numberOfEntriesCreated, @""); 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDASpace.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDASpace.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 06/03/14. 6 | // 7 | // 8 | 9 | #import "CDAResource+Private.h" 10 | #import "CDASpace+Private.h" 11 | 12 | @interface CDASpace () 13 | 14 | @property (nonatomic) NSString* name; 15 | 16 | @end 17 | 18 | #pragma mark - 19 | 20 | @implementation CDASpace 21 | 22 | +(NSString *)CDAType { 23 | return @"Space"; 24 | } 25 | 26 | #pragma mark - 27 | 28 | -(instancetype)initWithDictionary:(NSDictionary *)dictionary 29 | client:(CDAClient*)client 30 | localizationAvailable:(BOOL)localizationAvailable { 31 | self = [super initWithDictionary:dictionary client:client localizationAvailable:localizationAvailable]; 32 | if (self) { 33 | self.defaultLocale = @"en-US"; 34 | self.locales = dictionary[@"locales"]; 35 | self.name = dictionary[@"name"]; 36 | 37 | for (NSDictionary* locale in self.locales) { 38 | if ([locale[@"default"] boolValue] && locale[@"code"]) { 39 | NSString* code = locale[@"code"]; 40 | self.defaultLocale = code; 41 | } 42 | } 43 | } 44 | return self; 45 | } 46 | 47 | -(NSArray *)localeCodes { 48 | return [self.locales valueForKey:@"code"] ?: @[ self.defaultLocale ]; 49 | } 50 | 51 | // We only encode properties that have write permissions 52 | #pragma mark - NSCoding 53 | 54 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 55 | self = [super initWithCoder:aDecoder]; 56 | if (self) { 57 | self.name = [aDecoder decodeObjectForKey:@"name"]; 58 | self.defaultLocale = [aDecoder decodeObjectForKey:@"defaultLocale"]; 59 | self.locales = [aDecoder decodeObjectForKey:@"locales"]; 60 | } 61 | return self; 62 | } 63 | 64 | - (void)encodeWithCoder:(NSCoder *)aCoder { 65 | [super encodeWithCoder:aCoder]; 66 | 67 | [aCoder encodeObject:self.name forKey:@"name"]; 68 | [aCoder encodeObject:self.defaultLocale forKey:@"defaultLocale"]; 69 | [aCoder encodeObject:self.locales forKey:@"locales"]; 70 | } 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /ContentfulSDK.xcodeproj/xcshareddata/xcschemes/ManagementTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /Tests/Data/ComplexSyncTests/LinkTestUpdate.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "space": { 9 | "sys": { 10 | "type": "Link", 11 | "linkType": "Space", 12 | "id": "emh6o2ireilu" 13 | } 14 | }, 15 | "type": "Entry", 16 | "contentType": { 17 | "sys": { 18 | "type": "Link", 19 | "linkType": "ContentType", 20 | "id": "6bAvxqodl6s4MoKuWYkmqe" 21 | } 22 | }, 23 | "id": "1Wl5HnguK8CiaykiQAiGu6", 24 | "revision": 1, 25 | "createdAt": "2014-04-09T11:48:47.866Z", 26 | "updatedAt": "2014-04-09T11:48:47.866Z" 27 | }, 28 | "fields": { 29 | "title": { 30 | "en-US": "WithCategory" 31 | }, 32 | "body": { 33 | "en-US": "some text" 34 | }, 35 | "category": { 36 | "en-US": { 37 | "sys": { 38 | "type": "Link", 39 | "linkType": "Entry", 40 | "id": "6nRlw4CofeeICEYgIqaIIg" 41 | } 42 | } 43 | } 44 | } 45 | }, 46 | { 47 | "sys": { 48 | "space": { 49 | "sys": { 50 | "type": "Link", 51 | "linkType": "Space", 52 | "id": "emh6o2ireilu" 53 | } 54 | }, 55 | "type": "Entry", 56 | "contentType": { 57 | "sys": { 58 | "type": "Link", 59 | "linkType": "ContentType", 60 | "id": "51LZmvenywOe8aig28sCgY" 61 | } 62 | }, 63 | "id": "6nRlw4CofeeICEYgIqaIIg", 64 | "revision": 1, 65 | "createdAt": "2014-04-09T11:48:42.407Z", 66 | "updatedAt": "2014-04-09T11:48:42.407Z" 67 | }, 68 | "fields": { 69 | "name": { 70 | "en-US": "foobar" 71 | } 72 | } 73 | } 74 | ], 75 | "nextSyncUrl": "http://cdn.contentful.com/spaces/emh6o2ireilu/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyZ7T8Obw5Inwoh6Tk7Cq8KUDcK7w5ssw6vCgjLDjk9Hwr3DusOzw7XCo8OIwo3CicK5SBkqCcK7woDDhSjCkMOGw7rCqMOtE1V1L3LDq8KIck_DssK4K8OBe0vDn0vDrXjDkMOf" 76 | } 77 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AFNetworking (3.2.1): 3 | - AFNetworking/NSURLSession (= 3.2.1) 4 | - AFNetworking/Reachability (= 3.2.1) 5 | - AFNetworking/Security (= 3.2.1) 6 | - AFNetworking/Serialization (= 3.2.1) 7 | - AFNetworking/UIKit (= 3.2.1) 8 | - AFNetworking/NSURLSession (3.2.1): 9 | - AFNetworking/Reachability 10 | - AFNetworking/Security 11 | - AFNetworking/Serialization 12 | - AFNetworking/Reachability (3.2.1) 13 | - AFNetworking/Security (3.2.1) 14 | - AFNetworking/Serialization (3.2.1) 15 | - AFNetworking/UIKit (3.2.1): 16 | - AFNetworking/NSURLSession 17 | - CCLRequestReplay (0.9.0): 18 | - CCLRequestReplay/Blueprint (= 0.9.0) 19 | - CCLRequestReplay/JSON (= 0.9.0) 20 | - CCLRequestReplay/Manager (= 0.9.0) 21 | - CCLRequestReplay/Record (= 0.9.0) 22 | - CCLRequestReplay/Replay (= 0.9.0) 23 | - CCLRequestReplay/Blueprint (0.9.0) 24 | - CCLRequestReplay/JSON (0.9.0) 25 | - CCLRequestReplay/Manager (0.9.0) 26 | - CCLRequestReplay/Record (0.9.0) 27 | - CCLRequestReplay/Replay (0.9.0) 28 | - FBSnapshotTestCase/Core (2.1.4) 29 | - ISO8601 (0.6.0) 30 | - OCMock (3.4.2) 31 | - VCRURLConnection (0.2.4) 32 | 33 | DEPENDENCIES: 34 | - AFNetworking (~> 3.2.1) 35 | - CCLRequestReplay (from `https://github.com/neonichu/CCLRequestReplay.git`) 36 | - FBSnapshotTestCase/Core 37 | - ISO8601 (~> 0.6.0) 38 | - OCMock 39 | - VCRURLConnection (= 0.2.4) 40 | 41 | SPEC REPOS: 42 | https://github.com/cocoapods/specs.git: 43 | - AFNetworking 44 | - FBSnapshotTestCase 45 | - ISO8601 46 | - OCMock 47 | - VCRURLConnection 48 | 49 | EXTERNAL SOURCES: 50 | CCLRequestReplay: 51 | :git: https://github.com/neonichu/CCLRequestReplay.git 52 | 53 | CHECKOUT OPTIONS: 54 | CCLRequestReplay: 55 | :commit: e2d771c32c5327ef18b367acaed299e3c841b9ed 56 | :git: https://github.com/neonichu/CCLRequestReplay.git 57 | 58 | SPEC CHECKSUMS: 59 | AFNetworking: b6f891fdfaed196b46c7a83cf209e09697b94057 60 | CCLRequestReplay: a472e52da260c28d7a41df76697b8e518625a44f 61 | FBSnapshotTestCase: 094f9f314decbabe373b87cc339bea235a63e07a 62 | ISO8601: d3ea3ba9b752820cf92c6b47a9ee327e9f0e13fc 63 | OCMock: ebe9ee1dca7fbed0ff9193ac0b3e2d8862ea56f6 64 | VCRURLConnection: c522ba51c372f6ec79df4f0cd2cb0ac6cdb8ad65 65 | 66 | PODFILE CHECKSUM: 71937c87938d073786a8178ad3cc2c56124d65da 67 | 68 | COCOAPODS: 1.5.3 69 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDAError.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDAError.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 05/03/14. 6 | // 7 | // 8 | 9 | #import "CDAError+Private.h" 10 | #import "CDAResource+Private.h" 11 | 12 | NSString* const CDAErrorDomain = @"CDAErrorDomain"; 13 | 14 | @interface CDAError () 15 | 16 | @property (nonatomic) NSDictionary* details; 17 | @property (nonatomic) NSString* message; 18 | 19 | @end 20 | 21 | #pragma mark - 22 | 23 | @implementation CDAError 24 | 25 | +(NSError*)buildErrorWithCode:(NSInteger)code userInfo:(NSDictionary*)userInfo { 26 | return [NSError errorWithDomain:CDAErrorDomain 27 | code:code 28 | userInfo:userInfo]; 29 | } 30 | 31 | +(NSString *)CDAType { 32 | return @"Error"; 33 | } 34 | 35 | #pragma mark - 36 | 37 | -(NSString *)description { 38 | return [[self errorRepresentationWithCode:0] description]; 39 | } 40 | 41 | -(NSError *)errorRepresentationWithCode:(NSInteger)code { 42 | return [[self class] buildErrorWithCode:code 43 | userInfo:@{ @"details": self.details ?: @{}, 44 | @"identifier": self.identifier, 45 | NSLocalizedDescriptionKey: self.message ?: @"" }]; 46 | } 47 | 48 | -(instancetype)initWithDictionary:(NSDictionary *)dictionary 49 | client:(CDAClient*)client 50 | localizationAvailable:(BOOL)localizationAvailable { 51 | self = [super initWithDictionary:dictionary client:client localizationAvailable:localizationAvailable]; 52 | if (self) { 53 | self.details = dictionary[@"details"]; 54 | self.message = dictionary[@"message"]; 55 | } 56 | return self; 57 | } 58 | 59 | // We only encode properties that have write permissions 60 | #pragma mark - NSCoding 61 | 62 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 63 | self = [super initWithCoder:aDecoder]; 64 | if (self) { 65 | self.details = [aDecoder decodeObjectForKey:@"details"]; 66 | self.message = [aDecoder decodeObjectForKey:@"message"]; 67 | } 68 | return self; 69 | } 70 | 71 | - (void)encodeWithCoder:(NSCoder *)aCoder { 72 | [super encodeWithCoder:aCoder]; 73 | 74 | [aCoder encodeObject:self.details forKey:@"details"]; 75 | [aCoder encodeObject:self.message forKey:@"message"]; 76 | } 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /Tests/Data/SyncTests/initialWithoutToken.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "fields": { 8 | "title": { 9 | "en-US": "contentful-logo" 10 | }, 11 | "file": { 12 | "en-US": { 13 | "fileName": "512_black.png", 14 | "contentType": "image/png", 15 | "details": { 16 | "image": { 17 | "width": 512, 18 | "height": 512 19 | }, 20 | "size": 4189 21 | }, 22 | "url": "//images.contentful.com/emh6o2ireilu/2q1Ns7Oygo2mAgoweuMCAA/7a4eb574952f359b851b3f353573b708/512_black.png" 23 | } 24 | } 25 | }, 26 | "sys": { 27 | "space": { 28 | "sys": { 29 | "type": "Link", 30 | "linkType": "Space", 31 | "id": "emh6o2ireilu" 32 | } 33 | }, 34 | "type": "Asset", 35 | "id": "2q1Ns7Oygo2mAgoweuMCAA", 36 | "revision": 1, 37 | "createdAt": "2014-03-25T11:01:41.057Z", 38 | "updatedAt": "2014-03-25T11:01:41.057Z" 39 | } 40 | }, 41 | { 42 | "sys": { 43 | "space": { 44 | "sys": { 45 | "type": "Link", 46 | "linkType": "Space", 47 | "id": "emh6o2ireilu" 48 | } 49 | }, 50 | "type": "Entry", 51 | "contentType": { 52 | "sys": { 53 | "type": "Link", 54 | "linkType": "ContentType", 55 | "id": "6bAvxqodl6s4MoKuWYkmqe" 56 | } 57 | }, 58 | "id": "4upDPGUMMEkG8w8UUs2OiO", 59 | "revision": 1, 60 | "createdAt": "2014-03-25T11:00:43.065Z", 61 | "updatedAt": "2014-03-25T11:00:43.065Z" 62 | }, 63 | "fields": { 64 | "title": { 65 | "en-US": "Test" 66 | }, 67 | "body": { 68 | "en-US": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 69 | } 70 | } 71 | } 72 | ] 73 | } 74 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDARequestOperationManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDARequestOperationManager.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 06/03/14. 6 | // 7 | // 8 | 9 | #import 10 | #import "CDAClient.h" 11 | 12 | @class CDARequest; 13 | 14 | @interface CDARequestOperationManager : AFHTTPSessionManager 15 | 16 | -(instancetype)initWithSpaceKey:(NSString *)spaceKey 17 | accessToken:(NSString *)accessToken 18 | client:(CDAClient*)client 19 | configuration:(CDAConfiguration*)configuration 20 | isCMARequest:(BOOL)isCMARequest; 21 | 22 | -(CDARequest*)deleteURLPath:(NSString*)URLPath 23 | headers:(NSDictionary*)headers 24 | parameters:(NSDictionary*)parameters 25 | success:(CDAObjectFetchedBlock)success 26 | failure:(CDARequestFailureBlock)failure; 27 | -(CDARequest*)fetchArrayAtURLPath:(NSString*)URLPath 28 | parameters:(NSDictionary*)parameters 29 | success:(CDAArrayFetchedBlock)success 30 | failure:(CDARequestFailureBlock)failure; 31 | -(CDAArray*)fetchArraySynchronouslyAtURLPath:(NSString*)URLPath 32 | parameters:(NSDictionary*)parameters 33 | error:(NSError **)error; 34 | -(CDARequest*)fetchSpaceWithSuccess:(CDASpaceFetchedBlock)success 35 | failure:(CDARequestFailureBlock)failure; 36 | -(CDARequest*)fetchURLPath:(NSString*)URLPath 37 | parameters:(NSDictionary*)parameters 38 | success:(CDAObjectFetchedBlock)success 39 | failure:(CDARequestFailureBlock)failure; 40 | 41 | -(CDARequest*)postURLPath:(NSString*)URLPath 42 | headers:(NSDictionary*)headers 43 | parameters:(NSDictionary*)parameters 44 | success:(CDAObjectFetchedBlock)success 45 | failure:(CDARequestFailureBlock)failure; 46 | -(CDARequest*)putURLPath:(NSString*)URLPath 47 | headers:(NSDictionary*)headers 48 | parameters:(NSDictionary*)parameters 49 | success:(CDAObjectFetchedBlock)success 50 | failure:(CDARequestFailureBlock)failure; 51 | 52 | + (NSData *)sendSynchronousRequest:(NSURLRequest *)request 53 | returningResponse:(__autoreleasing NSURLResponse **)responsePtr 54 | error:(__autoreleasing NSError **)errorPtr; 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /Tests/Data/ComplexSyncTests/NewEntryAndDeletionUpdate.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "space": { 9 | "sys": { 10 | "type": "Link", 11 | "linkType": "Space", 12 | "id": "emh6o2ireilu" 13 | } 14 | }, 15 | "type": "Entry", 16 | "contentType": { 17 | "sys": { 18 | "type": "Link", 19 | "linkType": "ContentType", 20 | "id": "6bAvxqodl6s4MoKuWYkmqe" 21 | } 22 | }, 23 | "id": "42yYtmO46IY0GO2UCgOK2o", 24 | "revision": 1, 25 | "createdAt": "2014-04-10T09:37:06.719Z", 26 | "updatedAt": "2014-04-10T09:37:06.719Z" 27 | }, 28 | "fields": { 29 | "title": { 30 | "en-US": "NewOneWithLinkToFoobar" 31 | }, 32 | "body": { 33 | "en-US": "foo" 34 | }, 35 | "category": { 36 | "en-US": { 37 | "sys": { 38 | "type": "Link", 39 | "linkType": "Entry", 40 | "id": "6nRlw4CofeeICEYgIqaIIg" 41 | } 42 | } 43 | } 44 | } 45 | }, 46 | { 47 | "sys": { 48 | "space": { 49 | "sys": { 50 | "type": "Link", 51 | "linkType": "Space", 52 | "id": "emh6o2ireilu" 53 | } 54 | }, 55 | "type": "Entry", 56 | "contentType": { 57 | "sys": { 58 | "type": "Link", 59 | "linkType": "ContentType", 60 | "id": "6bAvxqodl6s4MoKuWYkmqe" 61 | } 62 | }, 63 | "id": "1Wl5HnguK8CiaykiQAiGu6", 64 | "revision": 3, 65 | "createdAt": "2014-04-09T11:48:47.866Z", 66 | "updatedAt": "2014-04-10T09:36:40.283Z" 67 | }, 68 | "fields": { 69 | "title": { 70 | "en-US": "WithCategory" 71 | }, 72 | "body": { 73 | "en-US": "some text" 74 | }, 75 | "category": { 76 | "en-US": null 77 | }, 78 | "picture": { 79 | "en-US": { 80 | "sys": { 81 | "type": "Link", 82 | "linkType": "Asset", 83 | "id": "2q1Ns7Oygo2mAgoweuMCAA" 84 | } 85 | } 86 | } 87 | } 88 | } 89 | ], 90 | "nextSyncUrl": "https://cdn.contentful.com/spaces/emh6o2ireilu/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdybCr1IuSMKBHcKNbynCrcOdF8KoNCA_cwJHKx5VfW1EMsOBdkFww5_CjcOMQcOQw4_Dg8KjNcK8w7RrYCY8w57DmjM5wprDgcOxw7JOw7jDuQjDgsKVTsKBw7HDvcOX" 91 | } 92 | -------------------------------------------------------------------------------- /Tests/Data/SyncTests/initial.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "fields": { 8 | "title": { 9 | "en-US": "contentful-logo" 10 | }, 11 | "file": { 12 | "en-US": { 13 | "fileName": "512_black.png", 14 | "contentType": "image/png", 15 | "details": { 16 | "image": { 17 | "width": 512, 18 | "height": 512 19 | }, 20 | "size": 4189 21 | }, 22 | "url": "//images.contentful.com/emh6o2ireilu/2q1Ns7Oygo2mAgoweuMCAA/7a4eb574952f359b851b3f353573b708/512_black.png" 23 | } 24 | } 25 | }, 26 | "sys": { 27 | "space": { 28 | "sys": { 29 | "type": "Link", 30 | "linkType": "Space", 31 | "id": "emh6o2ireilu" 32 | } 33 | }, 34 | "type": "Asset", 35 | "id": "2q1Ns7Oygo2mAgoweuMCAA", 36 | "revision": 1, 37 | "createdAt": "2014-03-25T11:01:41.057Z", 38 | "updatedAt": "2014-03-25T11:01:41.057Z" 39 | } 40 | }, 41 | { 42 | "sys": { 43 | "space": { 44 | "sys": { 45 | "type": "Link", 46 | "linkType": "Space", 47 | "id": "emh6o2ireilu" 48 | } 49 | }, 50 | "type": "Entry", 51 | "contentType": { 52 | "sys": { 53 | "type": "Link", 54 | "linkType": "ContentType", 55 | "id": "6bAvxqodl6s4MoKuWYkmqe" 56 | } 57 | }, 58 | "id": "4upDPGUMMEkG8w8UUs2OiO", 59 | "revision": 1, 60 | "createdAt": "2014-03-25T11:00:43.065Z", 61 | "updatedAt": "2014-03-25T11:00:43.065Z" 62 | }, 63 | "fields": { 64 | "title": { 65 | "en-US": "Test" 66 | }, 67 | "body": { 68 | "en-US": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 69 | } 70 | } 71 | } 72 | ], 73 | "nextSyncUrl": "https://cdn.contentful.com/spaces/emh6o2ireilu/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyYxwoHDtsKywrXDmQs_WcOvIcOzwotYw6PCgcOsAcOYYcO4YsKCw7TCnsK_clnClS7Csx9lwoFcw6nCqnnCpWh3w7k7SkI-CcOuQyXDlw_Dlh9RwqkcElwpW30sw4k" 74 | } 75 | -------------------------------------------------------------------------------- /Tests/Data/SyncTests/update.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "fields": { 8 | "title": { 9 | "en-US": "doge (changed)" 10 | }, 11 | "file": { 12 | "en-US": { 13 | "fileName": "vaa4by0.png", 14 | "contentType": "image/png", 15 | "details": { 16 | "image": { 17 | "width": 265, 18 | "height": 265 19 | }, 20 | "size": 103652 21 | }, 22 | "url": "//images.contentful.com/emh6o2ireilu/6koKmTXVzUquae6ewQQ8Eu/5f79cef98e3feb466c30f0b38bec0359/vaa4by0.png" 23 | } 24 | } 25 | }, 26 | "sys": { 27 | "space": { 28 | "sys": { 29 | "type": "Link", 30 | "linkType": "Space", 31 | "id": "emh6o2ireilu" 32 | } 33 | }, 34 | "type": "Asset", 35 | "id": "6koKmTXVzUquae6ewQQ8Eu", 36 | "revision": 2, 37 | "createdAt": "2014-03-25T11:08:25.412Z", 38 | "updatedAt": "2014-03-26T10:37:20.069Z" 39 | } 40 | }, 41 | { 42 | "sys": { 43 | "space": { 44 | "sys": { 45 | "type": "Link", 46 | "linkType": "Space", 47 | "id": "emh6o2ireilu" 48 | } 49 | }, 50 | "type": "Entry", 51 | "contentType": { 52 | "sys": { 53 | "type": "Link", 54 | "linkType": "ContentType", 55 | "id": "6bAvxqodl6s4MoKuWYkmqe" 56 | } 57 | }, 58 | "id": "4upDPGUMMEkG8w8UUs2OiO", 59 | "revision": 2, 60 | "createdAt": "2014-03-25T11:00:43.065Z", 61 | "updatedAt": "2014-03-26T10:37:09.128Z" 62 | }, 63 | "fields": { 64 | "title": { 65 | "en-US": "Test (changed)" 66 | }, 67 | "body": { 68 | "en-US": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 69 | } 70 | } 71 | } 72 | ], 73 | "nextSyncUrl": "https://cdn.contentful.com/spaces/emh6o2ireilu/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdybCvMKEwp1eHxbDvzpAw5BOfcOEwqNMw4LChcKHwpBYw5FsHGMddMKsCBrChcKiOcKoG3ICUz8-C8K9TcOvQcO7wodaw5kVwpLCgx1kw7M7w4xVwoRswrpIwpsmw58" 74 | } 75 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAConfiguration.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAConfiguration.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #if __has_feature(modules) 10 | @import Foundation; 11 | #else 12 | #import 13 | #endif 14 | 15 | #import "CDANullabilityStubs.h" 16 | 17 | NS_ASSUME_NONNULL_BEGIN 18 | 19 | extern const NSString* const CDA_DEFAULT_SERVER; 20 | 21 | /** 22 | Class representing additional configuration options for a `CDAClient`. 23 | */ 24 | @interface CDAConfiguration : NSObject 25 | 26 | /** @name Creating a configuration */ 27 | 28 | /** 29 | * Creating a configuration with default parameters. 30 | * 31 | * @return A configuration initialized with default parameters. 32 | */ 33 | +(instancetype)defaultConfiguration; 34 | 35 | /** @name Configuring parameters */ 36 | 37 | /** Automatically filter any non-existing resources being linked to. */ 38 | @property (nonatomic) BOOL filterNonExistingResources; 39 | 40 | /** Automatically retry requests if rate-limits are exceeded. */ 41 | @property (nonatomic) BOOL rateLimiting; 42 | 43 | /** If `YES`, a secure HTTPS connection will be used instead of regular HTTP. Default value: `YES` */ 44 | @property (nonatomic) BOOL secure; 45 | 46 | /** The server address to use for accessing any resources. Default value: "cdn.contentful.com" */ 47 | @property (nonatomic) NSString* server; 48 | 49 | /** @name Configure Preview Mode */ 50 | 51 | /** Preview mode allows retrieving unpublished Resources. 52 | 53 | To use it, you have to obtain a special access 54 | token which you can get in the "API" tab of the Contentful app. 55 | 56 | In preview mode, data can be invalid, because no validation is performed on unpublished entries. Your 57 | app needs to deal with that. Be aware that the access token is read-write and should in no case be 58 | shipped with a production app. 59 | 60 | Another difference in preview mode is that there are never any updates when performing 61 | synchronizations. You will receive all Assets and Entries during the initial synchronization, just as 62 | in normal mode, including the unpublished Resources. Any subsequent synchronization will return 63 | successful immediately, not reporting any changes. In addition to that, the parameters for syncing 64 | specific content will have no effect in preview mode, instead all content will always be fetched. 65 | 66 | In preview mode, the `include` parameter of a query is not used at all, instead includes will always 67 | be resolved for full ten levels. Because of this, preview mode will be slower than normal. 68 | */ 69 | @property (nonatomic) BOOL previewMode; 70 | 71 | @end 72 | 73 | NS_ASSUME_NONNULL_END 74 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAField.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAField.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #if __has_feature(modules) 10 | @import Foundation; 11 | #else 12 | #import 13 | #endif 14 | 15 | #import "CDANullabilityStubs.h" 16 | 17 | /** 18 | Possible field types. 19 | 20 | [For further reference](https://www.contentful.com/developers/documentation/content-delivery-api/#content-type-fields) 21 | */ 22 | typedef NS_ENUM(NSInteger, CDAFieldType) { 23 | /** List of values. Value type depends on `itemType` property. */ 24 | CDAFieldTypeArray, 25 | /** Flag, represented as a `BOOL`. */ 26 | CDAFieldTypeBoolean, 27 | /** Date, represented as an `NSDate` instance. */ 28 | CDAFieldTypeDate, 29 | /** Number type without decimals. Values from -2^53 to 2^53. */ 30 | CDAFieldTypeInteger, 31 | /** Links represent relationships between Resources. */ 32 | CDAFieldTypeLink, 33 | /** A location coordinate, values should be retrieved using the `-CLLocationCoordinate2DFromFieldWithIdentifier:` of `CDAEntry`. */ 34 | CDAFieldTypeLocation, 35 | /** Unspecified, used to denominate the `itemType` of any field which is not an array. */ 36 | CDAFieldTypeNone, 37 | /** Number type with decimals. */ 38 | CDAFieldTypeNumber, 39 | /** JSON object. */ 40 | CDAFieldTypeObject, 41 | /** Basic list of characters. */ 42 | CDAFieldTypeSymbol, 43 | /** Same as String, but can be filtered via [Full-Text Search](https://www.contentful.com/developers/documentation/content-delivery-api/#search-filter-full-text). */ 44 | CDAFieldTypeText, 45 | /** Used as `itemType` for arrays of entries or as `type` for links. */ 46 | CDAFieldTypeEntry, 47 | /** Used as `itemType` for arrays of assets or as `type` for links. */ 48 | CDAFieldTypeAsset, 49 | }; 50 | 51 | NS_ASSUME_NONNULL_BEGIN 52 | 53 | /** A `CDAField` describes a single property of a `CDAEntry`. */ 54 | @interface CDAField : NSObject 55 | 56 | /** @name Accessing Field Information */ 57 | 58 | /** Unique ID of the field. */ 59 | @property (nonatomic, readonly) NSString* identifier; 60 | /** Name of the field. */ 61 | @property (nonatomic, readonly) NSString* name; 62 | /** Type of the field. */ 63 | @property (nonatomic, readonly) CDAFieldType type; 64 | /** Whether the field was disabled. */ 65 | @property (nonatomic, readonly) BOOL disabled; 66 | 67 | /** @name Accessing the Type of Array Items */ 68 | 69 | /** Field type of items if the field is an Array, `CDAFieldTypeNone` otherwise. */ 70 | @property (nonatomic, readonly) CDAFieldType itemType; 71 | 72 | @end 73 | 74 | NS_ASSUME_NONNULL_END 75 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | __SIM_ID=`xcrun simctl list|egrep -m 1 '$(SIM_NAME) \([^(]*\) \([^(]*\)$$'|sed -e 's/.* (\(.*\)) (.*)/\1/'` 2 | SIM_NAME=iPhone 6s 3 | SIM_ID=$(shell echo $(__SIM_ID)) 4 | 5 | ifeq ($(strip $(SIM_ID)),) 6 | $(error Could not find $(SIM_NAME) simulator) 7 | endif 8 | 9 | WORKSPACE=ContentfulSDK.xcworkspace 10 | 11 | .PHONY: all open clean clean_simulators clean_pods doc example example-static pod static-lib test kill_simulator docs 12 | 13 | open: 14 | open ContentfulSDK.xcworkspace 15 | 16 | clean_pods: 17 | rm -rf Pods/ 18 | 19 | clean: clean_simulators 20 | rm -rf $(HOME)/Library/Developer/Xcode/DerivedData/* 21 | 22 | clean_simulators: kill_simulator 23 | xcrun simctl erase all 24 | 25 | all: test example_static 26 | 27 | pod: 28 | bundle exec pod install 29 | xcversion select 7.3.1 30 | xcrun bitcode_strip -r Pods/Realm/core/librealm-ios.a -o Pods/Realm/core/librealm-ios.a 31 | 32 | example: 33 | set -o pipefail && xcodebuild clean build -workspace $(WORKSPACE) \ 34 | -scheme ContentfulDeliveryAPI \ 35 | -sdk iphonesimulator -destination 'id=$(SIM_ID)'| xcpretty -c 36 | set -o pipefail && xcodebuild clean build -workspace $(WORKSPACE) \ 37 | -scheme 'UFO Example' \ 38 | -sdk iphonesimulator -destination 'id=$(SIM_ID)'| xcpretty -c 39 | 40 | example_static: static_lib 41 | cd Examples/UFO; set -o pipefail && xcodebuild clean build \ 42 | -sdk iphonesimulator -destination 'id=$(SIM_ID)'| xcpretty -c 43 | 44 | static_lib: 45 | bundle exec pod repo update >/dev/null 46 | bundle exec pod package ContentfulDeliveryAPI.podspec 47 | 48 | @cd Examples/UFO/Distribution; ./update.sh 49 | cd Examples; ./ship_it.sh 50 | 51 | rm -rf ContentfulDeliveryAPI-*/ 52 | 53 | kill_simulator: 54 | killall "Simulator" || true 55 | 56 | cda: clean_simulators 57 | set -x -o pipefail && xcodebuild -jobs `sysctl -n hw.ncpu` test -workspace $(WORKSPACE) \ 58 | -scheme 'ContentfulDeliveryAPI' -sdk iphonesimulator \ 59 | -destination 'platform=iOS Simulator,name=iPhone 6s,OS=11.4'| xcpretty -c 60 | kill_simulator 61 | bundle exec pod lib coverage 62 | 63 | integration_test: really_clean clean_simulators 64 | set -x -o pipefail && xcodebuild test -workspace $(WORKSPACE) \ 65 | -scheme 'ContentfulDeliveryAPI' -configuration "API_Coverage" \ 66 | -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6s,OS=9.3' | xcpretty -c 67 | 68 | lint: 69 | set -o pipefail && xcodebuild clean build -workspace $(WORKSPACE) -dry-run \ 70 | -scheme ContentfulDeliveryAPI \ 71 | -sdk iphonesimulator -destination 'id=$(SIM_ID)' clean build| \ 72 | xcpretty -r json-compilation-database -o compile_commands.json 73 | oclint-json-compilation-database 74 | 75 | docs: 76 | ./scripts/reference-docs.sh 77 | 78 | 79 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAClient+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAClient.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #import "CDAClient.h" 10 | 11 | @class CDAContentTypeRegistry; 12 | @class CDARequestOperationManager; 13 | 14 | extern NSString* const CDAContentTypeHeader; 15 | extern NSString* const CMAContentTypeHeader; 16 | 17 | @interface CDAClient () 18 | 19 | @property (nonatomic, readonly) BOOL localizationAvailable; 20 | @property (nonatomic, readonly) NSString* protocol; 21 | @property (nonatomic) CDARequestOperationManager* requestOperationManager; 22 | @property (nonatomic) NSString* resourceClassPrefix; 23 | 24 | -(instancetype)initWithSpaceKey:(NSString *)spaceKey 25 | accessToken:(NSString *)accessToken 26 | configuration:(CDAConfiguration*)configuration 27 | isCMARequest:(BOOL)isCMARequest; 28 | 29 | -(CDAConfiguration*)configuration; 30 | -(instancetype)copyWithSpace:(CDASpace*)space; 31 | -(CDAContentTypeRegistry*)contentTypeRegistry; 32 | -(CDARequest*)deleteURLPath:(NSString*)URLPath 33 | headers:(NSDictionary*)headers 34 | parameters:(NSDictionary*)parameters 35 | success:(CDAObjectFetchedBlock)success 36 | failure:(CDARequestFailureBlock)failure; 37 | -(CDARequest*)fetchArrayAtURLPath:(NSString *)URLPath 38 | parameters:(NSDictionary *)parameters 39 | success:(CDAArrayFetchedBlock)success 40 | failure:(CDARequestFailureBlock)failure; 41 | -(CDAArray*)fetchAssetsMatching:(NSDictionary*)query synchronouslyWithError:(NSError**)error; 42 | -(CDAArray*)fetchContentTypesMatching:(NSDictionary*)query synchronouslyWithError:(NSError**)error; 43 | -(CDAArray*)fetchEntriesMatching:(NSDictionary*)query synchronouslyWithError:(NSError**)error; 44 | -(CDARequest*)fetchURLPath:(NSString*)URLPath 45 | parameters:(NSDictionary*)parameters 46 | success:(CDAObjectFetchedBlock)success 47 | failure:(CDARequestFailureBlock)failure; 48 | -(CDARequest*)postURLPath:(NSString*)URLPath 49 | headers:(NSDictionary*)headers 50 | parameters:(NSDictionary*)parameters 51 | success:(CDAObjectFetchedBlock)success 52 | failure:(CDARequestFailureBlock)failure; 53 | -(CDARequest*)putURLPath:(NSString*)URLPath 54 | headers:(NSDictionary*)headers 55 | parameters:(NSDictionary*)parameters 56 | success:(CDAObjectFetchedBlock)success 57 | failure:(CDARequestFailureBlock)failure; 58 | -(CDASpace*)space; 59 | -(NSString*)spaceKey; 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /Tests/LocalizedModel.xcdatamodeld/LocalizedModel.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDAEntry.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAEntry.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #if __has_feature(modules) 10 | @import MapKit; 11 | #else 12 | #import " 13 | #endif 14 | 15 | #import "CDANullabilityStubs.h" 16 | #import "CDAResource.h" 17 | 18 | @class CDAContentType; 19 | 20 | NS_ASSUME_NONNULL_BEGIN 21 | 22 | /** 23 | Entries represent textual content in a Space. An Entry's data adheres to a certain Content Type. 24 | */ 25 | @interface CDAEntry : CDAResource 26 | 27 | /** @name Accessing Fields */ 28 | 29 | /** Properties according to Content Type. */ 30 | @property (nonatomic, readonly) NSDictionary* fields; 31 | 32 | /** 33 | Retrieve the value of a specific Field as a `CLLocationCoordinate2D` for easy interaction with 34 | CoreLocation or MapKit. 35 | 36 | @param identifier The `sys.id` of the Field which should be queried. 37 | @return The actual location value of the Field. 38 | @exception NSIllegalArgumentException If the specified Field is not of type Location. 39 | */ 40 | -(CLLocationCoordinate2D)CLLocationCoordinate2DFromFieldWithIdentifier:(NSString*)identifier; 41 | 42 | /** @name Accessing Content Types */ 43 | 44 | /** The Entry's Content Type. */ 45 | @property (nonatomic, readonly) CDAContentType* __nullable contentType; 46 | 47 | /** @name Accessing Localized Content */ 48 | 49 | /** 50 | * Locale to be used for accessing any values of `fields`. 51 | * 52 | * By default, this will be set to the Space's default locale. If set to a non-existing locale, it 53 | * will automatically revert to the default value. 54 | * 55 | * Changing this property only has an effect if the receiver was obtained from a `CDASyncedSpace` 56 | * originally. Entries obtained any other way will only contain values for the locale specified in 57 | * the query or for the default locale. In addition to that, this properties value will also only 58 | * be accurate for Entries obtained from a `CDASyncedSpace`originally. 59 | * 60 | */ 61 | @property (nonatomic) NSString* locale; 62 | 63 | /** @name Handle custom objects */ 64 | 65 | /** 66 | * Copy a set of Field values from this Entry to any custom object you provide. The mapping is 67 | * supposed to map a keypath on this Entry to a keypath on `object`. 68 | * 69 | * This can be used as a quick way to fill your own value objects with data from Entries fetched 70 | * from the Contentful API, e.g. if you want to use Core Data in your app. 71 | * 72 | * Currently, this method does no conversion or error checking on top of what is already provided by 73 | * Key-Value-Coding. However, it will skip all relations to other Resources, because those will 74 | * likely need special behaviour. 75 | * 76 | * Example: 77 | * 78 | * `[someEntry mapFieldsToObject:someObject usingMapping:@{ @"fields.name": @"name" }]; 79 | * 80 | * @param object The target object which is supposed to be filled with data. 81 | * @param dictionary A dictionary described the mapping between keypaths on the Entry and 82 | * keypaths on the `object` parameter. 83 | * 84 | * @return To make one line mappings possible, this method returns the `object` parameter. 85 | */ 86 | -(id)mapFieldsToObject:(NSObject*)object usingMapping:(NSDictionary*)dictionary; 87 | 88 | @end 89 | 90 | NS_ASSUME_NONNULL_END 91 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDARequestSerializer.m: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // CDARequestSerializer.m 4 | // ContentfulSDK 5 | // 6 | // Created by Boris Bügling on 21/07/14. 7 | // 8 | // 9 | 10 | #import "CDARequestSerializer.h" 11 | #import "CDAUtilities.h" 12 | #import "Versions.h" 13 | 14 | @implementation CDARequestSerializer 15 | 16 | -(instancetype)initWithAccessToken:(NSString*)accessToken { 17 | NSParameterAssert(accessToken); 18 | 19 | self = [super init]; 20 | if (self) { 21 | [self setValue:[@"Bearer " stringByAppendingString:accessToken] forHTTPHeaderField:@"Authorization"]; 22 | 23 | NSString *userAgentHeaderString = [self userAgentHeaderString]; 24 | [self setValue:userAgentHeaderString forHTTPHeaderField:@"X-Contentful-User-Agent"]; 25 | } 26 | return self; 27 | } 28 | 29 | - (NSString *)userAgentHeaderString { 30 | NSMutableString *userAgentString = [[NSMutableString alloc] initWithString:@""]; 31 | 32 | NSString *appVersionString = [self appVersionString]; 33 | if (appVersionString != nil) { 34 | [userAgentString appendString:[NSString stringWithFormat:@"app %@; ", appVersionString]]; 35 | } 36 | 37 | [userAgentString appendString:[NSString stringWithFormat:@"sdk %@;", [self sdkVersionString]]]; 38 | [userAgentString appendString:[NSString stringWithFormat:@" platform %@;", [self platformVersionString]]]; 39 | 40 | NSString *operatingSystemVersionString = [self operatingSystemVersionString]; 41 | if (operatingSystemVersionString != nil) { 42 | [userAgentString appendString:[NSString stringWithFormat:@" os %@;", operatingSystemVersionString]]; 43 | } 44 | return userAgentString; 45 | } 46 | 47 | - (NSString *)appVersionString { 48 | NSDictionary *bundleInfo = [[NSBundle mainBundle] infoDictionary]; 49 | NSString *versionNumberString = bundleInfo[@"CFBundleShortVersionString"]; 50 | NSString *appBundleId = [[NSBundle mainBundle] bundleIdentifier]; 51 | if (versionNumberString == nil || appBundleId == nil) { 52 | return nil; 53 | } 54 | 55 | return [NSString stringWithFormat:@"%@/%@", appBundleId, versionNumberString]; 56 | } 57 | 58 | - (NSString *)platformVersionString { 59 | return @"Objective-C"; 60 | } 61 | 62 | - (NSString *)sdkVersionString { 63 | 64 | NSString *sdkVersion = DELIVERY_SDK_VERSION; 65 | NSString *sdkVersionString = [NSString stringWithFormat:@"contentful.objc/%@", sdkVersion]; 66 | 67 | return sdkVersionString; 68 | } 69 | 70 | - (NSString *)operatingSystemVersionString { 71 | NSString *operatingSystemPlatform = [self operatingSystemPlatform]; 72 | if (operatingSystemPlatform == nil) { 73 | return nil; 74 | } 75 | 76 | NSOperatingSystemVersion osVersion = [[NSProcessInfo processInfo] operatingSystemVersion]; 77 | NSString *osVersionString = [NSString stringWithFormat:@"%li.%li.%li", (long)osVersion.majorVersion, (long)osVersion.minorVersion, (long)osVersion.patchVersion]; 78 | 79 | return [NSString stringWithFormat:@"%@/%@", operatingSystemPlatform, osVersionString]; 80 | } 81 | 82 | 83 | - (NSString *)operatingSystemPlatform { 84 | NSString *osName; 85 | 86 | #if TARGET_OS_IOS 87 | osName = @"iOS"; 88 | #elif TARGET_OS_OSX 89 | osName = @"macOS"; 90 | #elif TARGET_OS_TV 91 | osName = @"tvOS"; 92 | #elif TARGET_OS_WATCH 93 | osName = @"watchOS"; 94 | #endif 95 | return osName; 96 | } 97 | 98 | @end 99 | -------------------------------------------------------------------------------- /Tests/LinkedAssetSyncTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // LinkedAssetSyncTest.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 09/04/14. 6 | // 7 | // 8 | 9 | #import "SyncBaseTestCase.h" 10 | 11 | @interface LinkedAssetSyncTest : SyncBaseTestCase 12 | 13 | @end 14 | 15 | #pragma mark - 16 | 17 | @implementation LinkedAssetSyncTest 18 | 19 | -(void)setUp { 20 | [super setUp]; 21 | 22 | /* 23 | Map URLs to JSON response files 24 | */ 25 | NSDictionary* stubs = @{ @"https://cdn.contentful.com/spaces/emh6o2ireilu/sync?initial=true": @"AssetTestInitial", @"https://cdn.contentful.com/spaces/emh6o2ireilu/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyZ7T8Obw5Inwoh6Tk7Cq8KUDcK7w5ssw6vCgjLDjk9Hwr3DusOzw7XCo8OIwo3CicK5SBkqCcK7woDDhSjCkMOGw7rCqMOtE1V1L3LDq8KIck_DssK4K8OBe0vDn0vDrXjDkMOf": @"AssetTestUpdate", @"https://cdn.contentful.com/spaces/emh6o2ireilu/": @"space", @"https://cdn.contentful.com/spaces/emh6o2ireilu/assets?limit=1&sys.id%5Bin%5D=2q1Ns7Oygo2mAgoweuMCAA": @"AssetTestResolve", @"https://cdn.contentful.com/spaces/emh6o2ireilu/entries?limit=1&sys.id%5Bin%5D=6nRlw4CofeeICEYgIqaIIg": @"AssetTestResolve2", }; 26 | 27 | [self stubHTTPRequestUsingFixtures:stubs inDirectory:@"ComplexSyncTests"]; 28 | } 29 | 30 | -(void)syncedSpace:(CDASyncedSpace *)space didCreateEntry:(CDAEntry *)entry { 31 | [super syncedSpace:space didCreateEntry:entry]; 32 | 33 | XCTAssertNotNil([entry.fields[@"picture"] URL], @""); 34 | } 35 | 36 | -(void)testSyncLinkedAsset { 37 | XCTestExpectation *expectation = [self expectationWithDescription:@""]; 38 | 39 | CDARequest* request = [self.client initialSynchronizationWithSuccess:^(CDAResponse *response, CDASyncedSpace *space) { 40 | space.delegate = self; 41 | 42 | [space performSynchronizationWithSuccess:^{ 43 | [expectation fulfill]; 44 | } failure:^(CDAResponse *response, NSError *error) { 45 | XCTFail(@"Error: %@", error); 46 | 47 | [expectation fulfill]; 48 | }]; 49 | } failure:^(CDAResponse *response, NSError *error) { 50 | XCTFail(@"Error: %@", error); 51 | 52 | [expectation fulfill]; 53 | }]; 54 | XCTAssertNotNil(request, @""); 55 | 56 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 57 | } 58 | 59 | -(void)testSyncLinkedAssetWithoutSyncSpaceInstance { 60 | XCTestExpectation *expectation = [self expectationWithDescription:@""]; 61 | 62 | CDARequest* request = [self.client initialSynchronizationWithSuccess:^(CDAResponse *response, CDASyncedSpace *space) { 63 | CDASyncedSpace* shallowSyncSpace = [CDASyncedSpace shallowSyncSpaceWithToken:space.syncToken 64 | client:self.client]; 65 | shallowSyncSpace.delegate = self; 66 | 67 | [shallowSyncSpace performSynchronizationWithSuccess:^{ 68 | [expectation fulfill]; 69 | } failure:^(CDAResponse *response, NSError *error) { 70 | XCTFail(@"Error: %@", error); 71 | 72 | [expectation fulfill]; 73 | }]; 74 | } failure:^(CDAResponse *response, NSError *error) { 75 | XCTFail(@"Error: %@", error); 76 | 77 | [expectation fulfill]; 78 | }]; 79 | XCTAssertNotNil(request, @""); 80 | 81 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 82 | } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDAResource.h: -------------------------------------------------------------------------------- 1 | // 2 | // CDAResource.h 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #import "CDANullabilityStubs.h" 10 | 11 | #if __has_feature(modules) 12 | @import Foundation; 13 | #else 14 | #import 15 | #endif 16 | 17 | @class CDAClient; 18 | @class CDAResponse; 19 | 20 | NS_ASSUME_NONNULL_BEGIN 21 | 22 | /** Base class of all remotely available entities. */ 23 | @interface CDAResource : NSObject 24 | 25 | /** @name Accessing System Properties */ 26 | 27 | /** The `sys.id` of this Resource, used to uniquely identify it in its Space. */ 28 | @property (nonatomic, readonly) NSString* identifier; 29 | /** Value of all system properties of this Resource. */ 30 | @property (nonatomic, readonly) NSDictionary* sys; 31 | 32 | /** @name Comparing Resources */ 33 | 34 | /** 35 | * Returns a Boolean value that indicates whether a given Resource is equal to the receiver. 36 | * 37 | * @param resource The Resource with which to compare the receiver. 38 | * 39 | * @return `YES` if `resource` is equivalent to the receiver, otherwise `NO`. 40 | */ 41 | -(BOOL)isEqualToResource:(CDAResource*)resource; 42 | 43 | /** @name Persisting Resources */ 44 | 45 | /** 46 | * Read a previously serialized Resource from file. 47 | * 48 | * @param filePath The path to the file with a serialized Resource. 49 | * @param client The client to use for upcoming requests. 50 | * 51 | * @return A new Resource initialized with values from a previously serialized Resource. 52 | */ 53 | +(nullable instancetype)readFromFile:(NSString*)filePath client:(CDAClient*)client; 54 | 55 | /** 56 | * Read a previously serialized Resource from file. 57 | * 58 | * @param fileURL The file URL to the file with a serialized Resource. 59 | * @param client The client to use for upcoming requests. 60 | * 61 | * @return A new Resource initialized with values from a previously serialized Resource. 62 | */ 63 | +(nullable instancetype)readFromFileURL:(NSURL*)fileURL client:(CDAClient*)client; 64 | 65 | /** 66 | * Serialize a Resource to a file. 67 | * 68 | * This can be used for offline caching of Resources. 69 | * 70 | * @param filePath The path to the file to which the Resource should be written. 71 | */ 72 | -(void)writeToFile:(NSString*)filePath; 73 | 74 | /** @name Accessing Remote Data */ 75 | 76 | /** 77 | Indicates whether or not the actual data of this Resource has been fetched yet. It will usually have 78 | the value `NO` for linked resources, unless you set `include` in the initial query. You can easily 79 | request the missing data by invoking `resolveWithSuccess:failure:` on it. 80 | */ 81 | @property (nonatomic, readonly) BOOL fetched; 82 | 83 | /** 84 | * Resolve a Link by fetching the actual data of the Resource. If the data of the Resource is already 85 | * complete, the `success` block will be called immediately, with `response` being `nil` as no 86 | * additional network requests were made. 87 | * 88 | * @param success A block which gets called upon successful retrieval of all data. 89 | * @param failure A block which gets called if an error occured during the retrieval process. 90 | */ 91 | -(void)resolveWithSuccess:(void (^)(CDAResponse* response, CDAResource* resource))success 92 | failure:(void (^)(CDAResponse* response, NSError* error))failure; 93 | 94 | @end 95 | 96 | NS_ASSUME_NONNULL_END 97 | -------------------------------------------------------------------------------- /Tests/ComplexSyncTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // ComplexSyncTests.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 09/04/14. 6 | // 7 | // 8 | 9 | #import "SyncBaseTestCase.h" 10 | 11 | @interface ComplexSyncTests : SyncBaseTestCase 12 | 13 | @end 14 | 15 | #pragma mark - 16 | 17 | @implementation ComplexSyncTests 18 | 19 | -(void)setUp { 20 | [super setUp]; 21 | 22 | /* 23 | Map URLs to JSON response files 24 | */ 25 | NSDictionary* stubs = @{ @"https://cdn.contentful.com/spaces/emh6o2ireilu/sync?initial=true": @"LinkTestInitial", @"https://cdn.contentful.com/spaces/emh6o2ireilu/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyYZw7DCkTbDliYIw4vDp0jCqMOQw7vDqAbCoiLDkAnCs8OWXcOEwpXClGoUWMK1w54KCmlqw48Ow6EaJzQjwrnCnS3DpsKOw57CpMK1wrEGwrV8wpzCmcKtwqbCisK4w5LDgMKYw6HDsQ": @"LinkTestUpdate", @"https://cdn.contentful.com/spaces/emh6o2ireilu/": @"space", @"https://cdn.contentful.com/spaces/emh6o2ireilu/entries?sys.id%5Bin%5D=6nRlw4CofeeICEYgIqaIIg": @"LinkTestResolve", }; 26 | 27 | [self stubHTTPRequestUsingFixtures:stubs inDirectory:@"ComplexSyncTests"]; 28 | } 29 | 30 | -(void)testSyncWithLinks { 31 | XCTestExpectation *expectation = [self expectationWithDescription:@""]; 32 | 33 | CDARequest* request = [self.client initialSynchronizationWithSuccess:^(CDAResponse *response, CDASyncedSpace *space) { 34 | space.delegate = self; 35 | 36 | [space performSynchronizationWithSuccess:^{ 37 | [expectation fulfill]; 38 | } failure:^(CDAResponse *response, NSError *error) { 39 | XCTFail(@"Error: %@", error); 40 | 41 | [expectation fulfill]; 42 | }]; 43 | } failure:^(CDAResponse *response, NSError *error) { 44 | XCTFail(@"Error: %@", error); 45 | 46 | [expectation fulfill]; 47 | }]; 48 | XCTAssertNotNil(request, @""); 49 | 50 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 51 | 52 | XCTAssertEqual(2U, self.numberOfEntriesCreated, @""); 53 | XCTAssertEqual(0U, self.numberOfEntriesUpdated, @""); 54 | } 55 | 56 | -(void)testSyncWithLinksWithoutSyncSpaceInstance { 57 | XCTestExpectation *expectation = [self expectationWithDescription:@""]; 58 | 59 | CDARequest* request = [self.client initialSynchronizationWithSuccess:^(CDAResponse *response, CDASyncedSpace *space) { 60 | NSString* syncToken = space.syncToken; 61 | space = nil; 62 | XCTAssertNil(space, @""); 63 | 64 | self.client = [self mockContentTypeRetrievalForClient:[self buildClient]]; 65 | CDASyncedSpace* shallowSyncSpace = [CDASyncedSpace shallowSyncSpaceWithToken:syncToken 66 | client:self.client]; 67 | shallowSyncSpace.delegate = self; 68 | 69 | [shallowSyncSpace performSynchronizationWithSuccess:^{ 70 | [expectation fulfill]; 71 | } failure:^(CDAResponse *response, NSError *error) { 72 | XCTFail(@"Error: %@", error); 73 | 74 | [expectation fulfill]; 75 | }]; 76 | } failure:^(CDAResponse *response, NSError *error) { 77 | XCTFail(@"Error: %@", error); 78 | 79 | [expectation fulfill]; 80 | }]; 81 | XCTAssertNotNil(request, @""); 82 | 83 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 84 | 85 | XCTAssertEqual(2U, self.numberOfEntriesCreated, @""); 86 | XCTAssertEqual(0U, self.numberOfEntriesUpdated, @""); 87 | } 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /Tests/Data/ComplexSyncTests/LinkTestInitial.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "space": { 9 | "sys": { 10 | "type": "Link", 11 | "linkType": "Space", 12 | "id": "emh6o2ireilu" 13 | } 14 | }, 15 | "type": "Entry", 16 | "contentType": { 17 | "sys": { 18 | "type": "Link", 19 | "linkType": "ContentType", 20 | "id": "51LZmvenywOe8aig28sCgY" 21 | } 22 | }, 23 | "id": "1YCXOBW1xu2uSkekCmwyqo", 24 | "revision": 1, 25 | "createdAt": "2014-04-09T11:44:21.359Z", 26 | "updatedAt": "2014-04-09T11:44:21.359Z" 27 | }, 28 | "fields": { 29 | "name": { 30 | "en-US": "Some Category" 31 | } 32 | } 33 | }, 34 | { 35 | "fields": { 36 | "title": { 37 | "en-US": "doge (changed)" 38 | }, 39 | "file": { 40 | "en-US": { 41 | "fileName": "vaa4by0.png", 42 | "contentType": "image/png", 43 | "details": { 44 | "image": { 45 | "width": 265, 46 | "height": 265 47 | }, 48 | "size": 103652 49 | }, 50 | "url": "//images.contentful.com/emh6o2ireilu/6koKmTXVzUquae6ewQQ8Eu/5f79cef98e3feb466c30f0b38bec0359/vaa4by0.png" 51 | } 52 | } 53 | }, 54 | "sys": { 55 | "space": { 56 | "sys": { 57 | "type": "Link", 58 | "linkType": "Space", 59 | "id": "emh6o2ireilu" 60 | } 61 | }, 62 | "type": "Asset", 63 | "id": "6koKmTXVzUquae6ewQQ8Eu", 64 | "revision": 2, 65 | "createdAt": "2014-03-25T11:08:25.412Z", 66 | "updatedAt": "2014-03-26T10:37:20.069Z" 67 | } 68 | }, 69 | { 70 | "sys": { 71 | "space": { 72 | "sys": { 73 | "type": "Link", 74 | "linkType": "Space", 75 | "id": "emh6o2ireilu" 76 | } 77 | }, 78 | "type": "Entry", 79 | "contentType": { 80 | "sys": { 81 | "type": "Link", 82 | "linkType": "ContentType", 83 | "id": "6bAvxqodl6s4MoKuWYkmqe" 84 | } 85 | }, 86 | "id": "4upDPGUMMEkG8w8UUs2OiO", 87 | "revision": 2, 88 | "createdAt": "2014-03-25T11:00:43.065Z", 89 | "updatedAt": "2014-03-26T10:37:09.128Z" 90 | }, 91 | "fields": { 92 | "title": { 93 | "en-US": "Test (changed)" 94 | }, 95 | "body": { 96 | "en-US": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 97 | } 98 | } 99 | } 100 | ], 101 | "nextSyncUrl": "http://cdn.contentful.com/spaces/emh6o2ireilu/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyYZw7DCkTbDliYIw4vDp0jCqMOQw7vDqAbCoiLDkAnCs8OWXcOEwpXClGoUWMK1w54KCmlqw48Ow6EaJzQjwrnCnS3DpsKOw57CpMK1wrEGwrV8wpzCmcKtwqbCisK4w5LDgMKYw6HDsQ" 102 | } 103 | -------------------------------------------------------------------------------- /Tests/Data/ComplexSyncTests/ComplexLinkTestEntries.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 2, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "emh6o2ireilu" 16 | } 17 | }, 18 | "type": "Entry", 19 | "contentType": { 20 | "sys": { 21 | "type": "Link", 22 | "linkType": "ContentType", 23 | "id": "6bAvxqodl6s4MoKuWYkmqe" 24 | } 25 | }, 26 | "id": "4upDPGUMMEkG8w8UUs2OiO", 27 | "revision": 2, 28 | "createdAt": "2014-03-25T11:00:43.065Z", 29 | "updatedAt": "2014-03-26T10:37:09.128Z", 30 | "locale": "en-US" 31 | }, 32 | "fields": { 33 | "title": "Test (changed)", 34 | "body": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 35 | } 36 | }, 37 | { 38 | "sys": { 39 | "space": { 40 | "sys": { 41 | "type": "Link", 42 | "linkType": "Space", 43 | "id": "emh6o2ireilu" 44 | } 45 | }, 46 | "type": "Entry", 47 | "contentType": { 48 | "sys": { 49 | "type": "Link", 50 | "linkType": "ContentType", 51 | "id": "6bAvxqodl6s4MoKuWYkmqe" 52 | } 53 | }, 54 | "id": "1Wl5HnguK8CiaykiQAiGu6", 55 | "revision": 3, 56 | "createdAt": "2014-04-09T11:48:47.866Z", 57 | "updatedAt": "2014-04-10T09:36:40.283Z", 58 | "locale": "en-US" 59 | }, 60 | "fields": { 61 | "title": "WithCategory", 62 | "body": "some text", 63 | "category": null, 64 | "picture": { 65 | "sys": { 66 | "type": "Link", 67 | "linkType": "Asset", 68 | "id": "2q1Ns7Oygo2mAgoweuMCAA" 69 | } 70 | } 71 | } 72 | } 73 | ], 74 | "includes": { 75 | "Asset": [ 76 | { 77 | "fields": { 78 | "title": "contentful-logo", 79 | "file": { 80 | "fileName": "512_black.png", 81 | "contentType": "image/png", 82 | "details": { 83 | "image": { 84 | "width": 512, 85 | "height": 512 86 | }, 87 | "size": 4189 88 | }, 89 | "url": "//images.contentful.com/emh6o2ireilu/2q1Ns7Oygo2mAgoweuMCAA/7a4eb574952f359b851b3f353573b708/512_black.png" 90 | } 91 | }, 92 | "sys": { 93 | "space": { 94 | "sys": { 95 | "type": "Link", 96 | "linkType": "Space", 97 | "id": "emh6o2ireilu" 98 | } 99 | }, 100 | "type": "Asset", 101 | "id": "2q1Ns7Oygo2mAgoweuMCAA", 102 | "revision": 2, 103 | "createdAt": "2014-03-25T11:01:41.057Z", 104 | "updatedAt": "2014-04-10T08:46:27.961Z", 105 | "locale": "en-US" 106 | } 107 | } 108 | ] 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Tests/Data/ComplexSyncTests/ComplexLinkTestContentTypes.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 2, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "sys": { 11 | "space": { 12 | "sys": { 13 | "type": "Link", 14 | "linkType": "Space", 15 | "id": "emh6o2ireilu" 16 | } 17 | }, 18 | "type": "Entry", 19 | "contentType": { 20 | "sys": { 21 | "type": "Link", 22 | "linkType": "ContentType", 23 | "id": "6bAvxqodl6s4MoKuWYkmqe" 24 | } 25 | }, 26 | "id": "4upDPGUMMEkG8w8UUs2OiO", 27 | "revision": 2, 28 | "createdAt": "2014-03-25T11:00:43.065Z", 29 | "updatedAt": "2014-03-26T10:37:09.128Z", 30 | "locale": "en-US" 31 | }, 32 | "fields": { 33 | "title": "Test (changed)", 34 | "body": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 35 | } 36 | }, 37 | { 38 | "sys": { 39 | "space": { 40 | "sys": { 41 | "type": "Link", 42 | "linkType": "Space", 43 | "id": "emh6o2ireilu" 44 | } 45 | }, 46 | "type": "Entry", 47 | "contentType": { 48 | "sys": { 49 | "type": "Link", 50 | "linkType": "ContentType", 51 | "id": "6bAvxqodl6s4MoKuWYkmqe" 52 | } 53 | }, 54 | "id": "1Wl5HnguK8CiaykiQAiGu6", 55 | "revision": 3, 56 | "createdAt": "2014-04-09T11:48:47.866Z", 57 | "updatedAt": "2014-04-10T09:36:40.283Z", 58 | "locale": "en-US" 59 | }, 60 | "fields": { 61 | "title": "WithCategory", 62 | "body": "some text", 63 | "category": null, 64 | "picture": { 65 | "sys": { 66 | "type": "Link", 67 | "linkType": "Asset", 68 | "id": "2q1Ns7Oygo2mAgoweuMCAA" 69 | } 70 | } 71 | } 72 | } 73 | ], 74 | "includes": { 75 | "Asset": [ 76 | { 77 | "fields": { 78 | "title": "contentful-logo", 79 | "file": { 80 | "fileName": "512_black.png", 81 | "contentType": "image/png", 82 | "details": { 83 | "image": { 84 | "width": 512, 85 | "height": 512 86 | }, 87 | "size": 4189 88 | }, 89 | "url": "//images.contentful.com/emh6o2ireilu/2q1Ns7Oygo2mAgoweuMCAA/7a4eb574952f359b851b3f353573b708/512_black.png" 90 | } 91 | }, 92 | "sys": { 93 | "space": { 94 | "sys": { 95 | "type": "Link", 96 | "linkType": "Space", 97 | "id": "emh6o2ireilu" 98 | } 99 | }, 100 | "type": "Asset", 101 | "id": "2q1Ns7Oygo2mAgoweuMCAA", 102 | "revision": 2, 103 | "createdAt": "2014-03-25T11:01:41.057Z", 104 | "updatedAt": "2014-04-10T08:46:27.961Z", 105 | "locale": "en-US" 106 | } 107 | } 108 | ] 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Tests/NewEntryAndDeletionSyncTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // NewEntryAndDeletionSyncTest.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 10/04/14. 6 | // 7 | // 8 | 9 | #import "SyncBaseTestCase.h" 10 | 11 | @interface NewEntryAndDeletionSyncTest : SyncBaseTestCase 12 | 13 | @end 14 | 15 | #pragma mark - 16 | 17 | @implementation NewEntryAndDeletionSyncTest 18 | 19 | -(void)setUp { 20 | [super setUp]; 21 | 22 | /* 23 | Map URLs to JSON response files 24 | */ 25 | NSDictionary* stubs = @{ @"https://cdn.contentful.com/spaces/emh6o2ireilu/sync?initial=true": @"NewEntryAndDeletionInitial", @"https://cdn.contentful.com/spaces/emh6o2ireilu/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdybDjMOhw7gcw7BNwqzCqSDDhhrCjcKqWEDCjMKHwp4RE2ROw73ChnJnc3MVwpTCgcOcEMOxw53CpgbCpSXCocKbCBVHF8Kyw5nDmUIKwrnDpVByw4l5w57DvV4swoTCuxPCnQ": @"NewEntryAndDeletionUpdate", @"https://cdn.contentful.com/spaces/emh6o2ireilu/": @"space", @"https://cdn.contentful.com/spaces/emh6o2ireilu/assets?limit=1&sys.id%5Bin%5D=2q1Ns7Oygo2mAgoweuMCAA": @"NewEntryAndDeletionResolve", @"https://cdn.contentful.com/spaces/emh6o2ireilu/entries?limit=1&sys.id%5Bin%5D=6nRlw4CofeeICEYgIqaIIg": @"NewEntryAndDeletionResolve2", }; 26 | 27 | [self stubHTTPRequestUsingFixtures:stubs inDirectory:@"ComplexSyncTests"]; 28 | } 29 | 30 | -(void)testSyncNewEntryAndDeletion { 31 | XCTestExpectation *expectation = [self expectationWithDescription:@""]; 32 | 33 | CDARequest* request = [self.client initialSynchronizationWithSuccess:^(CDAResponse *response, CDASyncedSpace *space) { 34 | space.delegate = self; 35 | 36 | [space performSynchronizationWithSuccess:^{ 37 | [expectation fulfill]; 38 | } failure:^(CDAResponse *response, NSError *error) { 39 | XCTFail(@"Error: %@", error); 40 | 41 | [expectation fulfill]; 42 | }]; 43 | } failure:^(CDAResponse *response, NSError *error) { 44 | XCTFail(@"Error: %@", error); 45 | 46 | [expectation fulfill]; 47 | }]; 48 | XCTAssertNotNil(request, @""); 49 | 50 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 51 | 52 | XCTAssertEqual(1U, self.numberOfEntriesCreated, @""); 53 | XCTAssertEqual(1U, self.numberOfEntriesUpdated, @""); 54 | } 55 | 56 | -(void)testSyncNewEntryAndDeletionWithoutSyncSpaceInstance { 57 | XCTestExpectation *expectation = [self expectationWithDescription:@""]; 58 | 59 | CDARequest* request = [self.client initialSynchronizationWithSuccess:^(CDAResponse *response, CDASyncedSpace *space) { 60 | CDASyncedSpace* shallowSyncSpace = [CDASyncedSpace shallowSyncSpaceWithToken:space.syncToken 61 | client:self.client]; 62 | shallowSyncSpace.delegate = self; 63 | shallowSyncSpace.lastSyncTimestamp = space.lastSyncTimestamp; 64 | 65 | [shallowSyncSpace performSynchronizationWithSuccess:^{ 66 | [expectation fulfill]; 67 | } failure:^(CDAResponse *response, NSError *error) { 68 | XCTFail(@"Error: %@", error); 69 | 70 | [expectation fulfill]; 71 | }]; 72 | } failure:^(CDAResponse *response, NSError *error) { 73 | XCTFail(@"Error: %@", error); 74 | 75 | [expectation fulfill]; 76 | }]; 77 | XCTAssertNotNil(request, @""); 78 | 79 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 80 | 81 | XCTAssertEqual(1U, self.numberOfEntriesCreated, @""); 82 | XCTAssertEqual(1U, self.numberOfEntriesUpdated, @""); 83 | } 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /ContentfulSDK.xcodeproj/xcshareddata/xcschemes/CoreDataExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ContentfulSDK.xcodeproj/xcshareddata/xcschemes/DeliveryTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 68 | 69 | 70 | 71 | 75 | 76 | 77 | 78 | 79 | 80 | 86 | 87 | 89 | 90 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /Tests/Data/SyncTests/all-content-types.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "total": 4, 6 | "skip": 0, 7 | "limit": 100, 8 | "items": [ 9 | { 10 | "fields": [ 11 | { 12 | "name": "link1", 13 | "id": "link1", 14 | "type": "Link", 15 | "linkType": "Entry" 16 | }, 17 | { 18 | "name": "link2", 19 | "id": "link2", 20 | "type": "Link", 21 | "linkType": "Entry" 22 | }, 23 | { 24 | "name": "link3", 25 | "id": "link3", 26 | "type": "Link", 27 | "linkType": "Entry" 28 | } 29 | ], 30 | "name": "ThreeLinks to other Entries", 31 | "sys": { 32 | "space": { 33 | "sys": { 34 | "type": "Link", 35 | "linkType": "Space", 36 | "id": "emh6o2ireilu" 37 | } 38 | }, 39 | "type": "ContentType", 40 | "id": "4yCmJfmk1WeqACagaemOIs", 41 | "revision": 1, 42 | "createdAt": "2014-04-11T11:38:32.120Z", 43 | "updatedAt": "2014-04-11T11:38:32.120Z" 44 | } 45 | }, 46 | { 47 | "fields": [ 48 | { 49 | "name": "Title", 50 | "id": "title", 51 | "type": "Symbol", 52 | "required": true 53 | }, 54 | { 55 | "name": "Body", 56 | "id": "body", 57 | "type": "Text", 58 | "required": true 59 | }, 60 | { 61 | "name": "Picture", 62 | "id": "picture", 63 | "type": "Link", 64 | "linkType": "Asset" 65 | }, 66 | { 67 | "name": "Category", 68 | "id": "category", 69 | "type": "Link", 70 | "linkType": "Entry" 71 | } 72 | ], 73 | "name": "Blog Post", 74 | "sys": { 75 | "space": { 76 | "sys": { 77 | "type": "Link", 78 | "linkType": "Space", 79 | "id": "emh6o2ireilu" 80 | } 81 | }, 82 | "type": "ContentType", 83 | "id": "6bAvxqodl6s4MoKuWYkmqe", 84 | "revision": 3, 85 | "createdAt": "2014-03-25T11:00:11.739Z", 86 | "updatedAt": "2014-04-09T11:48:15.120Z" 87 | }, 88 | "displayField": "title" 89 | }, 90 | { 91 | "fields": [ 92 | { 93 | "name": "such field", 94 | "id": "suchField", 95 | "type": "Symbol" 96 | } 97 | ], 98 | "name": "⚡️ new content type", 99 | "sys": { 100 | "space": { 101 | "sys": { 102 | "type": "Link", 103 | "linkType": "Space", 104 | "id": "emh6o2ireilu" 105 | } 106 | }, 107 | "type": "ContentType", 108 | "id": "5kLp8FbRwAG0kcOOYa6GMa", 109 | "revision": 1, 110 | "createdAt": "2014-04-11T08:39:04.421Z", 111 | "updatedAt": "2014-04-11T08:39:04.421Z" 112 | }, 113 | "displayField": "suchField" 114 | }, 115 | { 116 | "fields": [ 117 | { 118 | "name": "Name", 119 | "id": "name", 120 | "type": "Symbol" 121 | } 122 | ], 123 | "name": "Category", 124 | "sys": { 125 | "space": { 126 | "sys": { 127 | "type": "Link", 128 | "linkType": "Space", 129 | "id": "emh6o2ireilu" 130 | } 131 | }, 132 | "type": "ContentType", 133 | "id": "51LZmvenywOe8aig28sCgY", 134 | "revision": 1, 135 | "createdAt": "2014-04-09T11:43:50.890Z", 136 | "updatedAt": "2014-04-09T11:43:50.890Z" 137 | }, 138 | "displayField": "name" 139 | } 140 | ] 141 | } 142 | -------------------------------------------------------------------------------- /Tests/SyncSpecificContentTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // SyncSpecificContentTests.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 30/04/14. 6 | // 7 | // 8 | 9 | #import "SyncBaseTestCase.h" 10 | 11 | @interface SyncSpecificContentTests : SyncBaseTestCase 12 | 13 | @end 14 | 15 | #pragma mark - 16 | 17 | @implementation SyncSpecificContentTests 18 | 19 | -(void)performSyncTestWithQuery:(NSDictionary*)query 20 | expectedNumberOfAssets:(NSUInteger)numberOfAssets 21 | entries:(NSUInteger)numberOfEntries { 22 | XCTestExpectation *expectation = [self expectationWithDescription:@""]; 23 | 24 | [self.client initialSynchronizationMatching:query 25 | success:^(CDAResponse *response, CDASyncedSpace *space) { 26 | XCTAssertEqual(numberOfAssets, space.assets.count, @""); 27 | XCTAssertEqual(numberOfEntries, space.entries.count, @""); 28 | 29 | [expectation fulfill]; 30 | } failure:^(CDAResponse *response, NSError *error) { 31 | XCTFail(@"Error: %@", error); 32 | 33 | [expectation fulfill]; 34 | }]; 35 | 36 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 37 | } 38 | 39 | #pragma mark - 40 | 41 | -(void)testThrowsWhenSpecifyingInvalidQuery { 42 | XCTestExpectation *expectation = [self expectationWithDescription:@""]; 43 | 44 | [self.client initialSynchronizationMatching:@{ @"type": @"bar" } 45 | success:^(CDAResponse *response, CDASyncedSpace *space) { 46 | XCTFail(@"This shouldn't be reached."); 47 | 48 | [expectation fulfill]; 49 | } failure:^(CDAResponse *response, NSError *error) { 50 | XCTAssertNotNil(error, @""); 51 | 52 | [expectation fulfill]; 53 | }]; 54 | 55 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 56 | } 57 | 58 | -(void)testSyncAll { 59 | [self performSyncTestWithQuery:@{ @"type": @"all" } 60 | expectedNumberOfAssets:2U 61 | entries:7U]; 62 | } 63 | 64 | -(void)testSyncAssets { 65 | [self performSyncTestWithQuery:@{ @"type": @"Asset" } 66 | expectedNumberOfAssets:2U 67 | entries:0U]; 68 | } 69 | 70 | -(void)testSyncEntries { 71 | [self performSyncTestWithQuery:@{ @"type": @"Entry" } 72 | expectedNumberOfAssets:0U 73 | entries:7U]; 74 | } 75 | 76 | -(void)testSyncEntriesForContentType { 77 | self.client = [CDAClient new]; 78 | 79 | [self performSyncTestWithQuery:@{ @"type": @"Entry", @"content_type": @"cat" } 80 | expectedNumberOfAssets:0U 81 | entries:3U]; 82 | } 83 | 84 | -(void)testSyncDeletions { 85 | [self performSyncTestWithQuery:@{ @"type": @"Deletion" } 86 | expectedNumberOfAssets:0U 87 | entries:0U]; 88 | } 89 | 90 | -(void)testSyncDeletionsOfAssets { 91 | [self performSyncTestWithQuery:@{ @"type": @"DeletedAsset" } 92 | expectedNumberOfAssets:0U 93 | entries:0U]; 94 | } 95 | 96 | -(void)testSyncDeletionsOfEntries { 97 | [self performSyncTestWithQuery:@{ @"type": @"DeletedEntry" } 98 | expectedNumberOfAssets:0U 99 | entries:0U]; 100 | } 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /Tests/ClientConfiguration.m: -------------------------------------------------------------------------------- 1 | // 2 | // ClientConfiguration.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 01/10/14. 6 | // 7 | // 8 | 9 | #import "ContentfulBaseTestCase.h" 10 | 11 | @interface ClientConfiguration : ContentfulBaseTestCase 12 | 13 | @end 14 | 15 | #pragma mark - 16 | 17 | @implementation ClientConfiguration 18 | 19 | -(void)testClientCanBeInstantiatedWithoutSpaceKey { 20 | CDAClient* client1 = [[CDAClient alloc] initWithSpaceKey:nil accessToken:@"yolo"]; 21 | CDAClient* client2 = [[CDAClient alloc] initWithSpaceKey:nil 22 | accessToken:@"yolo" 23 | configuration:[CDAConfiguration defaultConfiguration]]; 24 | 25 | XCTAssertNotNil(client1); 26 | XCTAssertNotNil(client2); 27 | } 28 | 29 | -(void)testDefaultUserAgent { 30 | XCTestExpectation *expectation = [self expectationWithDescription:@""]; 31 | 32 | CDARequest* request = [self.client fetchEntriesWithSuccess:^(CDAResponse *response, 33 | CDAArray *array) { 34 | [expectation fulfill]; 35 | } failure:^(CDAResponse *response, NSError *error) { 36 | [expectation fulfill]; 37 | }]; 38 | 39 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 40 | 41 | NSString* userAgentString = request.request.allHTTPHeaderFields[@"X-Contentful-User-Agent"]; 42 | 43 | NSString *versionNumberRegexString = @"\\d+\\.\\d+\\.\\d+(-(beta|RC|alpha)\\d*)?"; 44 | 45 | NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:[NSString stringWithFormat:@"sdk contentful.objc/%@; platform Objective-C; os iOS/\\d+\\.\\d+\\.\\d+;", versionNumberRegexString] options:0 error:nil]; 46 | NSArray *matches = [regex matchesInString:userAgentString options:0 range:NSMakeRange(0, userAgentString.length)]; 47 | 48 | 49 | XCTAssertTrue(matches.count == 1, @"The user agent header should have had at least one match."); 50 | } 51 | 52 | -(void)testFilterMissingEntities { 53 | CDAConfiguration* configuration = [CDAConfiguration defaultConfiguration]; 54 | configuration.filterNonExistingResources = YES; 55 | 56 | self.client = [[CDAClient alloc] initWithSpaceKey:@"vfvjfjyjrbbp" 57 | accessToken:@"422588c021896d2ae01eaf2d68faa720aaf6da4b361e7c99e9afac6feacb498b" 58 | configuration:configuration]; 59 | 60 | XCTestExpectation *expectation = [self expectationWithDescription:@""]; 61 | 62 | [self.client fetchEntriesWithSuccess:^(CDAResponse* response, CDAArray* array) { 63 | XCTAssertEqual(array.items.count, 1); 64 | 65 | CDAEntry* me = array.items.firstObject; 66 | XCTAssertNil(me.fields[@"link"]); 67 | 68 | [expectation fulfill]; 69 | } failure:^(CDAResponse *response, NSError *error) { 70 | XCTFail(@"Error: %@", error); 71 | 72 | [expectation fulfill]; 73 | }]; 74 | 75 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 76 | } 77 | 78 | -(void)testNotFilterMissingEntitiesIfNotConfigured { 79 | self.client = [[CDAClient alloc] initWithSpaceKey:@"vfvjfjyjrbbp" 80 | accessToken:@"422588c021896d2ae01eaf2d68faa720aaf6da4b361e7c99e9afac6feacb498b"]; 81 | 82 | XCTestExpectation *expectation = [self expectationWithDescription:@""]; 83 | 84 | [self.client fetchEntriesWithSuccess:^(CDAResponse* response, CDAArray* array) { 85 | XCTAssertEqual(array.items.count, 1); 86 | 87 | CDAEntry* me = array.items.firstObject; 88 | XCTAssertNotNil(me.fields[@"link"]); 89 | 90 | [expectation fulfill]; 91 | } failure:^(CDAResponse *response, NSError *error) { 92 | XCTFail(@"Error: %@", error); 93 | 94 | [expectation fulfill]; 95 | }]; 96 | 97 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 98 | } 99 | 100 | @end 101 | -------------------------------------------------------------------------------- /ContentfulSDK.xcodeproj/xcshareddata/xcschemes/ContentfulManagementAPI.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/Resources/CDAContentType.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDAContentType.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 04/03/14. 6 | // 7 | // 8 | 9 | #import "CDAContentType.h" 10 | 11 | #import "CDAClient+Private.h" 12 | #import "CDAContentTypeRegistry.h" 13 | #import "CDAField+Private.h" 14 | #import "CDAResource+Private.h" 15 | 16 | @interface CDAContentType () 17 | 18 | @property (nonatomic) NSDictionary* allFields; 19 | @property (nonatomic) NSString* displayField; 20 | @property (nonatomic) NSArray* fields; 21 | @property (nonatomic) NSString* name; 22 | @property (nonatomic) NSString* userDescription; 23 | 24 | @end 25 | 26 | #pragma mark - 27 | 28 | @implementation CDAContentType 29 | 30 | +(NSString *)CDAType { 31 | return @"ContentType"; 32 | } 33 | 34 | +(Class)fieldClass { 35 | return CDAField.class; 36 | } 37 | 38 | #pragma mark - 39 | 40 | -(NSString *)description { 41 | return [NSString stringWithFormat:@"CDAContentType %@ with %ld fields", 42 | self.name, (long)self.allFields.count]; 43 | } 44 | 45 | -(CDAField*)fieldForIdentifier:(NSString *)identifier { 46 | return self.allFields[identifier]; 47 | } 48 | 49 | -(instancetype)initWithDictionary:(NSDictionary *)dictionary 50 | client:(CDAClient*)client 51 | localizationAvailable:(BOOL)localizationAvailable { 52 | self = [super initWithDictionary:dictionary client:client localizationAvailable:localizationAvailable]; 53 | if (self) { 54 | if (dictionary[@"name"]) { 55 | NSString* name = dictionary[@"name"]; 56 | self.name = name; 57 | } else { 58 | [NSException raise:NSInvalidArgumentException format:@"Content-Types need a name"]; 59 | } 60 | 61 | self.displayField = dictionary[@"displayField"]; 62 | self.userDescription = dictionary[@"description"]; 63 | 64 | NSMutableDictionary* allFields = [@{} mutableCopy]; 65 | NSMutableArray* fields = [@[] mutableCopy]; 66 | 67 | for (NSDictionary* field in dictionary[@"fields"]) { 68 | CDAField* fieldObject = [[[self.class fieldClass] alloc] initWithDictionary:field 69 | client:self.client 70 | localizationAvailable:localizationAvailable]; 71 | 72 | allFields[fieldObject.identifier] = fieldObject; 73 | [fields addObject:fieldObject]; 74 | } 75 | 76 | self.allFields = allFields; 77 | self.fields = fields; 78 | 79 | [self.client.contentTypeRegistry addContentType:self]; 80 | } 81 | return self; 82 | } 83 | 84 | -(void)resolveWithSuccess:(void (^)(CDAResponse *, CDAResource *))success 85 | failure:(void (^)(CDAResponse *, NSError *))failure { 86 | if (self.fetched) { 87 | [super resolveWithSuccess:success failure:failure]; 88 | return; 89 | } 90 | 91 | [self.client fetchContentTypeWithIdentifier:self.identifier 92 | success:^(CDAResponse *response, CDAContentType *contentType) { 93 | if (success) { 94 | success(response, contentType); 95 | } 96 | } failure:failure]; 97 | } 98 | 99 | // We only encode properties that have write permissions 100 | #pragma mark - NSCoding 101 | 102 | -(instancetype)initWithCoder:(NSCoder *)aDecoder { 103 | self = [super initWithCoder:aDecoder]; 104 | if (self) { 105 | self.allFields = [aDecoder decodeObjectForKey:@"allFields"]; 106 | self.displayField = [aDecoder decodeObjectForKey:@"displayField"]; 107 | self.fields = [aDecoder decodeObjectForKey:@"fields"]; 108 | self.name = [aDecoder decodeObjectForKey:@"name"]; 109 | self.userDescription = [aDecoder decodeObjectForKey:@"userDescription"]; 110 | } 111 | return self; 112 | } 113 | 114 | -(void)encodeWithCoder:(NSCoder *)aCoder { 115 | [super encodeWithCoder:aCoder]; 116 | 117 | [aCoder encodeObject:self.allFields forKey:@"allFields"]; 118 | [aCoder encodeObject:self.displayField forKey:@"displayField"]; 119 | [aCoder encodeObject:self.fields forKey:@"fields"]; 120 | [aCoder encodeObject:self.name forKey:@"name"]; 121 | [aCoder encodeObject:self.userDescription forKey:@"userDescription"]; 122 | 123 | } 124 | 125 | @end 126 | -------------------------------------------------------------------------------- /ContentfulSDK.xcodeproj/xcshareddata/xcschemes/ContentfulDeliveryAPI.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 79 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 98 | 104 | 105 | 106 | 107 | 109 | 110 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /ContentfulDeliveryAPI/CDAFieldValueTransformer.m: -------------------------------------------------------------------------------- 1 | // 2 | // CDAFieldValueTransformer.m 3 | // ContentfulSDK 4 | // 5 | // Created by Boris Bügling on 09/03/14. 6 | // 7 | // 8 | 9 | @import MapKit; 10 | 11 | #import 12 | #import "CDAClient+Private.h" 13 | #import "CDAFieldValueTransformer.h" 14 | #import "CDAResource+Private.h" 15 | #import "CDAUtilities.h" 16 | 17 | @interface CDAFieldValueTransformer () 18 | 19 | @property (nonatomic, weak) CDAClient* client; 20 | @property (nonatomic) BOOL localizationAvailable; 21 | @property (nonatomic) CDAFieldType type; 22 | 23 | @end 24 | 25 | #pragma mark - 26 | 27 | @implementation CDAFieldValueTransformer 28 | 29 | +(BOOL)allowsReverseTransformation { 30 | return NO; 31 | } 32 | 33 | +(instancetype)transformerOfType:(CDAFieldType)type 34 | client:(CDAClient*)client 35 | localizationAvailable:(BOOL)localizationAvailable { 36 | return [[[self class] alloc] initWithType:type client:client localizationAvailable:localizationAvailable]; 37 | } 38 | 39 | #pragma mark - 40 | 41 | -(instancetype)initWithType:(CDAFieldType)type 42 | client:(CDAClient*)client 43 | localizationAvailable:(BOOL)localizationAvailable { 44 | self = [super init]; 45 | if (self) { 46 | NSParameterAssert(client); 47 | self.client = client; 48 | 49 | self.itemType = CDAFieldTypeNone; 50 | self.type = type; 51 | } 52 | return self; 53 | } 54 | 55 | -(id)locationFromDictionary:(NSDictionary*)dictionary { 56 | CLLocationCoordinate2D location; 57 | if ([dictionary isKindOfClass:[NSDictionary class]]) { 58 | location.latitude = [dictionary[@"lat"] doubleValue]; 59 | location.longitude = [dictionary[@"lon"] doubleValue]; 60 | } 61 | return [NSData dataWithBytes:&location length:sizeof(CLLocationCoordinate2D)]; 62 | } 63 | 64 | -(id)transformArrayValue:(id)arrayValue { 65 | CDAFieldValueTransformer* transformer = [CDAFieldValueTransformer transformerOfType:self.itemType 66 | client:self.client 67 | localizationAvailable:self.localizationAvailable]; 68 | 69 | NSMutableArray* array = [@[] mutableCopy]; 70 | for (id value in arrayValue) { 71 | id transformedValue = [transformer transformedValue:value]; 72 | if(transformedValue) { 73 | [array addObject:transformedValue]; 74 | } 75 | } 76 | 77 | return [array copy]; 78 | } 79 | 80 | -(id)transformedValue:(id)value { 81 | switch (self.type) { 82 | case CDAFieldTypeArray: 83 | if (value == [NSNull null] || ![value isKindOfClass:[NSArray class]]) { 84 | return @[]; 85 | } 86 | 87 | return [self transformArrayValue:value]; 88 | 89 | case CDAFieldTypeDate: 90 | if (value == [NSNull null] || ![value isKindOfClass:[NSString class]]) { 91 | return nil; 92 | } 93 | 94 | return [NSDate dateWithISO8601String:value]; 95 | 96 | case CDAFieldTypeBoolean: 97 | case CDAFieldTypeInteger: 98 | case CDAFieldTypeNumber: 99 | if (value == [NSNull null] || ![value isKindOfClass:[NSNumber class]]) { 100 | return @0; 101 | } 102 | 103 | return value; 104 | 105 | case CDAFieldTypeObject: 106 | if (value == [NSNull null]) { 107 | return nil; 108 | } 109 | 110 | return value; 111 | 112 | case CDAFieldTypeAsset: 113 | case CDAFieldTypeEntry: 114 | case CDAFieldTypeLink: 115 | if (value == [NSNull null]) { 116 | return nil; 117 | } 118 | 119 | return [CDAResource resourceObjectForDictionary:value 120 | client:self.client 121 | localizationAvailable:self.localizationAvailable]; 122 | 123 | case CDAFieldTypeLocation: 124 | if (value == [NSNull null]) { 125 | return nil; 126 | } 127 | 128 | return [self locationFromDictionary:value]; 129 | 130 | case CDAFieldTypeSymbol: 131 | case CDAFieldTypeText: 132 | if (value == [NSNull null]) { 133 | return @""; 134 | } 135 | 136 | if ([value isKindOfClass:[NSString class]]) { 137 | return value; 138 | } else { 139 | if (![value respondsToSelector:@selector(stringValue)]) { 140 | return @""; 141 | } 142 | return [value stringValue]; 143 | } 144 | 145 | case CDAFieldTypeNone: 146 | break; 147 | } 148 | 149 | NSAssert(false, @"Unhandled field type '%ld'", (long)self.type); 150 | return nil; 151 | } 152 | 153 | @end 154 | -------------------------------------------------------------------------------- /Tests/Data/Fixtures/ContentType-001.json: -------------------------------------------------------------------------------- 1 | { 2 | "fields": [ 3 | { 4 | "name": "Entry Type", 5 | "id": "entryType", 6 | "type": "Text" 7 | }, 8 | { 9 | "name": "Main Image", 10 | "id": "mainImage", 11 | "type": "Link", 12 | "linkType": "Asset" 13 | }, 14 | { 15 | "name": "Main Headline", 16 | "id": "mainHeadline", 17 | "type": "Text" 18 | }, 19 | { 20 | "name": "Main Body", 21 | "id": "mainBody", 22 | "type": "Text" 23 | }, 24 | { 25 | "name": "Should Show Teaser?", 26 | "id": "showTeaser", 27 | "type": "Boolean" 28 | }, 29 | { 30 | "name": "Teaser Headline", 31 | "id": "teaserHeadline", 32 | "type": "Text" 33 | }, 34 | { 35 | "name": "Teaser Text", 36 | "id": "teaserText", 37 | "type": "Text" 38 | }, 39 | { 40 | "name": "Teaser Text Long", 41 | "id": "teaserTextLong", 42 | "type": "Text" 43 | }, 44 | { 45 | "name": "Gallery Images", 46 | "id": "galleryImages", 47 | "type": "Array", 48 | "items": { 49 | "type": "Link", 50 | "linkType": "Asset" 51 | }, 52 | "disabled": true 53 | }, 54 | { 55 | "name": "Quiz Answer", 56 | "id": "quizAnswer", 57 | "type": "Text", 58 | "disabled": true 59 | }, 60 | { 61 | "name": "Channel", 62 | "id": "channel", 63 | "type": "Link", 64 | "linkType": "Entry" 65 | }, 66 | { 67 | "name": "Push Message", 68 | "id": "pushMessage", 69 | "type": "Text", 70 | "disabled": true 71 | }, 72 | { 73 | "name": "Push Message on Entry", 74 | "id": "pushMessageOnEntry", 75 | "type": "Boolean", 76 | "disabled": true 77 | }, 78 | { 79 | "name": "Push Message on Exit", 80 | "id": "pushMessageOnExit", 81 | "type": "Boolean", 82 | "disabled": true 83 | }, 84 | { 85 | "name": "UUID", 86 | "id": "uuid", 87 | "type": "Integer", 88 | "disabled": true 89 | }, 90 | { 91 | "name": "UUID Assgn", 92 | "id": "uuidAssgn", 93 | "type": "Link", 94 | "linkType": "Entry" 95 | }, 96 | { 97 | "name": "For Any Zone?", 98 | "id": "locationIsAll", 99 | "type": "Boolean" 100 | }, 101 | { 102 | "name": "Zones", 103 | "id": "multipleLocations", 104 | "type": "Array", 105 | "items": { 106 | "type": "Link", 107 | "linkType": "Entry", 108 | "validations": [ 109 | { 110 | "linkContentType": "Xvzf8HzuUweuwK0WuKuUq" 111 | } 112 | ] 113 | } 114 | }, 115 | { 116 | "name": "Beacon Major Value", 117 | "id": "beaconMajorValue", 118 | "type": "Integer", 119 | "disabled": true 120 | }, 121 | { 122 | "name": "Beacon Minor is All", 123 | "id": "beaconMinorIsAll", 124 | "type": "Boolean", 125 | "disabled": true 126 | }, 127 | { 128 | "name": "Beacon Minor Value", 129 | "id": "beaconMinorValue", 130 | "type": "Integer", 131 | "disabled": true 132 | }, 133 | { 134 | "name": "Beacon", 135 | "id": "beacon", 136 | "type": "Link", 137 | "linkType": "Entry", 138 | "disabled": false 139 | }, 140 | { 141 | "name": "BeaconProximity", 142 | "id": "beaconProximity", 143 | "type": "Text" 144 | }, 145 | { 146 | "name": "BeaconType", 147 | "id": "beaconType", 148 | "type": "Link", 149 | "linkType": "Entry", 150 | "disabled": true 151 | }, 152 | { 153 | "name": "BeaconEngagementType", 154 | "id": "beaconEngagementType", 155 | "type": "Text", 156 | "disabled": true 157 | }, 158 | { 159 | "name": "Location", 160 | "id": "location", 161 | "type": "Link", 162 | "linkType": "Entry", 163 | "disabled": true 164 | }, 165 | { 166 | "name": "Beacon Minor", 167 | "id": "beaconMinor", 168 | "type": "Text", 169 | "disabled": true 170 | }, 171 | { 172 | "name": "Location All", 173 | "id": "locationAll", 174 | "type": "Text", 175 | "disabled": true 176 | }, 177 | { 178 | "name": "TempMajorValue", 179 | "id": "tempMajorValue", 180 | "type": "Integer", 181 | "disabled": true 182 | }, 183 | { 184 | "name": "TempMinorValue", 185 | "id": "tempMinorValue", 186 | "type": "Integer", 187 | "disabled": true 188 | }, 189 | { 190 | "name": "Video URL", 191 | "id": "videoUrl", 192 | "type": "Text" 193 | } 194 | ], 195 | "name": "Main Post", 196 | "description": "Main posts - standard format with links to additional post types", 197 | "displayField": "mainHeadline", 198 | "sys": { 199 | "space": { 200 | "sys": { 201 | "type": "Link", 202 | "linkType": "Space", 203 | "id": "k7cpvwpbb4c0" 204 | } 205 | }, 206 | "type": "ContentType", 207 | "id": "2KZUNxrxfyYC8qO42o0oMQ", 208 | "revision": 4, 209 | "createdAt": "2014-09-28T20:03:07.383Z", 210 | "updatedAt": "2014-10-03T12:40:29.135Z", 211 | "locale": "en-US" 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /Tests/Data/ComplexSyncTests/AssetTestInitial.json: -------------------------------------------------------------------------------- 1 | { 2 | "sys": { 3 | "type": "Array" 4 | }, 5 | "items": [ 6 | { 7 | "sys": { 8 | "space": { 9 | "sys": { 10 | "type": "Link", 11 | "linkType": "Space", 12 | "id": "emh6o2ireilu" 13 | } 14 | }, 15 | "type": "Entry", 16 | "contentType": { 17 | "sys": { 18 | "type": "Link", 19 | "linkType": "ContentType", 20 | "id": "6bAvxqodl6s4MoKuWYkmqe" 21 | } 22 | }, 23 | "id": "1Wl5HnguK8CiaykiQAiGu6", 24 | "revision": 1, 25 | "createdAt": "2014-04-09T11:48:47.866Z", 26 | "updatedAt": "2014-04-09T11:48:47.866Z" 27 | }, 28 | "fields": { 29 | "title": { 30 | "en-US": "WithCategory" 31 | }, 32 | "body": { 33 | "en-US": "some text" 34 | }, 35 | "category": { 36 | "en-US": { 37 | "sys": { 38 | "type": "Link", 39 | "linkType": "Entry", 40 | "id": "6nRlw4CofeeICEYgIqaIIg" 41 | } 42 | } 43 | } 44 | } 45 | }, 46 | { 47 | "sys": { 48 | "space": { 49 | "sys": { 50 | "type": "Link", 51 | "linkType": "Space", 52 | "id": "emh6o2ireilu" 53 | } 54 | }, 55 | "type": "Entry", 56 | "contentType": { 57 | "sys": { 58 | "type": "Link", 59 | "linkType": "ContentType", 60 | "id": "51LZmvenywOe8aig28sCgY" 61 | } 62 | }, 63 | "id": "6nRlw4CofeeICEYgIqaIIg", 64 | "revision": 1, 65 | "createdAt": "2014-04-09T11:48:42.407Z", 66 | "updatedAt": "2014-04-09T11:48:42.407Z" 67 | }, 68 | "fields": { 69 | "name": { 70 | "en-US": "foobar" 71 | } 72 | } 73 | }, 74 | { 75 | "sys": { 76 | "space": { 77 | "sys": { 78 | "type": "Link", 79 | "linkType": "Space", 80 | "id": "emh6o2ireilu" 81 | } 82 | }, 83 | "type": "Entry", 84 | "contentType": { 85 | "sys": { 86 | "type": "Link", 87 | "linkType": "ContentType", 88 | "id": "51LZmvenywOe8aig28sCgY" 89 | } 90 | }, 91 | "id": "1YCXOBW1xu2uSkekCmwyqo", 92 | "revision": 1, 93 | "createdAt": "2014-04-09T11:44:21.359Z", 94 | "updatedAt": "2014-04-09T11:44:21.359Z" 95 | }, 96 | "fields": { 97 | "name": { 98 | "en-US": "Some Category" 99 | } 100 | } 101 | }, 102 | { 103 | "fields": { 104 | "title": { 105 | "en-US": "doge (changed)" 106 | }, 107 | "file": { 108 | "en-US": { 109 | "fileName": "vaa4by0.png", 110 | "contentType": "image/png", 111 | "details": { 112 | "image": { 113 | "width": 265, 114 | "height": 265 115 | }, 116 | "size": 103652 117 | }, 118 | "url": "//images.contentful.com/emh6o2ireilu/6koKmTXVzUquae6ewQQ8Eu/5f79cef98e3feb466c30f0b38bec0359/vaa4by0.png" 119 | } 120 | } 121 | }, 122 | "sys": { 123 | "space": { 124 | "sys": { 125 | "type": "Link", 126 | "linkType": "Space", 127 | "id": "emh6o2ireilu" 128 | } 129 | }, 130 | "type": "Asset", 131 | "id": "6koKmTXVzUquae6ewQQ8Eu", 132 | "revision": 2, 133 | "createdAt": "2014-03-25T11:08:25.412Z", 134 | "updatedAt": "2014-03-26T10:37:20.069Z" 135 | } 136 | }, 137 | { 138 | "sys": { 139 | "space": { 140 | "sys": { 141 | "type": "Link", 142 | "linkType": "Space", 143 | "id": "emh6o2ireilu" 144 | } 145 | }, 146 | "type": "Entry", 147 | "contentType": { 148 | "sys": { 149 | "type": "Link", 150 | "linkType": "ContentType", 151 | "id": "6bAvxqodl6s4MoKuWYkmqe" 152 | } 153 | }, 154 | "id": "4upDPGUMMEkG8w8UUs2OiO", 155 | "revision": 2, 156 | "createdAt": "2014-03-25T11:00:43.065Z", 157 | "updatedAt": "2014-03-26T10:37:09.128Z" 158 | }, 159 | "fields": { 160 | "title": { 161 | "en-US": "Test (changed)" 162 | }, 163 | "body": { 164 | "en-US": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 165 | } 166 | } 167 | } 168 | ], 169 | "nextSyncUrl": "http://cdn.contentful.com/spaces/emh6o2ireilu/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdyZ7T8Obw5Inwoh6Tk7Cq8KUDcK7w5ssw6vCgjLDjk9Hwr3DusOzw7XCo8OIwo3CicK5SBkqCcK7woDDhSjCkMOGw7rCqMOtE1V1L3LDq8KIck_DssK4K8OBe0vDn0vDrXjDkMOf" 170 | } 171 | --------------------------------------------------------------------------------