Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
khanlou committed Sep 3, 2020
0 parents commit c292c9f
Show file tree
Hide file tree
Showing 45 changed files with 2,915 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.build
.swiftpm/**
1 change: 1 addition & 0 deletions .swift-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
5.2
55 changes: 55 additions & 0 deletions Demo/Database.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// Database.swift
// Meridian
//
// Created by Soroush Khanlou on 8/29/20.
//

import Foundation
import Meridian

struct Todo: Codable {
var id: UUID
var title: String
var completed: Bool = false
var order: Int

var url: String {
return "https://meridian-demo.herokuapp.com/todos/\(id)"
}

enum CodingKeys: String, CodingKey {
case id, title, completed, url, order
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = UUID()
title = try container.decode(String.self, forKey: .title)
self.order = try container.decodeIfPresent(Int.self, forKey: .order) ?? -1
}

init(title: String, completed: Bool) {
self.id = UUID()
self.title = title
self.completed = completed
self.order = -1
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(title, forKey: .title)
try container.encode(completed, forKey: .completed)
try container.encode(url, forKey: .url)
try container.encode(order, forKey: .order)
}
}

final class Database {
var todos: [Todo]

init() {
todos = []
}
}
123 changes: 123 additions & 0 deletions Demo/Todos.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//
// Todos.swift
//
//
// Created by Soroush Khanlou on 9/2/20.
//

import Foundation
import Meridian

// Specs
// https://www.todobackend.com/specs/index.html?https://meridian-demo.herokuapp.com/todos

extension Response {
func allowCORS() -> Response {
return self.additionalHeaders([
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "X-Requested-With, Origin, Content-Type, Accept",
"Access-Control-Allow-Methods": "POST, GET, PUT, OPTIONS, DELETE, PATCH",
])
}
}

struct ListTodos: Route {

static let route: RouteMatcher = "/todos"

@EnvironmentObject var database: Database

func execute() throws -> Response {
JSON(database.todos)
.allowCORS()
}

}

struct ClearTodos: Route {
static let route: RouteMatcher = .delete("/todos")

@EnvironmentObject var database: Database

func execute() throws -> Response {
self.database.todos = []
return JSON(database.todos)
.allowCORS()
}
}

struct CreateTodo: Route {
static let route: RouteMatcher = .post("/todos")

@JSONBody var todo: Todo

@EnvironmentObject var database: Database

func execute() throws -> Response {
self.database.todos.append(todo)
return JSON(todo)
.statusCode(.created)
.allowCORS()
}
}

struct ShowTodo: Route {
static let route: RouteMatcher = "/todos/\(.id)"

@URLParameter(key: .id) var id: String

@EnvironmentObject var database: Database

func execute() throws -> Response {
guard let todo = database.todos.first(where: { $0.id.uuidString == id }) else {
throw NoRouteFound()
}
return JSON(todo).allowCORS()
}

}

struct TodoPatch: Codable {
var title: String?
var completed: Bool?
var order: Int?
}

struct EditTodo: Route {
static let route: RouteMatcher = .patch("/todos/\(.id)")

@URLParameter(key: .id) var id: String

@JSONBody var patch: TodoPatch

@EnvironmentObject var database: Database

func execute() throws -> Response {
guard let index = database.todos.firstIndex(where: { $0.id.uuidString == id }) else {
throw NoRouteFound()
}
if let newTitle = patch.title {
database.todos[index].title = newTitle
}
if let newCompleted = patch.completed {
database.todos[index].completed = newCompleted
}
if let newOrder = patch.order {
database.todos[index].order = newOrder
}
return JSON(database.todos[index]).allowCORS()
}
}

struct DeleteTodo: Route {
static let route: RouteMatcher = .delete("/todos/\(.id)")

@URLParameter(key: .id) var id: String

@EnvironmentObject var database: Database

func execute() throws -> Response {
database.todos.removeAll(where: { $0.id.uuidString == id })
return EmptyResponse().allowCORS()
}
}
27 changes: 27 additions & 0 deletions Demo/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// main.swift
// MeridianDemo
//
// Created by Soroush Khanlou on 8/26/20.
//

import Foundation
import Backtrace
import Meridian

Backtrace.install()

let app = Server(
routes: [
DeleteTodo.self,
EditTodo.self,
ShowTodo.self,
ClearTodos.self,
CreateTodo.self,
ListTodos.self,
],
errorRenderer: JSONErrorRenderer.self
)
.environmentObject(Database())

app.listen()
8 changes: 8 additions & 0 deletions LinuxMain.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import XCTest

import MeridianTests

var tests = [XCTestCaseEntry]()
tests += MeridianTests.__allTests()

XCTMain(tests)
30 changes: 30 additions & 0 deletions Meridian/EnvironmentStorage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// EnvironmentStorage.swift
// Meridian
//
// Created by Soroush Khanlou on 8/29/20.
//

import Foundation

public struct EnvironmentKey: Hashable {
private let id = UUID()

public init() {

}
}

extension EnvironmentKey {
public static let dateFormatter = EnvironmentKey()
}

final class EnvironmentStorage {

static let shared = EnvironmentStorage()

var objects: [AnyObject] = []

var keyedObjects: [EnvironmentKey: AnyObject] = [:]

}
34 changes: 34 additions & 0 deletions Meridian/Errors/ErrorRenderer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// ErrorRenderer.swift
// Meridian
//
// Created by Soroush Khanlou on 8/30/20.
//

import Foundation

public protocol ErrorRenderer {

init(error: Error)

func render() throws -> Response

}

struct ErrorContainer: Codable {
let message: String
}

public struct JSONErrorRenderer: ErrorRenderer {

public let error: Error

public init(error: Error) {
self.error = error
}

public func render() throws -> Response {
JSON(ErrorContainer(message: (error as? ErrorWithMessage)?.message ?? "An error occurred"))
.statusCode( (error as? ErrorWithStatusCode)?.statusCode ?? .badRequest)
}
}
Loading

0 comments on commit c292c9f

Please sign in to comment.