Remove encoder requirement to almost all Open API Node constructors. Made a new protocol for the few places where an encoder did need to be passed in.

This commit is contained in:
Mathew Polzin
2019-01-23 23:21:16 -08:00
parent 951c04ad44
commit 952fe8ba7e
6 changed files with 167 additions and 119 deletions
@@ -9,8 +9,8 @@ import JSONAPI
import Foundation
extension Includes: OpenAPINodeType where I: OpenAPINodeType {
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
let includeNode = try I.openAPINode(using: encoder)
public static func openAPINode() throws -> JSONNode {
let includeNode = try I.openAPINode()
return .array(.init(format: .generic,
required: true),
@@ -20,113 +20,113 @@ extension Includes: OpenAPINodeType where I: OpenAPINodeType {
}
extension Include0: OpenAPINodeType {
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
public static func openAPINode() throws -> JSONNode {
throw OpenAPITypeError.invalidNode
}
}
extension Include1: OpenAPINodeType where A: OpenAPINodeType {
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
return try .one(of: [A.openAPINode(using: encoder)])
public static func openAPINode() throws -> JSONNode {
return try .one(of: [A.openAPINode()])
}
}
extension Include2: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType {
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
public static func openAPINode() throws -> JSONNode {
return try .one(of: [
A.openAPINode(using: encoder),
B.openAPINode(using: encoder)
A.openAPINode(),
B.openAPINode()
])
}
}
extension Include3: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType {
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
public static func openAPINode() throws -> JSONNode {
return try .one(of: [
A.openAPINode(using: encoder),
B.openAPINode(using: encoder),
C.openAPINode(using: encoder)
A.openAPINode(),
B.openAPINode(),
C.openAPINode()
])
}
}
extension Include4: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType {
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
public static func openAPINode() throws -> JSONNode {
return try .one(of: [
A.openAPINode(using: encoder),
B.openAPINode(using: encoder),
C.openAPINode(using: encoder),
D.openAPINode(using: encoder)
A.openAPINode(),
B.openAPINode(),
C.openAPINode(),
D.openAPINode()
])
}
}
extension Include5: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType, E: OpenAPINodeType {
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
public static func openAPINode() throws -> JSONNode {
return try .one(of: [
A.openAPINode(using: encoder),
B.openAPINode(using: encoder),
C.openAPINode(using: encoder),
D.openAPINode(using: encoder),
E.openAPINode(using: encoder)
A.openAPINode(),
B.openAPINode(),
C.openAPINode(),
D.openAPINode(),
E.openAPINode()
])
}
}
extension Include6: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType, E: OpenAPINodeType, F: OpenAPINodeType {
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
public static func openAPINode() throws -> JSONNode {
return try .one(of: [
A.openAPINode(using: encoder),
B.openAPINode(using: encoder),
C.openAPINode(using: encoder),
D.openAPINode(using: encoder),
E.openAPINode(using: encoder),
F.openAPINode(using: encoder)
A.openAPINode(),
B.openAPINode(),
C.openAPINode(),
D.openAPINode(),
E.openAPINode(),
F.openAPINode()
])
}
}
extension Include7: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType, E: OpenAPINodeType, F: OpenAPINodeType, G: OpenAPINodeType {
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
public static func openAPINode() throws -> JSONNode {
return try .one(of: [
A.openAPINode(using: encoder),
B.openAPINode(using: encoder),
C.openAPINode(using: encoder),
D.openAPINode(using: encoder),
E.openAPINode(using: encoder),
F.openAPINode(using: encoder),
G.openAPINode(using: encoder)
A.openAPINode(),
B.openAPINode(),
C.openAPINode(),
D.openAPINode(),
E.openAPINode(),
F.openAPINode(),
G.openAPINode()
])
}
}
extension Include8: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType, E: OpenAPINodeType, F: OpenAPINodeType, G: OpenAPINodeType, H: OpenAPINodeType {
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
public static func openAPINode() throws -> JSONNode {
return try .one(of: [
A.openAPINode(using: encoder),
B.openAPINode(using: encoder),
C.openAPINode(using: encoder),
D.openAPINode(using: encoder),
E.openAPINode(using: encoder),
F.openAPINode(using: encoder),
G.openAPINode(using: encoder),
H.openAPINode(using: encoder)
A.openAPINode(),
B.openAPINode(),
C.openAPINode(),
D.openAPINode(),
E.openAPINode(),
F.openAPINode(),
G.openAPINode(),
H.openAPINode()
])
}
}
extension Include9: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType, E: OpenAPINodeType, F: OpenAPINodeType, G: OpenAPINodeType, H: OpenAPINodeType, I: OpenAPINodeType {
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
public static func openAPINode() throws -> JSONNode {
return try .one(of: [
A.openAPINode(using: encoder),
B.openAPINode(using: encoder),
C.openAPINode(using: encoder),
D.openAPINode(using: encoder),
E.openAPINode(using: encoder),
F.openAPINode(using: encoder),
G.openAPINode(using: encoder),
H.openAPINode(using: encoder),
I.openAPINode(using: encoder)
A.openAPINode(),
B.openAPINode(),
C.openAPINode(),
D.openAPINode(),
E.openAPINode(),
F.openAPINode(),
G.openAPINode(),
H.openAPINode(),
I.openAPINode()
])
}
}
@@ -13,38 +13,38 @@ private protocol _Optional {}
extension Optional: _Optional {}
extension Attribute: OpenAPINodeType where RawValue: OpenAPINodeType {
static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
static public func openAPINode() throws -> JSONNode {
// If the RawValue is not required, we actually consider it
// nullable. To be not required is for the Attribute itself
// to be optional.
if try !RawValue.openAPINode(using: encoder).required {
return try RawValue.openAPINode(using: encoder).requiredNode().nullableNode()
if try !RawValue.openAPINode().required {
return try RawValue.openAPINode().requiredNode().nullableNode()
}
return try RawValue.openAPINode(using: encoder)
return try RawValue.openAPINode()
}
}
extension Attribute: RawOpenAPINodeType where RawValue: RawRepresentable, RawValue.RawValue: OpenAPINodeType {
static public func rawOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode {
static public func rawOpenAPINode() throws -> JSONNode {
// If the RawValue is not required, we actually consider it
// nullable. To be not required is for the Attribute itself
// to be optional.
if try !RawValue.RawValue.openAPINode(using: encoder).required {
return try RawValue.RawValue.openAPINode(using: encoder).requiredNode().nullableNode()
if try !RawValue.RawValue.openAPINode().required {
return try RawValue.RawValue.openAPINode().requiredNode().nullableNode()
}
return try RawValue.RawValue.openAPINode(using: encoder)
return try RawValue.RawValue.openAPINode()
}
}
extension Attribute: WrappedRawOpenAPIType where RawValue: RawOpenAPINodeType {
public static func wrappedOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode {
public static func wrappedOpenAPINode() throws -> JSONNode {
// If the RawValue is not required, we actually consider it
// nullable. To be not required is for the Attribute itself
// to be optional.
if try !RawValue.rawOpenAPINode(using: encoder).required {
return try RawValue.rawOpenAPINode(using: encoder).requiredNode().nullableNode()
if try !RawValue.rawOpenAPINode().required {
return try RawValue.rawOpenAPINode().requiredNode().nullableNode()
}
return try RawValue.rawOpenAPINode(using: encoder)
return try RawValue.rawOpenAPINode()
}
}
@@ -61,14 +61,14 @@ extension Attribute: AnyWrappedJSONCaseIterable where RawValue: AnyJSONCaseItera
}
extension TransformedAttribute: OpenAPINodeType where RawValue: OpenAPINodeType {
static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
static public func openAPINode() throws -> JSONNode {
// If the RawValue is not required, we actually consider it
// nullable. To be not required is for the Attribute itself
// to be optional.
if try !RawValue.openAPINode(using: encoder).required {
return try RawValue.openAPINode(using: encoder).requiredNode().nullableNode()
if try !RawValue.openAPINode().required {
return try RawValue.openAPINode().requiredNode().nullableNode()
}
return try RawValue.openAPINode(using: encoder)
return try RawValue.openAPINode()
}
}
@@ -96,7 +96,7 @@ extension ToOneRelationship: OpenAPINodeType {
// Will use "enum" with one possible value for now.
// TODO: metadata & links
static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
static public func openAPINode() throws -> JSONNode {
let nullable = Identifiable.self is _Optional.Type
return .object(.init(format: .generic,
required: true),
@@ -111,7 +111,7 @@ extension ToManyRelationship: OpenAPINodeType {
// Will use "enum" with one possible value for now.
// TODO: metadata & links
static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
static public func openAPINode() throws -> JSONNode {
return .object(.init(format: .generic,
required: true),
.init(properties: [
@@ -122,7 +122,7 @@ extension ToManyRelationship: OpenAPINodeType {
}
}
extension Entity: OpenAPINodeType where Description.Attributes: Sampleable, Description.Relationships: Sampleable {
extension Entity: OpenAPIEncodedNodeType, OpenAPINodeType where Description.Attributes: Sampleable, Description.Relationships: Sampleable {
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
// NOTE: const for json `type` not supported by OpenAPI 3.0
// Will use "enum" with one possible value for now.
@@ -165,13 +165,13 @@ extension Entity: OpenAPINodeType where Description.Attributes: Sampleable, Desc
}
}
extension SingleResourceBody: OpenAPINodeType where Entity: OpenAPINodeType {
extension SingleResourceBody: OpenAPIEncodedNodeType, OpenAPINodeType where Entity: OpenAPIEncodedNodeType {
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
return try Entity.openAPINode(using: encoder)
}
}
extension ManyResourceBody: OpenAPINodeType where Entity: OpenAPINodeType {
extension ManyResourceBody: OpenAPIEncodedNodeType, OpenAPINodeType where Entity: OpenAPIEncodedNodeType {
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
return .array(.init(format: .generic,
required: true),
@@ -179,7 +179,7 @@ extension ManyResourceBody: OpenAPINodeType where Entity: OpenAPINodeType {
}
}
extension Document: OpenAPINodeType where PrimaryResourceBody: OpenAPINodeType, IncludeType: OpenAPINodeType {
extension Document: OpenAPIEncodedNodeType, OpenAPINodeType where PrimaryResourceBody: OpenAPIEncodedNodeType, IncludeType: OpenAPINodeType {
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
// TODO: metadata, links, api description, errors
// TODO: represent data and errors as the two distinct possible outcomes
@@ -190,7 +190,7 @@ extension Document: OpenAPINodeType where PrimaryResourceBody: OpenAPINodeType,
let includeNode: JSONNode?
do {
includeNode = try Includes<Include>.openAPINode(using: encoder)
includeNode = try Includes<Include>.openAPINode()
} catch let err as OpenAPITypeError {
guard err == .invalidNode else {
throw err
@@ -13,12 +13,25 @@ import Foundation
/// Anything conforming to `OpenAPINodeType` can provide an
/// OpenAPI schema representing itself.
public protocol OpenAPINodeType {
static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode
static func openAPINode() throws -> JSONNode
}
extension OpenAPINodeType where Self: Sampleable, Self: Encodable {
public static func openAPINodeWithExample(using encoder: JSONEncoder = JSONEncoder()) throws -> JSONNode {
return try openAPINode(using: encoder).with(example: Self.successSample ?? Self.sample, using: encoder)
return try openAPINode().with(example: Self.successSample ?? Self.sample, using: encoder)
}
}
/// Anything conforming to `OpenAPIEncodedNodeType` can provide an
/// OpenAPI schema representing itself but it may need an Encoder
/// to do its job.
public protocol OpenAPIEncodedNodeType: OpenAPINodeType {
static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode
}
extension OpenAPIEncodedNodeType {
public static func openAPINode() throws -> JSONNode {
return try openAPINode(using: JSONEncoder())
}
}
@@ -29,7 +42,7 @@ extension OpenAPINodeType where Self: Sampleable, Self: Encodable {
/// different schema. The "different" conditions have to do
/// with Raw Representability, hence the name of this protocol.
public protocol RawOpenAPINodeType {
static func rawOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode
static func rawOpenAPINode() throws -> JSONNode
}
/// Anything conforming to `RawOpenAPINodeType` can provide an
@@ -39,7 +52,7 @@ public protocol RawOpenAPINodeType {
/// different schema. The "different" conditions have to do
/// with Optionality, hence the name of this protocol.
public protocol WrappedRawOpenAPIType {
static func wrappedOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode
static func wrappedOpenAPINode() throws -> JSONNode
}
/// Anything conforming to `RawOpenAPINodeType` can provide an
@@ -52,7 +65,7 @@ public protocol DoubleWrappedRawOpenAPIType {
// NOTE: This is definitely a rabbit hole... hopefully I
// will realize I've been missing something obvious
// and dig my way back out at some point...
static func wrappedOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode
static func wrappedOpenAPINode() throws -> JSONNode
}
/// Anything conforming to `AnyJSONCaseIterable` can provide a
@@ -282,15 +295,14 @@ public enum JSONNode: Equatable {
nullable: Bool = false,
// constantValue: Format.SwiftType? = nil,
allowedValues: [AnyCodable]? = nil,
example: AnyCodable? = nil,
using encoder: JSONEncoder = JSONEncoder()) {
example: (codable: AnyCodable, encoder: JSONEncoder)? = nil) {
self.format = format
self.required = required
self.nullable = nullable
// self.constantValue = constantValue
self.allowedValues = allowedValues
self.example = example
.flatMap { try? encoder.encode($0)}
.flatMap { try? $0.encoder.encode($0.codable)}
.flatMap { String(data: $0, encoding: .utf8) }
}
@@ -331,13 +343,13 @@ public enum JSONNode: Equatable {
}
/// Return this context with the given example
public func with(example: AnyCodable) -> Context {
public func with(example: AnyCodable, using encoder: JSONEncoder) -> Context {
return .init(format: format,
required: required,
nullable: nullable,
// constantValue: constantValue,
allowedValues: allowedValues,
example: example)
example: (codable: example, encoder: encoder))
}
}
@@ -562,17 +574,17 @@ public enum JSONNode: Equatable {
switch self {
case .boolean(let context):
return .boolean(context.with(example: example))
return .boolean(context.with(example: example, using: encoder))
case .object(let contextA, let contextB):
return .object(contextA.with(example: example), contextB)
return .object(contextA.with(example: example, using: encoder), contextB)
case .array(let contextA, let contextB):
return .array(contextA.with(example: example), contextB)
return .array(contextA.with(example: example, using: encoder), contextB)
case .number(let context, let contextB):
return .number(context.with(example: example), contextB)
return .number(context.with(example: example, using: encoder), contextB)
case .integer(let context, let contextB):
return .integer(context.with(example: example), contextB)
return .integer(context.with(example: example, using: encoder), contextB)
case .string(let context, let contextB):
return .string(context.with(example: example), contextB)
return .string(context.with(example: example, using: encoder), contextB)
case .all, .one, .any, .not:
return self
}
@@ -33,26 +33,26 @@ Any object:
**/
extension Optional: OpenAPINodeType where Wrapped: OpenAPINodeType {
static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
return try Wrapped.openAPINode(using: encoder).optionalNode()
static public func openAPINode() throws -> JSONNode {
return try Wrapped.openAPINode().optionalNode()
}
}
extension Optional: RawOpenAPINodeType where Wrapped: RawRepresentable, Wrapped.RawValue: OpenAPINodeType {
static public func rawOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode {
return try Wrapped.RawValue.openAPINode(using: encoder).optionalNode()
static public func rawOpenAPINode() throws -> JSONNode {
return try Wrapped.RawValue.openAPINode().optionalNode()
}
}
extension Optional: WrappedRawOpenAPIType where Wrapped: RawOpenAPINodeType {
static public func wrappedOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode {
return try Wrapped.rawOpenAPINode(using: encoder).optionalNode()
static public func wrappedOpenAPINode() throws -> JSONNode {
return try Wrapped.rawOpenAPINode().optionalNode()
}
}
extension Optional: DoubleWrappedRawOpenAPIType where Wrapped: WrappedRawOpenAPIType {
static public func wrappedOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode {
return try Wrapped.wrappedOpenAPINode(using: encoder).optionalNode()
static public func wrappedOpenAPINode() throws -> JSONNode {
return try Wrapped.wrappedOpenAPINode().optionalNode()
}
}
@@ -63,7 +63,7 @@ extension Optional: AnyJSONCaseIterable where Wrapped: CaseIterable, Wrapped: Co
}
extension String: OpenAPINodeType {
static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
static public func openAPINode() throws -> JSONNode {
return .string(.init(format: .generic,
required: true),
.init())
@@ -71,22 +71,22 @@ extension String: OpenAPINodeType {
}
extension Bool: OpenAPINodeType {
static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
static public func openAPINode() throws -> JSONNode {
return .boolean(.init(format: .generic,
required: true))
}
}
extension Array: OpenAPINodeType where Element: OpenAPINodeType {
static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
static public func openAPINode() throws -> JSONNode {
return .array(.init(format: .generic,
required: true),
.init(items: try Element.openAPINode(using: encoder)))
.init(items: try Element.openAPINode()))
}
}
extension Double: OpenAPINodeType {
static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
static public func openAPINode() throws -> JSONNode {
return .number(.init(format: .double,
required: true),
.init())
@@ -94,7 +94,7 @@ extension Double: OpenAPINodeType {
}
extension Float: OpenAPINodeType {
static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
static public func openAPINode() throws -> JSONNode {
return .number(.init(format: .float,
required: true),
.init())
@@ -102,7 +102,7 @@ extension Float: OpenAPINodeType {
}
extension Int: OpenAPINodeType {
static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
static public func openAPINode() throws -> JSONNode {
return .integer(.init(format: .generic,
required: true),
.init())
@@ -110,7 +110,7 @@ extension Int: OpenAPINodeType {
}
extension Int32: OpenAPINodeType {
static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
static public func openAPINode() throws -> JSONNode {
return .integer(.init(format: .int32,
required: true),
.init())
@@ -118,7 +118,7 @@ extension Int32: OpenAPINodeType {
}
extension Int64: OpenAPINodeType {
static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
static public func openAPINode() throws -> JSONNode {
return .integer(.init(format: .int64,
required: true),
.init())
@@ -69,16 +69,16 @@ extension Sampleable {
let maybeOpenAPINode: JSONNode? = try {
switch type(of: child.value) {
case let valType as OpenAPINodeType.Type:
return try valType.openAPINode(using: encoder)
return try valType.openAPINode()
case let valType as RawOpenAPINodeType.Type:
return try valType.rawOpenAPINode(using: encoder)
return try valType.rawOpenAPINode()
case let valType as WrappedRawOpenAPIType.Type:
return try valType.wrappedOpenAPINode(using: encoder)
return try valType.wrappedOpenAPINode()
case let valType as DoubleWrappedRawOpenAPIType.Type:
return try valType.wrappedOpenAPINode(using: encoder)
return try valType.wrappedOpenAPINode()
default:
return nil
@@ -6,15 +6,23 @@
//
import XCTest
import SwiftCheck
import JSONAPI
import JSONAPIOpenAPI
class JSONAPIDocumentOpenAPITests: XCTestCase {
func test_SingleResourceDocument() {
let node = try! SingleEntityDocument.openAPINode()
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
dateFormatter.locale = Locale(identifier: "en_US")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.dateEncodingStrategy = .formatted(dateFormatter)
let node = try! SingleEntityDocument.openAPINodeWithExample(using: encoder)
print(String(data: try! encoder.encode(node), encoding: .utf8)!)
}
@@ -27,9 +35,11 @@ extension JSONAPIDocumentOpenAPITests {
struct Attributes: JSONAPI.Attributes, Sampleable {
let name: Attribute<String>
let date: Attribute<Date>
static var sample: Attributes {
return .init(name: "hello world")
return .init(name: "hello world",
date: .init(value: Date()))
}
}
@@ -40,3 +50,29 @@ extension JSONAPIDocumentOpenAPITests {
typealias SingleEntityDocument = Document<SingleResourceBody<TestEntity>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>
}
extension Id: Sampleable where RawType == String {
public static var sample: Id<RawType, IdentifiableType> {
return .init(rawValue: String.arbitrary.generate)
}
}
extension JSONAPI.Entity: Sampleable where Description.Attributes: Sampleable, Description.Relationships: Sampleable, MetaType: Sampleable, LinksType: Sampleable, EntityRawIdType == String {
public static var sample: JSONAPI.Entity<Description, MetaType, LinksType, EntityRawIdType> {
return JSONAPI.Entity(id: .sample,
attributes: .sample,
relationships: .sample,
meta: .sample,
links: .sample)
}
}
extension Document: Sampleable where PrimaryResourceBody: Sampleable, MetaType: Sampleable, LinksType: Sampleable, IncludeType: Sampleable, APIDescription: Sampleable, Error: Sampleable {
public static var sample: Document<PrimaryResourceBody, MetaType, LinksType, IncludeType, APIDescription, Error> {
return Document(apiDescription: .sample,
body: .sample,
includes: .sample,
meta: .sample,
links: .sample)
}
}