feat: add partial Mastodon entities

This commit is contained in:
CMK 2021-01-28 14:52:35 +08:00
parent d29f473898
commit f8718510a6
22 changed files with 679 additions and 134 deletions

View File

@ -8,11 +8,11 @@
import Foundation
extension Mastodon.API.Error {
public enum MastodonAPIError: Swift.Error {
case generic(errorResponse: Mastodon.Response.ErrorResponse)
public enum MastodonError: Swift.Error {
case generic(error: Mastodon.Entity.Error)
init(errorResponse: Mastodon.Response.ErrorResponse) {
self = .generic(errorResponse: errorResponse)
init(error: Mastodon.Entity.Error) {
self = .generic(error: error)
}
}
}

View File

@ -12,23 +12,23 @@ extension Mastodon.API {
public struct Error: Swift.Error {
public var httpResponseStatus: HTTPResponseStatus
public var mastodonAPIError: MastodonAPIError?
public var mastodonError: MastodonError?
init(
httpResponseStatus: HTTPResponseStatus,
mastodonAPIError: Mastodon.API.Error.MastodonAPIError?
mastodonError: Mastodon.API.Error.MastodonError?
) {
self.httpResponseStatus = httpResponseStatus
self.mastodonAPIError = mastodonAPIError
self.mastodonError = mastodonError
}
init(
httpResponseStatus: HTTPResponseStatus,
errorResponse: Mastodon.Response.ErrorResponse
error: Mastodon.Entity.Error
) {
self.init(
httpResponseStatus: httpResponseStatus,
mastodonAPIError: MastodonAPIError(errorResponse: errorResponse)
mastodonError: MastodonError(error: error)
)
}

View File

@ -18,7 +18,7 @@ extension Mastodon.API.App {
session: URLSession,
domain: String,
query: CreateQuery
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.API.App.Application>, Error> {
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Application>, Error> {
let request = Mastodon.API.request(
url: appEndpointURL(domain: domain),
query: query,
@ -26,7 +26,7 @@ extension Mastodon.API.App {
)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
let value = try Mastodon.API.decode(type: Application.self, from: data, response: response)
let value = try Mastodon.API.decode(type: Mastodon.Entity.Application.self, from: data, response: response)
return Mastodon.Response.Content(value: value, response: response)
}
.eraseToAnyPublisher()
@ -36,29 +36,6 @@ extension Mastodon.API.App {
extension Mastodon.API.App {
public struct Application: Codable {
public let id: String
public let name: String
public let website: String?
public let redirectURI: String
public let clientID: String
public let clientSecret: String
public let vapidKey: String
enum CodingKeys: String, CodingKey {
case id
case name
case website
case redirectURI = "redirect_uri"
case clientID = "client_id"
case clientSecret = "client_secret"
case vapidKey = "vapid_key"
}
}
public struct CreateQuery: Codable, PostQuery {
public let clientName: String
public let redirectURIs: String

View File

@ -11,11 +11,23 @@ import enum NIOHTTP1.HTTPResponseStatus
extension Mastodon.API {
static let timeoutInterval: TimeInterval = 10
static let httpHeaderDateFormatter: ISO8601DateFormatter = {
var formatter = ISO8601DateFormatter()
formatter.formatOptions.insert(.withFractionalSeconds)
return formatter
}()
static let fractionalSecondsPreciseISO8601Formatter: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions.insert(.withFractionalSeconds)
return formatter
}()
static let fullDatePreciseISO8601Formatter: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withFullDate, .withDashSeparatorInDate]
return formatter
}()
static let encoder: JSONEncoder = {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
@ -27,9 +39,11 @@ extension Mastodon.API {
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
let formatter = ISO8601DateFormatter()
formatter.formatOptions.insert(.withFractionalSeconds)
if let date = formatter.date(from: string) {
if let date = fractionalSecondsPreciseISO8601Formatter.date(from: string) {
return date
}
if let date = fullDatePreciseISO8601Formatter.date(from: string) {
return date
}
@ -116,11 +130,11 @@ extension Mastodon.API {
}
let httpResponseStatus = HTTPResponseStatus(statusCode: httpURLResponse.statusCode)
if let errorResponse = try? Mastodon.API.decoder.decode(Mastodon.Response.ErrorResponse.self, from: data) {
throw Mastodon.API.Error(httpResponseStatus: httpResponseStatus, errorResponse: errorResponse)
if let error = try? Mastodon.API.decoder.decode(Mastodon.Entity.Error.self, from: data) {
throw Mastodon.API.Error(httpResponseStatus: httpResponseStatus, error: error)
}
throw Mastodon.API.Error(httpResponseStatus: httpResponseStatus, mastodonAPIError: nil)
throw Mastodon.API.Error(httpResponseStatus: httpResponseStatus, mastodonError: nil)
}
}

View File

@ -0,0 +1,89 @@
//
// Mastodon+Entity+Account.swift
//
//
// Created by MainasuK Cirno on 2021/1/27.
//
import Foundation
extension Mastodon.Entity {
// FIXME: prefer `Account`. `User` will be deprecated
public typealias User = Account
/// Account
///
/// - Since: 0.1.0
/// - Version: 3.3.0
/// # Last Update
/// 2021/1/28
/// # Reference
/// [Document](https://docs.joinmastodon.org/entities/account/)
public class Account: Codable {
public typealias ID = String
// Base
public let id: ID
public let username: String
public let acct: String
public let url: String
// Display
public let displayName: String
public let note: String
public let avatar: String
public let avatarStatic: String?
public let header: String
public let headerStatic: String?
public let locked: Bool
public let emojis: [Emoji]?
public let discoverable: Bool?
// Statistical
public let createdAt: Date
public let lastStatusAt: Date?
public let statusesCount: Int
public let followersCount: Int
public let followingCount: Int
public let moved: User?
public let fields: [Field]?
public let bot: Bool?
public let source: Source?
public let suspended: Bool?
public let muteExpiresAt: Date?
enum CodingKeys: String, CodingKey {
case id
case username
case acct
case url
case displayName = "display_name"
case note
case avatar
case avatarStatic = "avatar_static"
case header
case headerStatic = "header_static"
case locked
case emojis
case discoverable
case createdAt = "created_at"
case lastStatusAt = "last_status_at"
case statusesCount = "statuses_count"
case followersCount = "followers_count"
case followingCount = "following_count"
case moved
case fields
case bot
case source
case suspended
case muteExpiresAt = "mute_expires_at"
}
}
}

View File

@ -0,0 +1,40 @@
//
// Mastodon+Entity+Application.swift
//
//
// Created by MainasuK Cirno on 2021/1/28.
//
import Foundation
extension Mastodon.Entity {
/// Application
///
/// - Since: 0.9.9
/// - Version: 3.3.0
/// # Last Update
/// 2021/1/28
/// # Reference
/// [Document](https://docs.joinmastodon.org/entities/application/)
public struct Application: Codable {
public let name: String
public let website: String?
public let vapidKey: String?
// Client
public let redirectURI: String? // undocumented
public let clientID: String?
public let clientSecret: String?
enum CodingKeys: String, CodingKey {
case name
case website
case vapidKey = "vapid_key"
case redirectURI = "redirect_uri"
case clientID = "client_id"
case clientSecret = "client_secret"
}
}
}

View File

@ -0,0 +1,63 @@
//
// Mastodon+Entity+Card.swift
//
//
// Created by MainasuK Cirno on 2021/1/28.
//
import Foundation
extension Mastodon.Entity {
/// Card
///
/// - Since: 1.0.0
/// - Version: 3.3.0
/// # Last Update
/// 2021/1/28
/// # Reference
/// [Document](https://docs.joinmastodon.org/entities/card/)
public struct Card: Codable {
// Base
public let url: String
public let title: String
public let description: String
public let type: Type?
public let authorName: String?
public let authorURL: String?
public let providerName: String?
public let providerURL: String?
public let html: String?
public let width: Int?
public let height: Int?
public let image: String?
public let embedURL: String?
public let blurhash: String?
enum CodingKeys: String, CodingKey {
case url
case title
case description
case type
case authorName = "author_name"
case authorURL = "author_url"
case providerName = "provider_name"
case providerURL = "provider_url"
case html
case width
case height
case image
case embedURL = "embed_url"
case blurhash
}
}
}
extension Mastodon.Entity.Card {
public enum `Type`: String, Codable {
case link
case photo
case video
case rich
}
}

View File

@ -0,0 +1,35 @@
//
// Mastodon+Entity+Emoji.swift
//
//
// Created by MainasuK Cirno on 2021/1/28.
//
import Foundation
extension Mastodon.Entity {
/// Emoji
///
/// - Since: 2.0.0
/// - Version: 3.3.0
/// # Last Update
/// 2021/1/28
/// # Reference
/// [Document](https://docs.joinmastodon.org/entities/emoji/)
public struct Emoji: Codable {
public let shortcode: String
public let url: String
public let staticURL: String
public let visibleInPicker: Bool
public let category: String?
enum CodingKeys: String, CodingKey {
case shortcode
case url
case staticURL = "static_url"
case visibleInPicker = "visible_in_picker"
case category
}
}
}

View File

@ -0,0 +1,28 @@
//
// Mastodon+Entity+Error.swift
//
//
// Created by MainasuK Cirno on 2021/1/28.
//
import Foundation
extension Mastodon.Entity {
/// Error
///
/// - Since: 0.6.0
/// - Version: 3.3.0
/// # Last Update
/// 2021/1/28
/// # Reference
/// [Document](https://docs.joinmastodon.org/entities/error/)
public struct Error: Codable {
public let error: String
public let errorDescription: String?
enum CodingKeys: String, CodingKey {
case error
case errorDescription = "error_description"
}
}
}

View File

@ -0,0 +1,31 @@
//
// Mastodon+Entity+Field.swift
//
//
// Created by MainasuK Cirno on 2021/1/28.
//
import Foundation
extension Mastodon.Entity {
/// Field
///
/// - Since: 2.4.0
/// - Version: 3.3.0
/// # Last Update
/// 2021/1/28
/// # Reference
/// [Document](https://docs.joinmastodon.org/entities/field/)
public struct Field: Codable {
public let name: String
public let value: String
public let verifiedAt: Date?
enum CodingKeys: String, CodingKey {
case name
case value
case verifiedAt = "verified_at"
}
}
}

View File

@ -0,0 +1,25 @@
//
// Mastodon+Entity+History.swift
//
//
// Created by MainasuK Cirno on 2021/1/28.
//
import Foundation
extension Mastodon.Entity {
/// History
///
/// - Since: 2.4.1
/// - Version: 3.3.0
/// # Last Update
/// 2021/1/28
/// # Reference
/// [Document](https://docs.joinmastodon.org/entities/history/)
public struct History: Codable {
/// UNIX timestamp on midnight of the given day
public let day: Date
public let uses: Int
public let accounts: Int
}
}

View File

@ -8,13 +8,21 @@
import Foundation
extension Mastodon.Entity {
/// Instance
///
/// - Since: 1.1.0
/// - Version: 3.3.0
/// # Last Update
/// 2021/1/28
/// # Reference
/// [Document](https://docs.joinmastodon.org/entities/instance/)
public struct Instance: Codable {
public let uri: String?
public let title: String?
public let description: String?
public let uri: String
public let title: String
public let description: String
public let shortDescription: String?
public let email: String?
public let email: String
public let version: String?
public let languages: [String]? // (ISO 639 Part 1-5 language codes)
public let registrations: Bool?

View File

@ -0,0 +1,37 @@
//
// Mastodon+Entity+Mention.swift
//
//
// Created by MainasuK Cirno on 2021/1/28.
//
import Foundation
extension Mastodon.Entity {
/// Mention
///
/// - Since: 0.6.0
/// - Version: 3.3.0
/// # Last Update
/// 2021/1/28
/// # Reference
/// [Document](https://docs.joinmastodon.org/entities/mention/)
public struct Mention: Codable {
public typealias ID = String
public let id: ID
public let username: String
public let acct: String
public let url: String
enum CodingKeys: String, CodingKey {
case id
case username
case acct
case url
}
}
}

View File

@ -0,0 +1,62 @@
//
// Mastodon+Entity+Poll.swift
//
//
// Created by MainasuK Cirno on 2021/1/28.
//
import Foundation
extension Mastodon.Entity {
/// Poll
///
/// - Since: 2.8.0
/// - Version: 3.3.0
/// # Last Update
/// 2021/1/28
/// # Reference
/// [Document](https://docs.joinmastodon.org/entities/poll/)
public struct Poll: Codable {
public typealias ID = String
public let id: ID
public let expiresAt: Date
public let expired: Bool
public let multiple: Bool
public let votesCount: Int
/// nil if `multiple` is false
public let votersCount: Int?
/// nil if no current user
public let voted: Bool?
/// nil if no current user
public let ownVotes: [Int]?
public let options: [Option]
enum CodingKeys: String, CodingKey {
case id
case expiresAt = "expires_at"
case expired
case multiple
case votesCount = "votes_count"
case votersCount = "voters_count"
case voted
case ownVotes = "own_votes"
case options
}
}
}
extension Mastodon.Entity.Poll {
public struct Option: Codable {
public let title: String
/// nil if results are not published yet
public let votesCount: Int?
public let emojis: [Mastodon.Entity.Emoji]
enum CodingKeys: String, CodingKey {
case title
case votesCount = "votes_count"
case emojis
}
}
}

View File

@ -0,0 +1,49 @@
//
// Mastodon+Entity+Source.swift
//
//
// Created by MainasuK Cirno on 2021/1/28.
//
import Foundation
extension Mastodon.Entity {
/// Source
///
/// - Since: 1.5.0
/// - Version: 3.3.0
/// # Last Update
/// 2021/1/28
/// # Reference
/// [Document](https://docs.joinmastodon.org/entities/source/)
public struct Source: Codable {
// Base
public let note: String
public let fields: [Field]?
public let privacy: Privacy?
public let sensitive: Bool?
public let language: String? // (ISO 639-1 language two-letter code)
public let followRequestsCount: String
enum CodingKeys: String, CodingKey {
case note
case fields
case privacy
case sensitive
case language
case followRequestsCount = "follow_requests_count"
}
}
}
extension Mastodon.Entity.Source {
public enum Privacy: String, Codable {
case `public`
case unlisted
case `private`
case direct
}
}

View File

@ -0,0 +1,112 @@
//
// Mastodon+Entity+Toot.swift
//
//
// Created by MainasuK Cirno on 2021/1/27.
//
import Foundation
extension Mastodon.Entity {
// FIXME: prefer `Status`. `Toot` will be deprecated
public typealias Toot = Status
/// Status
///
/// - Since: 0.1.0
/// - Version: 3.3.0
/// # Last Update
/// 2021/1/28
/// # Reference
/// [Document](https://docs.joinmastodon.org/entities/status/)
public class Status: Codable {
public typealias ID = String
// Base
public let id: ID
public let uri: String
public let createdAt: Date
public let account: Account
public let content: String
public let visibility: String?
public let sensitive: Bool?
public let spoilerText: String?
public let application: Application?
// Rendering
public let mentions: [Mention]?
public let tags: [Tag]?
public let emojis: [Emoji]?
// Informational
public let reblogsCount: Int
public let favouritesCount: Int
public let repliesCount: Int?
public let url: String?
public let inReplyToID: Status.ID?
public let inReplyToAccountID: Account.ID?
public let reblog: Status?
public let poll: Poll?
public let card: Card?
public let language: String? // (ISO 639 Part 1 two-letter language code)
public let text: String?
// Authorized user
public let favourited: Bool?
public let reblogged: Bool?
public let muted: Bool?
public let bookmarked: Bool?
public let pinned: Bool?
enum CodingKeys: String, CodingKey {
case id
case uri
case createdAt = "created_at"
case account
case content
case visibility
case sensitive
case spoilerText = "spoiler_text"
case application
case mentions
case tags
case emojis
case reblogsCount = "reblogs_count"
case favouritesCount = "favourites_count"
case repliesCount = "replies_count"
case url
case inReplyToID = "in_reply_to_id"
case inReplyToAccountID = "in_reply_to_account_id"
case reblog
case poll
case card
case language
case text
case favourited
case reblogged
case muted
case bookmarked
case pinned
}
}
}
extension Mastodon.Entity.Status {
public enum Visibility: String, Codable {
case `public`
case unlisted
case `private`
case direct
}
}

View File

@ -0,0 +1,26 @@
//
// Mastodon+Entity+Tag.swift
//
//
// Created by MainasuK Cirno on 2021/1/28.
//
import Foundation
extension Mastodon.Entity {
/// Tag
///
/// - Since: 0.9.0
/// - Version: 3.3.0
/// # Last Update
/// 2021/1/28
/// # Reference
/// [Document](https://docs.joinmastodon.org/entities/tag/)
public struct Tag: Codable {
// Base
public let name: String
public let url: String
public let history: [History]?
}
}

View File

@ -1,34 +0,0 @@
//
// Mastodon+Entity+Toot.swift
//
//
// Created by MainasuK Cirno on 2021/1/27.
//
import Foundation
extension Mastodon.Entity {
public struct Toot: Codable {
public typealias ID = String
public let id: ID
public let createdAt: Date
public let content: String
public let account: User
public let language: String?
public let visibility: String
enum CodingKeys: String, CodingKey {
case id
case createdAt = "created_at"
case content
case account
case language
case visibility
}
}
}

View File

@ -1,31 +0,0 @@
//
// Mastodon+Entity+User.swift
//
//
// Created by MainasuK Cirno on 2021/1/27.
//
import Foundation
extension Mastodon.Entity {
public struct User: Codable {
public typealias ID = String
public let id: ID
public let username: String
public let acct: String
public let displayName: String?
public let avatar: String?
enum CodingKeys: String, CodingKey {
case id
case username
case acct
case displayName = "display_name"
case avatar
}
}
}

View File

@ -8,3 +8,14 @@
import Foundation
extension Mastodon.Entity { }
// MARK: - Entity Document Template
/// Entity Name
///
/// - Since: 0.1.0
/// - Version: x.x.x
/// # Last Update
/// yyyy/MM/dd
/// # Reference
/// [Title](link)

View File

@ -1,20 +0,0 @@
//
// Mastodon+Response+ErrorResponse.swift
//
//
// Created by MainasuK Cirno on 2021/1/27.
//
import Foundation
extension Mastodon.Response {
public struct ErrorResponse: Codable {
public let error: String
public let errorDescription: String?
enum CodingKeys: String, CodingKey {
case error
case errorDescription = "error_description"
}
}
}

View File

@ -6,10 +6,23 @@ final class MastodonSDKTests: XCTestCase {
var disposeBag = Set<AnyCancellable>()
let domain = "mstdn.jp"
let mstdnDomain = "mstdn.jp"
let pawooDomain = "pawoo.net"
let session = URLSession(configuration: .ephemeral)
}
extension MastodonSDKTests {
func testCreateAnAnpplication() throws {
func testCreateAnAnpplication_mstdn() throws {
try _testCreateAnAnpplication(domain: pawooDomain)
}
func testCreateAnAnpplication_pawoo() throws {
try _testCreateAnAnpplication(domain: pawooDomain)
}
func _testCreateAnAnpplication(domain: String) throws {
let theExpectation = expectation(description: "Create An Application")
let query = Mastodon.API.App.CreateQuery(
@ -35,9 +48,20 @@ final class MastodonSDKTests: XCTestCase {
wait(for: [theExpectation], timeout: 10.0)
}
}
extension MastodonSDKTests {
func testPublicTimeline() throws {
let theExpectation = expectation(description: "Create An Application")
func testPublicTimeline_mstdn() throws {
try _testPublicTimeline(domain: mstdnDomain)
}
func testPublicTimeline_pawoo() throws {
try _testPublicTimeline(domain: pawooDomain)
}
private func _testPublicTimeline(domain: String) throws {
let theExpectation = expectation(description: "Fetch Public Timeline")
let query = Mastodon.API.Timeline.PublicTimelineQuery()
Mastodon.API.Timeline.public(session: session, domain: domain, query: query)
@ -56,7 +80,6 @@ final class MastodonSDKTests: XCTestCase {
.store(in: &disposeBag)
wait(for: [theExpectation], timeout: 10.0)
}
}