Logging in Swift

Ofcourse we can use print statements to output messages but this is pretty basic, a preference is for a logging framework which allows us to log different levels of information, for example debug, trace, info, error etc.

The documentation Generating Log Messages from Your Code shows that for macOS 11 and later to use the Logger structure. We can find a Logger implementation here apple swift-log.

We’ll need to update the Package.swift file in the Package dependencies as per the following

.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),

Obviously change the version in the above to suit the latest available version.

Next, add the following to the (in my case) executableTarget

.product(name: "Logging", package: "swift-log")

To use the library in your code, declare a variable/const, for example

let logger: Logger = Logger(label: "com.putridparrot.EurekaService")

The label is output to the logger to distinguish the components which are logging.

Now to log we simply use

logger.info("Sending data")

The output would look like this

2022-03-05T16:39:52+0000 info com.putridparrot.EurekaService : Sending data

Out of the box the output is streamed to stderr, for example

LoggingSystem.bootstrap(StreamLogHandler.standardError)

Checkout the swift-log repository for more information on alternate LogHandlers.

Installing Erlang on Ubuntu 20.x

The steps below are taken from the sites listed in the reference, recreating here for my reference.

Installing

sudo apt update
sudo apt install software-properties-common apt-transport-https
wget -O- https://packages.erlang-solutions.com/ubuntu/erlang_solutions.asc | sudo apt-key add -
echo "deb https://packages.erlang-solutions.com/ubuntu focal contrib" | sudo tee /etc/apt/sources.list.d/erlang.list
sudo apt update
sudo apt install erlang

Checking it all worked

Let’s check if everything worked. You can just run

erl

from your terminal OR let’s write the good ol’ hello world by create helloworld.erl and add the following code

-module(helloworld). 
-export([start/0]). 
start() -> io:fwrite("Hello World\n").

Run this using

erlc helloworld.erl
// then
erl -noshell -s helloworld start -s init stop

If all went well you’ll see Hello World written to standard io.

To add the last bit of the How to Install Erlang in Ubuntu post, to remove Erlang run

sudo apt remove erlang
sudo apt autoremove

References

How To Install Erlang on Ubuntu 22.04|20.04|18.04
How to Install Erlang in Ubuntu

Interacting with Eureka from Swift 5

I wanted to get my little Swift console application (developed in the post Creating a console app. in Swift (on Linux)) working with Eureka.

So in the last post I outlined the simple steps to get Eureka up and running in docker. Now let’s combine these posts along with the information from my the post Interacting with Eureka via it’s REST operations and create ourselves a Swift Eureka client.

Before I get started, I have not found the generation of JSON from types or for that matter generating the Dictionary required for JSONSerialization as intuitive as C# (for example). I’m not sure if there’s a better way to do these things, but this code works so it’ll do for now. But if you know a better way please use it.

To start with we’re just going to manually generate our JSON string to match the POST request that we need to tell EUREKA that our application is starting

let json: String = "{" +
  "\"instance\": {" +
    "\"hostName\": \"myhost\"," +
    "\"app\": \"SWIFT-EUREKA\"," +
    "\"vipAddress\": \"myservice\"," +
    "\"secureVipAddress\": \"myservice\"," +
    "\"ipAddr\": \"10.0.0.10\"," +
    "\"status\": \"STARTING\"," +
    "\"port\": {\"$\": \"8080\", \"@enabled\": \"true\"}," +
    "\"securePort\": {\"$\": \"8443\", \"@enabled\": \"true\"}," +
    "\"healthCheckUrl\": \"http://myservice:8080/healthcheck\"," +
    "\"statusPageUrl\": \"http://myservice:8080/status\"," +
    "\"homePageUrl\": \"http://myservice:8080\"," +
    "\"dataCenterInfo\": {" +
      "\"@class\": \"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo\", " +
      "\"name\": \"MyOwn\"" +
    "}" +
  "}" +
"}"

Hopefully this is fairly obvious (even with all the escaping characters) what we’re doing, but to use this in JSONSerialization.data method is seems we need to convert this string to a Dictionary or Array, so here’s an extension method to convert to a dictionary

extension String {
  func toDictionary() -> [String:AnyObject]? {
    if let data = self.data(using: .utf8) {
      return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:AnyObject]
    }
    return nil
  } 
}

There’s zero error handling here – I leave it to the reader to add that.

So using the extension method toDictionary we’ll convert our JSON string into a dictionary which is then passed into the JSONSerialization.data to create a Data object, i.e.

let data = json.toDictionary()
let httpBody = try JSONSerialization.data(withJSONObject: data!, options: [])

Now we’ll create the URL and URLRequest to call the Eureka service

let url = URL(string: "http://192.168.1.1:8761/eureka/apps/swift-eureka")
var request = URLRequest(url: url!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.httpBody = httpBody
request.timeoutInterval = 20

The rest of the code is the same as Creating a console app. in Swift (on Linux) but for completeness it’s list below in it’s entirety (don’t forget the extension method toDictionary is in some extension file).

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

let session = URLSession.shared
let semaphore = DispatchSemaphore(value: 0)

let json: String = "{" +
  "\"instance\": {" +
    "\"hostName\": \"myhost\"," +
    "\"app\": \"SWIFT-EUREKA\"," +
    "\"vipAddress\": \"myservice\"," +
    "\"secureVipAddress\": \"myservice\"," +
    "\"ipAddr\": \"10.0.0.10\"," +
    "\"status\": \"DOWN\"," +
    "\"port\": {\"$\": \"8080\", \"@enabled\": \"true\"}," +
    "\"securePort\": {\"$\": \"8443\", \"@enabled\": \"true\"}," +
    "\"healthCheckUrl\": \"http://myservice:8080/healthcheck\"," +
    "\"statusPageUrl\": \"http://myservice:8080/status\"," +
    "\"homePageUrl\": \"http://myservice:8080\"," +
    "\"dataCenterInfo\": {" +
      "\"@class\": \"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo\", " +
      "\"name\": \"MyOwn\"" +
    "}" +
  "}" +
"}"

let data = json.toDictionary()
let httpBody = try? JSONSerialization.data(withJSONObject: data!, options: [])

let url = URL(string: "http://192.168.0.88:8761/eureka/apps/swift-eureka")
var request = URLRequest(url: url!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.httpBody = httpBody
request.timeoutInterval = 20

session.dataTask(with: request) { (data, response, error) in
    let result = String.init(data: data!, encoding: .utf8)
    print(result!)
    semaphore.signal()
}.resume()

semaphore.wait()

At this time I haven’t got a great solution for using types to define our request as the request has some invalid name keys, such as $ and @enabled (again there may be a solution which I’ve not come across yet). To give the reader a starting point, here’s the code I have for turning my EurekaRequest type into a JSON string.

I’ve not included the EurekaRequest so this really just shows a way to convert a type to a JSON string.

let instance = EurekaRequest()
let encoder = JSONEncoder();
// required to stop // becoming \/
encoder.outputFormatting = .withoutEscapingSlashes
let jsonData = try encoder.encode(instance)

// convert to a string
var json = String(data: jsonData, encoding: String.Encoding.utf8)!;

Eureka in Docker

I’ve actually covered this in a previous post, but because it was part of Eureka Server (using Steeltoe) revisited. It didn’t quite stand out when I was searching for it, so let’s recreate here in it’s own post…

Pull the docker image using

docker pull steeltoeoss/eureka-server

Now to run it

docker run --publish 8761:8761 steeltoeoss/eureka-server

This will now be available locally or remotely on port 8761, i.e.

	
http://locahost:8761

Creating a console app. in Swift (on Linux)

The first thing we need to do is use the Swift CLI to generate the project etc. so run

swift package init --type executable

This will generate Sources folder, Tests, the Package.swift and even a README.md and .gitignore.

Now in the previous post we created a simple hello world Vapor REST server, so let’s write the code to call that from our console app.

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

let session = URLSession.shared
let url = URL(string: "http://192.168.1.1:8080")

let semaphore = DispatchSemaphore(value: 0)

session.dataTask(with: url!) { (data, response, error) in
    let result = String.init(data: data!, encoding: .utf8)
    print(result!)
    semaphore.signal()
}.resume()

semaphore.wait()

Let’s break down this code…

The first bit of interest is the import. We need FoundationNetworking for the URLSession.shared within the Linux implementation (if I understand correctly) but not in the Mac OS version (I need to verify that). To allow us to only import FoundationNetworking if required, we use

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

As you’ve probably surmised URLSession is a shared resource, hence the shared singleton property.

Next we need the URL, which is pretty explanatory. Now the URLSession calls are asynchronous, hence if we just let this code run without the semaphore we’ll exit the application before the response from the web service returns.

The resume method is required to start the task (seems the wrong name to me, but there you go). See dataTask.

Finally we wait on the signal from the semaphore. Within the actual dataTask we’re just going to assume there’s data and no error (ofcourse in a real world application you’ll want to add a guard etc. to check for such things and also check the optionals actual have some value before unwrapping).

In this simple example we’ll simply assuming all worked, unwrap the data and print it to the console. Ofcourse the data will be encoded, so we’re need to create a string from the data and the required encoding as per the String.init.

Now just run the command

swift run

If all went well you’ll see the root level response from the web server code we wrote in the last post.

Getting started with Vapor

Vapor is a web application framework written in Swift.

Installing on Linux

As I’m more likely to be using a Linux server for my web application, that’s what we’ll use for this post. So following the Install on Linux instructione, I went the route of installing the toolbox for the toolbox repo (instructions recreated below)

git clone https://github.com/vapor/toolbox.git
cd toolbox
git checkout <desired version>
make install

This will build and install vapor.

Generating an application

Vapor will generate an application for us using

vapor new hello-vapor -n

(obviously replace hello-vapor with your application name).

As we’re using Linux, we’ll cd into the hello-vapor application folder and then run

swift run

This will run, by default on localhost:8080 which is not great if you want to access remotely, so let’s instead start the application using

swift run Run --hostname 0.0.0.0 --port 8080

Now if you connect to the server’s ip address, port 8080 you should see the default return “It works!” if you use http://localhost:8080/hello you’ll see “Hello, world!” returned.

If you take a look at the source generated by Vapor and check the folder Sources/App/routes.swift you’ll see code like this

import Vapor

func routes(_ app: Application) throws {
    app.get { req in
        return "It works!"
    }

    app.get("hello") { req -> String in
        return "Hello, world!"
    }
}

As you can see this is routing the /hello GET method to return “Hello, world!”.

Using HTTP methods

As you can see from the previous code, app.get denotes the method is a GET method, so let’s simply write an example of each of the HTTP methods, so change your routes.swift file to add the following

app.get("method") { req -> String in
   return "\(req)"
}

app.post("method") { req -> String in
   return "\(req)"
}

app.put("method") { req -> String in
   return "\(req)"
}

app.patch("method") { req -> String in
   return "\(req)"
}

app.delete("method") { req -> String in
   return "\(req)"
}

This doesn’t cover all HTTP methods, but we can use the alternate syntax to the above, which looks like this

app.on(.GET, "method") { req -> String in
   return "\(req)"
}

and simply replace .GET with each of the HTTP methods, such as .CONNECT, .OPTIONS, etc.

Now let’s tests these – if we simply use CURL to execute each method, i.e.

curl -X GET localhost:8080/method
curl -X POST localhost:8080/method
curl -X PUT localhost:8080/method
curl -X PATCH localhost:8080/method
curl -X DELETE localhost:8080/method

Parameters and Query Parameters

We’re also likely to need to handle parameters, so for example /method/aparam

app.get("method", ":param") { req -> String in
   let param = req.parameters.get("param")!        
   return "\(param) --> \(req)"
}

The : prefixing the param token is indicates the parameter is dynamic and hence we need to use req.parameters.get(“param”)! to get the value from the param.

Finally for this post, what about if we want to pass query parameters, for example http://192.168.0.88:8080/method?firstName=Scooby&lastName=Doo

We can declare a struct to handle our expected query parameters like this

struct Person: Content {
    var firstName: String?
    var lastName: String?
}

and now change our GET method to decode the query parameters into this struct, for example

app.get("method") { req -> String in
   let person = try req.query.decode(Person.self)
   return "Firstname: \(person.firstName!), Lastname \(person.lastName!)"
}

There’s a whole lot more functionality, as you’d expect, including streaming, validation etc. take a look at the Vapor Docs.

Publishing my first Rust library to crates.io

As part of my end of 2021 project, I rewrote my F# units of measurement converters to a format which allowed me to code generate a bunch of libraries in different languages, currently including the original F#, C# (both idiomatic to their language), Java, Python, Swift, TypeScript and Rust.

I’ve got the F# and C# ones available on nuget and the TypeScript on npmjs, I wanted to try to get the Rust library onto crates.io.

How do we do this?

  • First off, check if your package name is available on crates.io, if it is then you’ll need to change your name. Rust creates do not support namespaces so you cannot reuse an existing name but prefix with your company or whatever
  • Load up https://crates.io/ and create an account, I think using GitHub to login was the only option, so that’s what I did
  • Ensure you have an email within your profile page and that it’s verified
  • You can go to the API Tokens page and create a new token and a name for the token – you’ll need to copy the API token for the publish step (and keep it for further publishes unless you wish to revoke and regenerate – obviously not too useful in CI. If you set the environment variable CARGO_REGISTRY_TOKEN to the token then cargo will use it from there.
  • Ensure your code is committed (if you’re using source control) or you can always specify the –allow-dirty option if you prefer not to commit changes
  • Ensure you lib.rs has the correct version for your library setup. Semantic versioning is expected, hence you can use -alpha or the likes on your version
  • Now run
    cargo login abcdefghijklmnopqrstuvwxyz
    

    replacing abcdefghijklmnopqrstuvwxyz with your API token

  • Next run
    cargo publish
    

If all the above steps went well you can now search crates.io for your library.

SwiftUI – why is my Mac window not resizable?

As a newbie to SwiftUI, I was surprised to find my simple little sample application would not resize. If you’re used to WPF, WinForms and even other non-Windows user interface libraries, you’ll notice by default the windows are usually resizable. So what’s the deal with SwiftUI

So my code looked like this

var body: some View {
   VStack {
      Text(viewModel.title)
         .padding()
      TextField("Enter", text: $viewModel.title)
   }
   .padding()
   .frame(minWidth: 500, minHeight: 300)
}

Simple enough, display a text label and input text field.

So what’s the problem, why is the Man window not resizable?

Spacer

The Spacer is key here. If we change the code to the following, the it all starts to work as I wanted

var body: some View {
   VStack {
      Text(viewModel.title)
         .padding()
      TextField("Enter", text: $viewModel.title)
      Spacer()
   }
   .padding()
   .frame(minWidth: 500, minHeight: 300)
}

The Spacer expands to fill space.

If you have a Spacer within a VStack the spacer fills space on the vertical and if on an HStack it fills horizontally. But the above code will expand on both the vertical and horizontal. In this case the TextField seems to combine with the Spacer to allow both the axis to expand.

Setting up swift on Ubuntu 20.04

This is an update to Setting up swift on Ubuntu 18.04 – installing Swift on Ubuntu 20.04.

Check out Downloads for the current info. from swift.org.

  • We need to install dependencies, so start with
    apt-get install binutils git gnupg2 libc6-dev libcurl4 libedit2 libgcc-9-dev libpython2.7 libsqlite3-0 libstdc++-9-dev libxml2 libz3-dev pkg-config tzdata uuid-dev zlib1g-dev
    
  • Next we need to download the tar for the version of Swift you’re targeting, mine’s Swift 5.5.2
    wget https://download.swift.org/swift-5.5.2-release/ubuntu2004/swift-5.5.2-RELEASE/swift-5.5.2-RELEASE-ubuntu20.04.tar.gz
    

Next up we should verify this download

  • First download the signature using
    wget https://swift.org/builds/swift-5.5.2-release/ubuntu2004/swift-5.5.2-RELEASE/swift-5.5.2-RELEASE-ubuntu20.04.tar.gz.sig
    
  • Now import the key
    wget -q -O - https://swift.org/keys/all-keys.asc | gpg --import -
    
  • Now run the following
    gpg --keyserver hkp://keyserver.ubuntu.com --refresh-keys Swift
    gpg --verify swift-5.5.2-RELEASE-ubuntu20.04.tar.gz.sig
    

Assuming everything’s verified we now want to extract the download into our preferred location – in my case I am just creating a folder name ~/Swift and extracting the tar to this location, running the following from this location

tar xzf swift-5.5.2-RELEASE-ubuntu20.04.tar.gz

Finally let’s update the path – I’ve added this to the last line of my .bashrc

export PATH=~/Swift/swift-5.5.2-RELEASE-ubuntu20.04/usr/bin:"${PATH}"

Struct, Class and now Record types in C#

I thought it’d be interesting to compare the three C# types, struct, class and record. I’m sure we all know that a struct is a value type and a class a reference type, but what’s a record.

The record “keyword defines a reference type that has built-in functionality for encapsulating data” (see Records (C# reference)).

Before we get into record. let’s review what struct and classes give us using the example of a Person type which has a FirstName property.

struct

struct Person
{
  public string FirstName { get; set; }
}
  • A struct extends System.ValueType and structs are “allocated either on the stack or inline in containing types and deallocated when the stack unwinds or when their containing type gets deallocated. Therefore, allocations and deallocations of value types are in general cheaper than allocations and deallocations of reference types.” See Choosing Between Class and Struct.
  • ToString() will output YouNameSpace.Person by default.
  • Structs cannot be derived from anything else (although they can implement interfaces).
  • Structs should be used instead of classes where instances are small and short-lived or are commonly embedded in other objects.
  • As structs are ValueTypes they are boxed or cast to a reference type or one of the interfaces then implement.
  • As a function of being a ValueType, structs are passed by value

class

class Person
{
  public string FirstName { get; set; }
}
  • A class extends System.Object and classes are reference types and allocated on the heap.
  • ToString() will output YouNameSpace.Person by default.
  • Obviously classes may extend other class but cannot extend a record and vice versa, records cannot extend classes.
  • Reference types are passed by reference.

record

Records can have properties supplied via the constructor and the compiler turns these into readonly properties. The syntax is similar to TypeScript in that we can declare the properties in a terse syntax, such as

record Person(string FirstName);

Whilst records are primarily aimed at being immutable we can still declare them with mutability, i.e.

class Person
{
  public string FirstName { get; set; }
}
  • Records can only inherit for other records, for example
    record Scooby() : PersonRecord("Scooby");
    
  • Records use value comparison (not reference comparison) via IEquatable
  • ToString formats output to show the property values
  • Records support the with keyword which allows us to create a copy of a record and mutate at the point of copying. Obviously as records are generally aimed at being immutable this use of with allows us to copy an existing record with some changes, for example
    var p = somePerson with { FirstName = "Scrappy" };
    

    This is not a great example with our record which has a single property, but if we assume Person also had a LastName property set to “Doo” then we’d essentially have created a new record with the same LastName but now with the new FirstName of “Scrappy”.

    We can also copy whole records by not supplying any values within the { } i.e.

    var p = somePerson with { };